Why: The conversation and office fallbacks wrote marked output straight into the page, including converted docx HTML. Evidence: html-sinks ratchet forbids new innerHTML writes outside @ui safeHtml. Impact: Conversation keeps line-break rendering. Unsafe links are stripped after docx and pptx preview. dompurify is deduped with the UI package. Refs: feature/sillytavern-review Co-authored-by: Cursor <cursoragent@cursor.com>
29 lines
1.1 KiB
TypeScript
29 lines
1.1 KiB
TypeScript
// 棘轮:宿主侧的 HTML 写入必须走 @ui safeHtml,不能在组件里直接塞字符串。
|
|
import { describe, expect, it } from 'vitest'
|
|
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
import { join, relative, dirname } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const SRC = join(dirname(fileURLToPath(import.meta.url)), '..', 'src')
|
|
|
|
function walk(dir: string, out: string[] = []): string[] {
|
|
for (const name of readdirSync(dir)) {
|
|
const p = join(dir, name)
|
|
if (statSync(p).isDirectory()) walk(p, out)
|
|
else if (/\.(ts|tsx)$/.test(name)) out.push(p)
|
|
}
|
|
return out
|
|
}
|
|
|
|
describe('html sinks', () => {
|
|
it('frontend components do not write HTML themselves', () => {
|
|
const hits: string[] = []
|
|
for (const file of walk(SRC)) {
|
|
const text = readFileSync(file, 'utf8')
|
|
const rel = relative(SRC, file)
|
|
if (text.includes('dangerouslySetInnerHTML')) hits.push(`${rel}: dangerouslySetInnerHTML`)
|
|
if (/innerHTML\s*=\s*(?!['"]['"])/.test(text)) hits.push(`${rel}: innerHTML`)
|
|
}
|
|
expect(hits).toEqual([])
|
|
})
|
|
}) |