test(criteria): 抽出共享 check/finish(marker 不再靠记性)+ 失败信息自带修法

pi 的三条增量,前两条落地:

1. **错误信息自带修法**:受众不只是读过规范的人 —— 并发写 WebUI 的 agent 新加判据时不会打开
   CRITERIA.md,看到红的第一反应可能是"套件坏了"。所以把可照抄的修法写进那条错误本身
   (共享 helper 的用法 + 样板文件路径),并说明 node:test 的判据不用管。
   **red 是 ta 一定会看到的,文档不一定被打开。**

2. **marker 由共享 helper 打印**:新增 test/lib/checks.mjs(导出 check/finish),
   计数只可能在该模块内发生 → "漏打 marker"与"计数写错位置"这两类在新文件上不可能发生。
   为避免"写了没人用"(本仓踩过的坑),同时把两个手工计数的判据改用它:
   narrow-layout(原来只有 failed 计数)与 markdown-xss(原来根本没有计数器)——
   条数不变(52 / 9),套件仍全绿。

未回改其余 10 个文件:run-all 的 marker 检查已经覆盖它们。
This commit is contained in:
2026-09-14 15:11:47 +08:00
parent d1e0ba32f9
commit ec90cba129
4 changed files with 73 additions and 27 deletions

View File

@ -9,6 +9,7 @@ import { renderToStaticMarkup } from 'react-dom/server';
import React from 'react';
import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { check, finish } from './lib/checks.mjs';
// 只有【真实标签】里的危险内容才算漏。
// 注意不能直接搜 onerror=:raw HTML 被转义成 <img … onerror=" 后,
@ -27,19 +28,14 @@ const payloads = [
'**bold** `code`',
];
let leaks = 0;
// 每条 payload 就是一条判据(换来的是"跑了几条"可核对,而不是只报一句总括)
console.log('Markdown 注入回归:');
for (const p of payloads) {
const html = renderToStaticMarkup(
React.createElement(Markdown, { remarkPlugins: [remarkGfm] }, p)
);
const bad = dangerous.test(html);
if (bad) leaks++;
console.log((bad ? 'LEAK ' : 'safe '), JSON.stringify(p), '->', html.slice(0, 80));
check(`危险内容被清除:${JSON.stringify(p)}`, !bad, `渲染结果里出现了真实可执行的危险标记:${html.slice(0, 120)}`);
}
if (leaks > 0) {
console.error(`\n失败:${leaks} 处 XSS 泄漏`);
process.exit(1);
}
console.log('\n通过:raw HTML 被转义,javascript: URL 被清空');
// 机器可读汇总:每条 payload 算一条(leaks 就是 fail)
console.log(`RESULT pass=${payloads.length - leaks} fail=${leaks}`);
finish('Markdown 注入');