test(游戏共创): 一致性门禁把内容嗅探与 magic bytes 纳入比对(补最后一处规则空白)
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
- `scripts/check-project-bundle-policy-parity.mjs` 新增第 4 维:**凭据内容嗅探与嵌套包 magic bytes**。复核指出这两套「换名字也拦得住」的规则只靠常量表表达,过去门禁对 `sniff|PEM|AKIA|magic|secret` 零命中(只有看得见的规则才有守卫),本次补齐: · `SECRET_CONTENT_TEXT_EXTENSIONS` / `SECRET_CONTENT_SIGNATURES`(字符串数组集合相等)、`SECRET_CONTENT_MAX_SNIFF_BYTES` / `NESTED_ARCHIVE_SNIFF_BYTES`(整数相等)、`SECRET_AWS_ACCESS_KEY_PREFIX` / `PEM_PRIVATE_KEY_LINE_PREFIX` / `PEM_PRIVATE_KEY_MARKER`(字符串逐字相等)——新增 `rustStringConst` helper 抓 `&str` 常量 · `nested_archive_format` 里的 `b"..."` magic 字面量按「服务端 ⊆ 客户端」比对(客户端可以先拦、不能漏拦),并用新的 `byteStringLiterals` helper 抽取;两侧空集合一律 throw - **「故意改一侧会红」已验证(两处,均已原样恢复)**:① 从客户端 `BUNDLE_FORBIDDEN_PATH_CHARS` 去掉 `'?'`/`'*'` → 报两条「路径段禁止字符…客户端没有」;② 从客户端 `SECRET_CONTENT_SIGNATURES` 去掉 `ghp_` → 报「嗅探规则不一致:服务端 …=ghp_|github_pat_|…,客户端 …=github_pat_|…」 - 文档 §3.5.2 与 §5.2 同步门禁的四个覆盖维度(目录/前缀/后缀/全名 + 上限、路径形状、内容嗅探特征表、magic bytes),并把 §5.2 的门禁行从「P1-a」扩为「P1-a + 嗅探维度」 - 门禁:`check-project-bundle-policy-parity` 0(服务端 51 条规则全被客户端覆盖;嗅探:`ghp_/github_pat_/xoxa-/xoxb-/xoxp-` + `AKIA` + PEM 标记;magic:7z / PK / Rar! / gzip / ustar;窗口 512 字节两侧一致);`check:doc-index` 248 份 OK;`check:encoding` 5305 files OK;`git diff --check` 0
This commit is contained in:
@@ -115,6 +115,22 @@ function matchesMacroChars(source) {
|
||||
return charLiteralsMatching(source, /matches!\([^,]*,\s*[^)]*\)/gu);
|
||||
}
|
||||
|
||||
/// 抓取 `const NAME: &str = "...";` 的字面量(两侧必须完全一致的那类常量)。
|
||||
function rustStringConst(source, name, file) {
|
||||
const match = new RegExp(`const\\s+${name}\\s*:\\s*&str\\s*=\\s*"([^"]*)"\\s*;`).exec(
|
||||
source,
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(`${file} 里找不到字符串常量 ${name}`);
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
/// 抓取某段代码里所有 `b"..."` 字节串字面量的**源码文本**(magic bytes 比对用)。
|
||||
function byteStringLiterals(source) {
|
||||
return unique([...source.matchAll(/b"((?:\\.|[^"\\])*)"/gu)].map((entry) => entry[1]));
|
||||
}
|
||||
|
||||
function unique(values) {
|
||||
return [...new Set(values)].sort();
|
||||
}
|
||||
@@ -288,6 +304,55 @@ console.log(
|
||||
`结尾字符 ${serverPathSuffixChars.join(' ')};禁止字符 ${serverPathForbiddenChars.join(' ')}`,
|
||||
);
|
||||
|
||||
// 4. 凭据内容嗅探与嵌套包 magic bytes:这两套规则集也必须两端一致。
|
||||
//
|
||||
// 为什么单独一维(复核 §12 的最后一处空白):内容嗅探与 magic 嗅探是「换名字也拦得住」的那一层,
|
||||
// 但它们只靠常量表表达;常量表一旦只改一侧,门禁过去完全看不见。这里把六个常量做成
|
||||
// **必须逐字相等**的对,并把 `nested_archive_format` 里的 `b"..."` magic 字面量做成
|
||||
// 「服务端 ⊆ 客户端」(客户端可以先拦,绝不能漏拦),空集合一律报错。
|
||||
const SECRET_EQUAL_PAIRS = [
|
||||
// [服务端常量, 客户端常量, 抓取方式];抓取方式:array=字符串数组集合相等,int=整数相等,str=字符串相等
|
||||
['SECRET_CONTENT_TEXT_EXTENSIONS', 'SECRET_CONTENT_TEXT_EXTENSIONS', 'array'],
|
||||
['SECRET_CONTENT_MAX_SNIFF_BYTES', 'SECRET_CONTENT_MAX_SNIFF_BYTES', 'int'],
|
||||
['SECRET_CONTENT_SIGNATURES', 'SECRET_CONTENT_SIGNATURES', 'array'],
|
||||
['SECRET_AWS_ACCESS_KEY_PREFIX', 'SECRET_AWS_ACCESS_KEY_PREFIX', 'str'],
|
||||
['PEM_PRIVATE_KEY_LINE_PREFIX', 'PEM_PRIVATE_KEY_LINE_PREFIX', 'str'],
|
||||
['PEM_PRIVATE_KEY_MARKER', 'PEM_PRIVATE_KEY_MARKER', 'str'],
|
||||
['NESTED_ARCHIVE_SNIFF_BYTES', 'NESTED_ARCHIVE_SNIFF_BYTES', 'int'],
|
||||
];
|
||||
|
||||
for (const [serverName, clientName, kind] of SECRET_EQUAL_PAIRS) {
|
||||
let serverValue;
|
||||
let clientValue;
|
||||
if (kind === 'array') {
|
||||
serverValue = rustStringArray(server, serverName, SERVER_FILE).sort().join('|');
|
||||
clientValue = rustStringArray(client, clientName, CLIENT_FILE).sort().join('|');
|
||||
} else if (kind === 'int') {
|
||||
serverValue = rustConstant(server, serverName, SERVER_FILE);
|
||||
clientValue = rustConstant(client, clientName, CLIENT_FILE);
|
||||
} else {
|
||||
serverValue = rustStringConst(server, serverName, SERVER_FILE);
|
||||
clientValue = rustStringConst(client, clientName, CLIENT_FILE);
|
||||
}
|
||||
if (serverValue !== clientValue) {
|
||||
failures.push(
|
||||
`嗅探规则不一致:服务端 ${serverName}=${serverValue},客户端 ${clientName}=${clientValue}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const serverMagicBytes = byteStringLiterals(functionBody(server, 'nested_archive_format'));
|
||||
const clientMagicBytes = byteStringLiterals(functionBody(client, 'nested_archive_format'));
|
||||
requireCovered('嵌套包 magic bytes', serverMagicBytes, clientMagicBytes, failures);
|
||||
if (clientMagicBytes.length === 0) {
|
||||
throw new Error('嵌套包 magic bytes:客户端 token 抽取为空,比对不可信');
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[check:project-bundle-policy-parity] 嗅探:内容特征 ${rustStringArray(server, 'SECRET_CONTENT_SIGNATURES', SERVER_FILE).join('/')} + ${rustStringConst(server, 'SECRET_AWS_ACCESS_KEY_PREFIX', SERVER_FILE)}… + PEM 标记;` +
|
||||
`magic bytes ${serverMagicBytes.join(' ')};嗅探窗口 ${rustConstant(server, 'NESTED_ARCHIVE_SNIFF_BYTES', SERVER_FILE)} 字节`,
|
||||
);
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error('[check:project-bundle-policy-parity] 不一致:');
|
||||
for (const failure of failures) {
|
||||
|
||||
Reference in New Issue
Block a user