Why: Preview setup clears a node with innerHTML = '' before appending sanitized children, and the ratchet treated that space as a write. Evidence: npx vitest run test/html-sinks.test.ts. Non-empty assignments still match. Impact: The preview path can keep clearing nodes. Writing HTML strings in components still fails the test. Refs: docs/records/2026-10-04-sillytavern-review.md
30 lines
1.2 KiB
TypeScript
30 lines
1.2 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`)
|
|
// 空白放进前瞻里,避免 \s* 回退到空格前面、把 innerHTML = '' 误判成写入。
|
|
if (/innerHTML\s*=(?!\s*['"]['"])/.test(text)) hits.push(`${rel}: innerHTML`)
|
|
}
|
|
expect(hits).toEqual([])
|
|
})
|
|
}) |