From d9abdbc71d889592e5d47ac17bd623c6e4d8f183 Mon Sep 17 00:00:00 2001 From: RealDiligent Date: Sun, 26 Jul 2026 09:01:18 +0800 Subject: [PATCH] fix(scripts): drift-check forbidden-content.ts's hand-copied secret patterns against secret-patterns.ts scripts/forbidden-content.ts's FORBIDDEN_CONTENT is a THIRD hand-copy (#7433) of secret-patterns.ts's HARD_SECRET_KINDS regex bodies -- consumed by the four package-manifest checkers (check-mcp/miner/engine/ ui-kit-package.ts) to reject a packed tarball embedding a provider secret. Unlike the REES copy (already guarded by SECRET_DETECTION_TWIN_PAIR), it was never registered in check-engine-parity.ts's NAMED_TWIN_PAIRS, so a tightened or added HARD_SECRET_KINDS pattern would silently leave packaged tarballs scanning with a stale body, with no CI signal. Register scripts/forbidden-content.ts as a new named twin pair against src/review/secret-patterns.ts, with a marker set covering the distinctive backslash-free core of each HARD_SECRET_KINDS regex body forbidden-content hand-copied exactly (13 kinds, aws through jwt). github_token/github_pat/private_key_block are deliberately excluded -- forbidden-content keeps its own looser pre-#7433 bodies for those three, a pre-existing intentional divergence that would false-fail, exactly as SECRET_DETECTION_MARKERS excludes its own naming-divergent kinds. Tests: a drift-fail case proving a dropped shared body fails presence, plus assertions of the pair's identity and the three deliberate exclusions. The existing 'all named pairs pass against the real repo' regression test now also covers this pair (all 13 markers verified present in both live files -- no false positive). --- scripts/check-engine-parity.ts | 40 ++++++++++++++++++ test/unit/check-engine-parity-script.test.ts | 44 +++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/scripts/check-engine-parity.ts b/scripts/check-engine-parity.ts index ab5bc3500f..7e8e7d529d 100644 --- a/scripts/check-engine-parity.ts +++ b/scripts/check-engine-parity.ts @@ -147,6 +147,45 @@ export const SECRET_DETECTION_MARKERS = Object.freeze([ '"generic_secret_assignment"', ] as const); +/** `scripts/forbidden-content.ts`'s FORBIDDEN_CONTENT is a THIRD hand-copy (#7433) of the HARD_SECRET_KINDS + * regex bodies in `src/review/secret-patterns.ts` — the four package-manifest checkers (check-mcp/miner/ + * engine/ui-kit-package.ts) use it to reject a packed tarball embedding a provider secret. Unlike the REES + * pair it was never drift-checked, so a tightened or added HARD_SECRET_KINDS pattern would silently leave + * packaged MCP/miner/engine/ui-kit scanning with a stale body, with no CI signal (#8674). Registered here so + * a divergence in any of the shared regex bodies fails CI. */ +export const FORBIDDEN_CONTENT_TWIN_PAIR: NamedTwinPair = Object.freeze({ + area: "forbidden-content-secret-patterns", + hostRelative: "src/review/secret-patterns.ts", + engineRelative: "scripts/forbidden-content.ts", + hostFileName: "secret-patterns.ts", + engineFileName: "forbidden-content.ts", +}); + +// The distinctive core of each HARD_SECRET_KINDS regex body that forbidden-content.ts hand-copied EXACTLY +// (#7433/#8396). Anchored on the backslash-free inner body, NOT the `\b`/`\.` escapes (which necessarily differ +// between secret-patterns.ts's regex literals — single `\` — and forbidden-content.ts's string bodies — double +// `\\`), so a merely-reformatted boundary never false-fails while a change to the actual character class / +// length / watermark of any shared pattern does. `github_token`/`github_pat`/`private_key_block` are +// DELIBERATELY EXCLUDED: forbidden-content.ts keeps its own looser pre-#7433 bodies for those three (e.g. +// `gh[pousr]_[A-Za-z0-9_]+` vs secret-patterns.ts's `gh[pousr]_[A-Za-z0-9]{20,}`), a pre-existing intentional +// divergence — including them would false-fail this check on the very PR that introduces it, exactly as +// SECRET_DETECTION_MARKERS excludes its own two naming-divergent kinds. +export const FORBIDDEN_CONTENT_MARKERS = Object.freeze([ + "AKIA[0-9A-Z]{16}", // aws_access_key + "xox[baprs]-[A-Za-z0-9-]{10,}", // slack_token + "AIza[0-9A-Za-z_-]{35}", // google_api_key + "glpat-[0-9A-Za-z_-]{20}(?![0-9A-Za-z_-])", // gitlab_token + "npm_[A-Za-z0-9]{36}", // npm_token + "(?:sk|rk)_live_[0-9A-Za-z]{24,}", // stripe_secret_key + "[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])", // sendgrid_key (the distinctive 43-char secret half) + "hf_[A-Za-z0-9]{34}", // huggingface_token + "(?:pa|al)-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])", // voyage_api_key + "fc-[A-Za-z0-9]{16,}(?![A-Za-z0-9_-])", // firecrawl_api_key + "sk-(?:proj-|svcacct-|admin-)?[A-Za-z0-9_-]{20,}T3BlbkFJ[A-Za-z0-9_-]{20,}", // openai_api_key + "sk-ant-api03-[A-Za-z0-9_-]{93}AA", // anthropic_api_key + "eyJ[A-Za-z0-9_-]{10,}", // jwt (#8396) +] as const); + /** Every explicitly named twin pair, checked for core-marker presence in `runEngineParityChecks` — the * same escape hatch #4518 built for `GATE_DECISION_TWIN_PAIR`, generalized (#4605) so a function-level or * nested-directory duplicate can be added here without inventing a new mechanism. `GATE_DECISION_TWIN_PAIR` @@ -163,6 +202,7 @@ export const NAMED_TWIN_PAIRS: ReadonlyArray<{ pair: NamedTwinPair; markers: rea { pair: DIFF_FILE_PRIORITY_GROUNDING_TWIN_PAIR, markers: DIFF_FILE_PRIORITY_MARKERS }, { pair: SHARES_MEANINGFUL_FILE_TWIN_PAIR, markers: SHARES_MEANINGFUL_FILE_MARKERS }, { pair: SECRET_DETECTION_TWIN_PAIR, markers: SECRET_DETECTION_MARKERS }, + { pair: FORBIDDEN_CONTENT_TWIN_PAIR, markers: FORBIDDEN_CONTENT_MARKERS }, ]); const ENGINE_SRC_ROOT = "packages/loopover-engine/src"; const HOST_SRC_ROOT = "src"; diff --git a/test/unit/check-engine-parity-script.test.ts b/test/unit/check-engine-parity-script.test.ts index bdd206fa5e..f759edfe2c 100644 --- a/test/unit/check-engine-parity-script.test.ts +++ b/test/unit/check-engine-parity-script.test.ts @@ -21,6 +21,8 @@ import { discoverGateDecisionTwinPair, enginePackageVersionIncreased, extractForbiddenTermsRegex, + FORBIDDEN_CONTENT_MARKERS, + FORBIDDEN_CONTENT_TWIN_PAIR, GATE_DECISION_TWIN_PAIR, type EngineParityPair, isEngineStubPair, @@ -456,7 +458,7 @@ const CHECK_RUN_FORBIDDEN_TERMS = }); describe("named twin-pair coverage (#4605)", () => { - it("registers the gate-decision, content-lane, diff-file-priority twins, shares-meaningful-file, and secret-detection pairs", () => { + it("registers the gate-decision, content-lane, diff-file-priority, diff-file-priority-grounding, shares-meaningful-file, secret-detection, and forbidden-content pairs", () => { const areas = NAMED_TWIN_PAIRS.map(({ pair }) => pair.area); expect(areas).toEqual([ "gate-decision", @@ -465,6 +467,7 @@ const CHECK_RUN_FORBIDDEN_TERMS = "diff-file-priority-grounding", "shares-meaningful-file", "secret-detection", + "forbidden-content-secret-patterns", ]); }); @@ -635,6 +638,45 @@ const CHECK_RUN_FORBIDDEN_TERMS = }); }); + describe("forbidden-content twin coverage (#8674)", () => { + it("registers scripts/forbidden-content.ts against src/review/secret-patterns.ts", () => { + expect(FORBIDDEN_CONTENT_TWIN_PAIR.hostRelative).toBe("src/review/secret-patterns.ts"); + expect(FORBIDDEN_CONTENT_TWIN_PAIR.engineRelative).toBe("scripts/forbidden-content.ts"); + }); + + it("excludes the three kinds whose forbidden-content bodies deliberately diverge (github_token/github_pat/private_key_block)", () => { + // Those three keep looser pre-#7433 bodies in forbidden-content.ts (e.g. `gh[pousr]_[A-Za-z0-9_]+`); + // asserting their absence documents the deliberate omission and guards against someone "completing the + // set" and reintroducing a false-fail, exactly as the secret-detection pair does for its own divergences. + const joined = FORBIDDEN_CONTENT_MARKERS.join("\n"); + expect(joined).not.toContain("gh[pousr]_"); + expect(joined).not.toContain("github_pat_"); + expect(joined).not.toContain("PRIVATE KEY"); + }); + + it("fails presence when a shared HARD_SECRET_KINDS regex body drifts between the two copies", () => { + // Reproduces the drift class this pair exists to catch (#8674): a HARD_SECRET_KINDS body is tightened in + // secret-patterns.ts while forbidden-content.ts's hand-copy is left stale, silently packaging tarballs + // with an outdated scanner. Both bodies start from the FULL marker set so only the dropped line fails. + const droppedMarker = "sk-ant-api03-[A-Za-z0-9_-]{93}AA"; + const hostBody = FORBIDDEN_CONTENT_MARKERS.join("\n"); + const driftedTwinBody = FORBIDDEN_CONTENT_MARKERS.filter((marker) => marker !== droppedMarker).join("\n"); + const result = checkGateDecisionTwinPresence({ + root: "/fake", + readFile: (_root, relativePath) => { + if (relativePath === FORBIDDEN_CONTENT_TWIN_PAIR.hostRelative) return hostBody; + if (relativePath === FORBIDDEN_CONTENT_TWIN_PAIR.engineRelative) return driftedTwinBody; + throw new Error(`unexpected read: ${relativePath}`); + }, + pair: FORBIDDEN_CONTENT_TWIN_PAIR, + markers: FORBIDDEN_CONTENT_MARKERS, + }); + expect(result.failures).toHaveLength(1); + expect(result.failures[0]).toContain(FORBIDDEN_CONTENT_TWIN_PAIR.engineRelative); + expect(result.failures[0]).toContain(JSON.stringify(droppedMarker)); + }); + }); + describe("engine version skew", () => { it("classifies equal, behind, and ahead boundary cases", () => { expect(compareSemver("0.2.0", "0.2.0")).toBe(0);