From 0c6aa63885fc7ddf138d394ff66a405fca2bf9ab Mon Sep 17 00:00:00 2001 From: Clayton Date: Mon, 20 Jul 2026 08:01:59 -0500 Subject: [PATCH] fix(scripts): extend forbidden-content.mjs with the repo's other known-precise secret formats FORBIDDEN_CONTENT (the packaged-secret detector used by check-miner-package.mjs, check-mcp-package.mjs, and miner-mcp-contract.test.ts) only matched 4 shapes, even though this repo already ships a materially larger, individually-verified-precise set of concrete secret-format patterns in src/review/secret-patterns.ts's SECRET_PATTERNS. Hand-copy the 12 missing HARD_SECRET_KINDS patterns' exact regex bodies in (plain node can't resolve secret-patterns.ts's .ts import, unlike check-engine-parity.ts which runs via tsx) rather than import them. jwt, seed_or_mnemonic, and bittensor_key are deliberately left out -- jwt is out of scope for this issue, and the other two are documented upstream as weak, false-positive-prone heuristics. Closes #7433 --- scripts/forbidden-content.mjs | 14 ++++++++- test/unit/forbidden-content.test.ts | 43 ++++++++++++++++++++++++++++ test/unit/miner-mcp-contract.test.ts | 5 ++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/scripts/forbidden-content.mjs b/scripts/forbidden-content.mjs index 039a6d2d97..e81ed9f9cf 100644 --- a/scripts/forbidden-content.mjs +++ b/scripts/forbidden-content.mjs @@ -4,5 +4,17 @@ // the AMS MCP contract test (test/unit/miner-mcp-contract.test.ts) reuses the SAME pattern to assert no MCP tool // response ever leaks one — importing it here rather than hand-duplicating the regex keeps the two byte-for-byte in // sync instead of relying on manual vigilance. +// +// #7433: extended with 12 additional concrete secret formats (aws_access_key, slack_token, google_api_key, +// gitlab_token, npm_token, stripe_secret_key, sendgrid_key, huggingface_token, voyage_api_key, firecrawl_api_key, +// openai_api_key, anthropic_api_key), hand-copied byte-for-byte from src/review/secret-patterns.ts's +// SECRET_PATTERNS rather than imported: check-miner-package.mjs/check-mcp-package.mjs both run via plain +// `node scripts/*.mjs` (package.json's test:miner-pack/test:mcp-pack), and plain Node cannot resolve a `.ts` +// import ("Unknown file extension \".ts\"" against this repo's Node 22 runtime) without a TS loader neither +// script registers -- unlike scripts/check-engine-parity.ts, which runs via `tsx`. jwt, seed_or_mnemonic, and +// bittensor_key were deliberately left out: jwt is out of scope for this issue, and seed_or_mnemonic/ +// bittensor_key are documented in secret-patterns.ts as weak, false-positive-prone heuristics (a +// `coldkey:`/`hotkey =` line or the word "mnemonic" in ordinary Bittensor docs is not a leaked credential) +// excluded from HARD_SECRET_KINDS there for the same reason. export const FORBIDDEN_CONTENT = - /(BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|github_pat_[A-Za-z0-9_]+|gh[pousr]_[A-Za-z0-9_]+|gts_[0-9a-f]{64}|[A-Z0-9_]*(TOKEN|SECRET|PRIVATE_KEY)=)/; + /(BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|github_pat_[A-Za-z0-9_]+|gh[pousr]_[A-Za-z0-9_]+|gts_[0-9a-f]{64}|[A-Z0-9_]*(TOKEN|SECRET|PRIVATE_KEY)=|\bAKIA[0-9A-Z]{16}\b|\bxox[baprs]-[A-Za-z0-9-]{10,}\b|\bAIza[0-9A-Za-z_-]{35}\b|\bglpat-[0-9A-Za-z_-]{20}(?![0-9A-Za-z_-])|\bnpm_[A-Za-z0-9]{36}\b|\b(?:sk|rk)_live_[0-9A-Za-z]{24,}\b|\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])|\bhf_[A-Za-z0-9]{34}\b|\b(?:pa|al)-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])|\bfc-[A-Za-z0-9]{16,}(?![A-Za-z0-9_-])|\bsk-(?:proj-|svcacct-|admin-)?[A-Za-z0-9_-]{20,}T3BlbkFJ[A-Za-z0-9_-]{20,}\b|\bsk-ant-api03-[A-Za-z0-9_-]{93}AA\b)/; diff --git a/test/unit/forbidden-content.test.ts b/test/unit/forbidden-content.test.ts index 853de15947..06f1644e31 100644 --- a/test/unit/forbidden-content.test.ts +++ b/test/unit/forbidden-content.test.ts @@ -81,3 +81,46 @@ describe("FORBIDDEN_CONTENT is the single source of truth (#6290)", () => { expect(FORBIDDEN_CONTENT.test(SECRET_SHAPED_PROBE)).toBe(true); }); }); + +// #7433: FORBIDDEN_CONTENT only matched 4 shapes (private-key block, github_pat_, gh[pousr]_, gts_, and a +// generic TOKEN/SECRET/PRIVATE_KEY= assignment) even though this same repo already ships a materially larger, +// individually-verified-precise set of concrete secret-format patterns in src/review/secret-patterns.ts's +// SECRET_PATTERNS. Each fixture below is assembled from fragments (never a contiguous credential-shaped literal +// in this file's own source) and uses the same fake bodies the repo's own secrets-scan.test.ts and +// content-lane-security-scan.test.ts already use for these exact formats. +describe("FORBIDDEN_CONTENT matches the repo's other known-precise secret formats (#7433)", () => { + it.each([ + ["aws_access_key", "AKIA" + "ABCDEFGHIJKLMNOP"], + ["slack_token", "xoxb-" + "123456789012-ABCDEFabcdef"], + ["google_api_key", "AIza" + "SyABCDEFGHIJKLMNOPQRSTUVWXYZ0123456"], + ["gitlab_token", "glpat-" + "aBcDeFgHiJkLmNoPqRsT"], + ["npm_token", "npm_" + "a".repeat(36)], + ["stripe_secret_key", "sk_live_" + "a".repeat(24)], + ["sendgrid_key", "SG." + "a".repeat(22) + "." + "b".repeat(43)], + ["huggingface_token", "hf_" + "a".repeat(34)], + ["voyage_api_key", "pa-" + "aK9xQ2mZw7Ln4Rv8Pt3B"], + ["firecrawl_api_key", "fc-" + "aK9xQ2mZw7Ln4Rv8"], + ["openai_api_key", "sk-" + "a".repeat(20) + "T3BlbkFJ" + "b".repeat(20)], + ["anthropic_api_key", "sk-ant-api03-" + "a".repeat(93) + "AA"], + ])("matches a %s-shaped value", (_kind, fixture) => { + expect(FORBIDDEN_CONTENT.test(fixture)).toBe(true); + }); + + it("still matches the 4 pre-existing formats unchanged", () => { + expect(FORBIDDEN_CONTENT.test("-----BEGIN RSA PRIVATE KEY-----")).toBe(true); + expect(FORBIDDEN_CONTENT.test("github_pat_" + "a".repeat(20))).toBe(true); + expect(FORBIDDEN_CONTENT.test("ghp_" + "a".repeat(20))).toBe(true); + expect(FORBIDDEN_CONTENT.test("gts_" + "0123456789abcdef".repeat(4))).toBe(true); + expect(FORBIDDEN_CONTENT.test(SECRET_SHAPED_PROBE)).toBe(true); + }); + + // jwt/seed_or_mnemonic/bittensor_key were deliberately left out of this widening (see the code comment on + // FORBIDDEN_CONTENT) -- pinned here so a future edit can't silently reintroduce them without this test + // flagging the scope change. + it("does NOT match jwt/seed_or_mnemonic/bittensor_key shapes (deliberately out of scope)", () => { + const jwtShaped = "eyJ" + "a".repeat(10) + "." + "b".repeat(10) + "." + "c".repeat(10); + expect(FORBIDDEN_CONTENT.test(jwtShaped)).toBe(false); + expect(FORBIDDEN_CONTENT.test("our seed phrase backup process")).toBe(false); + expect(FORBIDDEN_CONTENT.test("hotkey = ss58addresshere")).toBe(false); + }); +}); diff --git a/test/unit/miner-mcp-contract.test.ts b/test/unit/miner-mcp-contract.test.ts index 9853e2da5b..ec820236f8 100644 --- a/test/unit/miner-mcp-contract.test.ts +++ b/test/unit/miner-mcp-contract.test.ts @@ -294,6 +294,11 @@ describe("contract assertions catch violations (canary)", () => { expect(() => assertNoSecretShapedValue(withText(JSON.stringify({ token: PLANTED_SECRET })))).toThrow(); }); + it("assertNoSecretShapedValue also throws on a newly-widened shape (#7433: aws_access_key)", () => { + const awsShapedKey = "AKIA" + "ABCDEFGHIJKLMNOP"; + expect(() => assertNoSecretShapedValue(withText(JSON.stringify({ token: awsShapedKey })))).toThrow(); + }); + it("assertNoExcludedColumn throws when an excluded column is present", () => { expect(() => assertNoExcludedColumn(withText(JSON.stringify({ payload_json: "x" })), ["payload_json"])).toThrow(); });