세션·로그인 제한, ZIP/업로드 스트리밍, CSP 최소 강제와 Report-Only, Playwright 격리 검증을 추가하고 미디어 목록을 reconcile 후 database 모드로 고정한다. Co-authored-by: Cursor <cursoragent@cursor.com>
100 lines
1.9 KiB
JavaScript
100 lines
1.9 KiB
JavaScript
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 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) => {
|
|
const html = String(value || '').trim()
|
|
return html ? sanitizeHtml(html, HTML_BLOCK_SANITIZE_OPTIONS).trim() : ''
|
|
}
|