diff --git a/scripts/forbidden-content.mjs b/scripts/forbidden-content.mjs index 039a6d2d97..bfef0136ef 100644 --- a/scripts/forbidden-content.mjs +++ b/scripts/forbidden-content.mjs @@ -1,8 +1,39 @@ // Single source of truth for the miner package's secret-shape detector. // -// scripts/check-miner-package.mjs uses this to reject any packed miner file that embeds a secret-like value, and -// 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. -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)=)/; +// scripts/check-miner-package.mjs + scripts/check-mcp-package.mjs use this to reject any packed miner/mcp file +// that embeds a secret-like value, and 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 every consumer byte-for-byte in sync instead of relying on manual vigilance. +// +// The concrete provider-key shapes below are hand-copied (#7433) from the exact regex bodies of the entries in +// src/review/secret-patterns.ts's SECRET_PATTERNS that are in its HARD_SECRET_KINDS set (the "near-zero +// false-positive" subset). They are NOT imported directly: this file is a plain `.mjs` run via `node` +// (test:miner-pack / test:mcp-pack), and secret-patterns.ts is TypeScript with no runtime `.js` sibling on this +// path — a runtime `import` of it from node would fail, so the exact bodies are copied per the issue's stated +// fallback. `jwt`, `seed_or_mnemonic`, and `bittensor_key` are deliberately NOT included: jwt is out of scope +// for #7433, and seed_or_mnemonic/bittensor_key are documented in secret-patterns.ts as weak, false-positive- +// prone heuristics intentionally excluded from HARD_SECRET_KINDS (an ordinary `coldkey:` / `hotkey =` line or +// the word "mnemonic" in Bittensor docs is not a leaked credential). +export const FORBIDDEN_CONTENT = new RegExp( + [ + // Already covered before #7433 (unchanged shapes): + "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)=", + // Added #7433 — exact bodies from secret-patterns.ts HARD_SECRET_KINDS entries: + "\\bAKIA[0-9A-Z]{16}\\b", // aws_access_key + "\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b", // slack_token + "\\bAIza[0-9A-Za-z_-]{35}\\b", // google_api_key + "\\bglpat-[0-9A-Za-z_-]{20}(?![0-9A-Za-z_-])", // gitlab_token + "\\bnpm_[A-Za-z0-9]{36}\\b", // npm_token + "\\b(?:sk|rk)_live_[0-9A-Za-z]{24,}\\b", // stripe_secret_key + "\\bSG\\.[A-Za-z0-9_-]{22}\\.[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])", // sendgrid_key + "\\bhf_[A-Za-z0-9]{34}\\b", // huggingface_token + "\\b(?:pa|al)-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])", // voyage_api_key + "\\bfc-[A-Za-z0-9]{16,}(?![A-Za-z0-9_-])", // firecrawl_api_key + "\\bsk-(?:proj-|svcacct-|admin-)?[A-Za-z0-9_-]{20,}T3BlbkFJ[A-Za-z0-9_-]{20,}\\b", // openai_api_key + "\\bsk-ant-api03-[A-Za-z0-9_-]{93}AA\\b", // anthropic_api_key + ].join("|"), +); diff --git a/test/unit/forbidden-content.test.ts b/test/unit/forbidden-content.test.ts index 853de15947..d518b8c896 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); }); }); + +describe("FORBIDDEN_CONTENT covers the concrete provider-key formats (#7433)", () => { + // One representative fixture per newly-added HARD_SECRET_KINDS format, each assembled from fragments so this + // file never contains a contiguous credential-shaped literal (same convention as secret-patterns.test.ts). + const A = (n: number) => "A".repeat(n); + const a = (n: number) => "a".repeat(n); + const NEW_FORMAT_PROBES: Array<[string, string]> = [ + ["aws_access_key", "AKIA" + "IOSFODNN7EXAMPLE"], + ["slack_token", "xox" + "b-" + a(12)], + ["google_api_key", "AIza" + a(35)], + ["gitlab_token", "glpat-" + a(20)], + ["npm_token", "npm_" + a(36)], + ["stripe_secret_key", "sk" + "_live_" + a(24)], + ["sendgrid_key", "SG." + a(22) + "." + a(43)], + ["huggingface_token", "hf_" + a(34)], + ["voyage_api_key", "pa" + "-" + a(20)], + ["firecrawl_api_key", "fc" + "-" + a(16)], + ["openai_api_key", "sk-" + a(20) + "T3Blbk" + "FJ" + a(20)], + ["anthropic_api_key", "sk-ant-" + "api03-" + a(93) + "AA"], + ]; + + it.each(NEW_FORMAT_PROBES)("matches a %s-shaped value", (_name, probe) => { + expect(FORBIDDEN_CONTENT.test(probe)).toBe(true); + }); + + it("still matches the pre-existing shapes (private-key block, github_pat, gh*, gts, generic assignment)", () => { + expect(FORBIDDEN_CONTENT.test("BEGIN RSA PRIVATE KEY")).toBe(true); + expect(FORBIDDEN_CONTENT.test("github_pat_" + a(20))).toBe(true); + expect(FORBIDDEN_CONTENT.test("ghp_" + a(30))).toBe(true); + expect(FORBIDDEN_CONTENT.test("gts_" + "0".repeat(64))).toBe(true); + expect(FORBIDDEN_CONTENT.test("MY" + "_TOKEN=" + "x")).toBe(true); + }); + + it("does NOT hard-block the deliberately-excluded weak heuristics (jwt / seed / bittensor key shapes)", () => { + // These are intentionally kept out of the packaged-secret hard block (#7433) — an ordinary Bittensor + // coldkey/hotkey mention or a mnemonic word is not a leaked credential; a bare JWT is out of scope. + expect(FORBIDDEN_CONTENT.test("coldkey: my-wallet-name")).toBe(false); + expect(FORBIDDEN_CONTENT.test("the recovery mnemonic is stored offline")).toBe(false); + // A bare header-dot-payload JWT shape is not matched by the hard-block detector. + expect(FORBIDDEN_CONTENT.test("eyJ" + A(20) + "." + a(20) + "." + a(20))).toBe(false); + expect(FORBIDDEN_CONTENT.global).toBe(false); + }); +});