feat(v1.2.6): 合并审核整改、确认完全原创并完善可选 API 披露
版本升至 1.2.6(1.2.5 审核被拒未发布,整改内容并入本版);README 新增原创声明(4.2.1/4.2.2 不适用);Hero 描述来源默认 none,选一言自动填入官方接口、清空回退本地句子库;文档列出两个 IP 定位候选接口与访客 IP 披露文案;含上一轮 4.4.2 内容渲染安全与 5.2 字体许可证修复。
This commit is contained in:
1 parent
dda86294b0
commit
c92429d139
18 files changed
+485
-63
No files matched your search
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 结构化数据 / 内容渲染安全冒烟测试(审核指南 4.4.2)
|
||||
*
|
||||
* 从构建产物 templates/post.html 提取 JSON-LD 生成脚本,用桩 DOM 执行,验证:
|
||||
* - 输出的 JSON-LD 合法、字段完整、可选字段缺省时不产生非法 JSON
|
||||
* - 标题里注入的脚本闭合标签只作为普通字符串保留,不会变成可执行内容
|
||||
* - 写入方式为 textContent,且模板中的 JSON-LD 标签初始为空
|
||||
*
|
||||
* 仅用 Node 内置模块,无浏览器 / 第三方依赖;需先执行 pnpm build。
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import vm from "node:vm";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const postHtmlPath = path.join(root, "templates", "post.html");
|
||||
|
||||
if (!fs.existsSync(postHtmlPath)) {
|
||||
console.error("[jsonld-smoke] 未找到 templates/post.html,请先执行 pnpm build");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const html = fs.readFileSync(postHtmlPath, "utf8");
|
||||
const blocks = [...html.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi)].map((m) => m[1]);
|
||||
const code = blocks.find((block) => block.includes("wi-article-jsonld"));
|
||||
if (!code) {
|
||||
console.error("[jsonld-smoke] 未找到 JSON-LD 生成脚本");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let pass = 0;
|
||||
let fail = 0;
|
||||
|
||||
function check(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✔ ${name}`);
|
||||
pass++;
|
||||
} catch (error) {
|
||||
console.error(` ✘ ${name} -> ${error.message}`);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
function render(values) {
|
||||
const target = { textContent: "" };
|
||||
const document = {
|
||||
querySelector: (selector) =>
|
||||
values[selector] == null ? null : { getAttribute: () => values[selector] },
|
||||
getElementById: (id) => (id === "wi-article-jsonld" ? target : null),
|
||||
title: "fallback title",
|
||||
};
|
||||
let executed = false;
|
||||
vm.runInNewContext(
|
||||
code,
|
||||
{ document, JSON, console, eval: () => (executed = true) },
|
||||
{ timeout: 1000 }
|
||||
);
|
||||
return { raw: target.textContent, executed };
|
||||
}
|
||||
|
||||
const base = {
|
||||
'meta[property="og:title"]': "普通标题",
|
||||
'meta[property="og:url"]': "https://example.com/archives/a",
|
||||
'meta[property="article:published_time"]': "2026-11-11",
|
||||
'meta[name="author"]': "暖心向阳335",
|
||||
'meta[property="og:site_name"]': "WarmIsland",
|
||||
'meta[property="og:image"]': "https://example.com/cover.png",
|
||||
'meta[property="og:description"]': '含"引号"与\n换行的摘要',
|
||||
};
|
||||
|
||||
check("正常文章生成合法 JSON-LD", () => {
|
||||
const data = JSON.parse(render(base).raw);
|
||||
if (data["@type"] !== "Article" || data.headline !== "普通标题") {
|
||||
throw new Error("标题 / 类型不符");
|
||||
}
|
||||
if (data.author.name !== "暖心向阳335") throw new Error("作者不符");
|
||||
if (!data.description.includes("\n")) throw new Error("换行丢失");
|
||||
if (data.image !== base['meta[property="og:image"]']) throw new Error("封面丢失");
|
||||
});
|
||||
|
||||
check("模板里的 JSON-LD 标签初始为空(动态内容不经 HTML 解析)", () => {
|
||||
const match = html.match(/<script\b[^>]*application\/ld\+json[^>]*>([\s\S]*?)<\/script>/i);
|
||||
if (!match) throw new Error("未找到 JSON-LD 标签");
|
||||
if (match[1].trim() !== "") throw new Error("标签内仍有静态 / 拼接内容,可能被解析为 HTML");
|
||||
});
|
||||
|
||||
check("脚本只用 textContent 写入,不含 innerHTML / document.write / eval", () => {
|
||||
if (!code.includes(".textContent = JSON.stringify")) throw new Error("未使用 textContent 安全赋值");
|
||||
if (/innerHTML|document\.write|\beval\s*\(/.test(code)) throw new Error("存在不安全写入方式");
|
||||
});
|
||||
|
||||
check("标题含脚本闭合标签时只作为 JSON 字符串保留,不产生动态执行", () => {
|
||||
const injected = "</" + "script><img src=x onerror=alert(1)>";
|
||||
const result = render({ ...base, 'meta[property="og:title"]': injected });
|
||||
const data = JSON.parse(result.raw);
|
||||
if (data.headline !== injected || typeof data.headline !== "string") {
|
||||
throw new Error("标题被篡改或不是纯字符串");
|
||||
}
|
||||
if (result.executed) throw new Error("发生动态执行");
|
||||
});
|
||||
|
||||
check("缺失可选字段时不产生 undefined / 非法 JSON", () => {
|
||||
const sparse = { ...base };
|
||||
delete sparse['meta[property="og:image"]'];
|
||||
delete sparse['meta[property="og:description"]'];
|
||||
const data = JSON.parse(render(sparse).raw);
|
||||
if ("image" in data || "description" in data) throw new Error("空字段未剔除");
|
||||
});
|
||||
|
||||
check("全部 meta 缺失时仍输出合法 JSON", () => {
|
||||
const data = JSON.parse(render({}).raw);
|
||||
if (data["@context"] !== "https://schema.org" || data.author.name !== "") {
|
||||
throw new Error("兜底失败");
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`[jsonld-smoke] 内容渲染安全 ${pass} 项通过 / ${fail} 项失败`);
|
||||
process.exit(fail ? 1 : 0);
|
||||
Reference in new issue
Block a user