v1.10.88 품질·보안 개선과 미디어 카탈로그 database 전환을 반영한다.
세션·로그인 제한, ZIP/업로드 스트리밍, CSP 최소 강제와 Report-Only, Playwright 격리 검증을 추가하고 미디어 목록을 reconcile 후 database 모드로 고정한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,35 +1,99 @@
|
||||
const DANGEROUS_TAG_NAMES = [
|
||||
'script',
|
||||
'iframe',
|
||||
'object',
|
||||
'embed',
|
||||
'form',
|
||||
'input',
|
||||
'button',
|
||||
'textarea',
|
||||
'select',
|
||||
'meta',
|
||||
'link',
|
||||
'base'
|
||||
import sanitizeHtml from 'sanitize-html'
|
||||
|
||||
const ALLOWED_HTML_BLOCK_TAGS = [
|
||||
'a',
|
||||
'abbr',
|
||||
'b',
|
||||
'blockquote',
|
||||
'br',
|
||||
'caption',
|
||||
'code',
|
||||
'col',
|
||||
'colgroup',
|
||||
'dd',
|
||||
'details',
|
||||
'div',
|
||||
'dl',
|
||||
'dt',
|
||||
'em',
|
||||
'figcaption',
|
||||
'figure',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'hr',
|
||||
'i',
|
||||
'img',
|
||||
'li',
|
||||
'mark',
|
||||
'ol',
|
||||
'p',
|
||||
'pre',
|
||||
's',
|
||||
'small',
|
||||
'span',
|
||||
'strong',
|
||||
'sub',
|
||||
'summary',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'td',
|
||||
'tfoot',
|
||||
'th',
|
||||
'thead',
|
||||
'time',
|
||||
'tr',
|
||||
'u',
|
||||
'ul'
|
||||
]
|
||||
|
||||
const dangerousTagPattern = new RegExp(`<\\s*(${DANGEROUS_TAG_NAMES.join('|')})\\b[\\s\\S]*?<\\s*\\/\\s*\\1\\s*>`, 'gi')
|
||||
const dangerousSingleTagPattern = new RegExp(`<\\s*(${DANGEROUS_TAG_NAMES.join('|')})\\b[^>]*\\/?>`, 'gi')
|
||||
const HTML_BLOCK_SANITIZE_OPTIONS = {
|
||||
allowedTags: ALLOWED_HTML_BLOCK_TAGS,
|
||||
allowedAttributes: {
|
||||
'*': ['class', 'title', 'aria-label', 'aria-hidden'],
|
||||
a: ['href', 'target', 'rel'],
|
||||
col: ['span', 'width'],
|
||||
img: ['src', 'alt', 'width', 'height', 'loading'],
|
||||
li: ['value'],
|
||||
ol: ['start', 'type'],
|
||||
td: ['colspan', 'rowspan', 'headers'],
|
||||
th: ['colspan', 'rowspan', 'headers', 'scope'],
|
||||
time: ['datetime']
|
||||
},
|
||||
allowedSchemes: ['http', 'https', 'mailto', 'tel'],
|
||||
allowedSchemesAppliedToAttributes: ['href', 'src'],
|
||||
allowProtocolRelative: false,
|
||||
nonTextTags: ['script', 'style', 'textarea', 'option', 'template', 'noscript'],
|
||||
transformTags: {
|
||||
a: (tagName, attribs) => {
|
||||
if (attribs.target !== '_blank') {
|
||||
return { tagName, attribs }
|
||||
}
|
||||
|
||||
const relValues = new Set(String(attribs.rel || '').split(/\s+/).filter(Boolean))
|
||||
relValues.add('noopener')
|
||||
relValues.add('noreferrer')
|
||||
|
||||
return {
|
||||
tagName,
|
||||
attribs: {
|
||||
...attribs,
|
||||
rel: [...relValues].join(' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 본문 HTML 블록에서 실행성 코드와 위험 속성을 제거한다.
|
||||
* @param {string} value - 원본 HTML
|
||||
* @returns {string} 정리된 HTML
|
||||
*/
|
||||
export const sanitizeHtmlBlock = (value) => String(value || '')
|
||||
.replace(dangerousTagPattern, '')
|
||||
.replace(dangerousSingleTagPattern, '')
|
||||
.replace(/\s+on[a-z]+\s*=\s*"[^"]*"/gi, '')
|
||||
.replace(/\s+on[a-z]+\s*=\s*'[^']*'/gi, '')
|
||||
.replace(/\s+on[a-z]+\s*=\s*[^\s>]+/gi, '')
|
||||
.replace(/\s+(href|src)\s*=\s*"javascript:[^"]*"/gi, '')
|
||||
.replace(/\s+(href|src)\s*=\s*'javascript:[^']*'/gi, '')
|
||||
.replace(/\s+(href|src)\s*=\s*javascript:[^\s>]+/gi, '')
|
||||
.replace(/\s+style\s*=\s*"[^"]*expression\s*\([^"]*"/gi, '')
|
||||
.replace(/\s+style\s*=\s*'[^']*expression\s*\([^']*'/gi, '')
|
||||
.trim()
|
||||
export const sanitizeHtmlBlock = (value) => {
|
||||
const html = String(value || '').trim()
|
||||
return html ? sanitizeHtml(html, HTML_BLOCK_SANITIZE_OPTIONS).trim() : ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user