返回 DeepSeek-Reasonix
credential-leak-check.mjs
根目录 / scripts / credential-leak-check.mjs
1 #!/usr/bin/env node
2 // Fails when a test prints a credential that is not one of its own fixtures.
3 //
4 // A test suite may print sk-legacy-123 because it wrote it a line earlier. It
5 // may not print a value that exists only in the developer's real credential
6 // store — that means the suite reached past its sandbox. The difference between
7 // the two is whether the value appears anywhere in the test sources.
8 //
9 // node scripts/credential-leak-check.mjs [./package/...]
10 //
11 // Exit 0 clean, 1 leaked. Values are never printed; a short digest identifies
12 // them across runs so a fix can be verified against the same finding.
13
14 import { execFileSync } from "node:child_process";
15 import { readFileSync, readdirSync, statSync } from "node:fs";
16 import { join } from "node:path";
17 import { createHash } from "node:crypto";
18
19 const PKG = process.argv[2] ?? "./internal/config/";
20
21 // Anything shaped like a provider token. Deliberately loose: a false positive
22 // costs one look at the fixtures, a false negative costs a leaked key.
23 const SECRET = /\b(?:sk|pk|api|key|token)[-_][A-Za-z0-9_-]{6,}\b/gi;
24
25 const digest = (s) => createHash("sha256").update(s).digest("hex").slice(0, 8);
26 const redact = (s) => `${s.slice(0, 3)}…${s.length} chars, sha256:${digest(s)}`;
27
28 function collectSources(dir, out = []) {
29 for (const name of readdirSync(dir)) {
30 const p = join(dir, name);
31 if (statSync(p).isDirectory()) {
32 if (name !== "node_modules" && name !== ".git") collectSources(p, out);
33 } else if (/\.(go|ts|tsx|js|mjs|json|toml|ya?ml)$/.test(name)) {
34 out.push(p);
35 }
36 }
37 return out;
38 }
39
40 // Everything written down in the repo is a fixture by definition — it cannot
41 // have come from the machine running the tests.
42 const fixtures = new Set();
43 for (const file of collectSources(".")) {
44 let text;
45 try {
46 text = readFileSync(file, "utf8");
47 } catch {
48 continue;
49 }
50 for (const m of text.matchAll(SECRET)) fixtures.add(m[0]);
51 }
52
53 let output = "";
54 try {
55 output = execFileSync("go", ["test", PKG, "-count=1"], {
56 encoding: "utf8",
57 maxBuffer: 64 * 1024 * 1024,
58 stdio: ["ignore", "pipe", "pipe"],
59 });
60 } catch (err) {
61 output = `${err.stdout ?? ""}${err.stderr ?? ""}`;
62 }
63
64 const leaked = new Map();
65 for (const line of output.split("\n")) {
66 for (const m of line.matchAll(SECRET)) {
67 if (fixtures.has(m[0])) continue;
68 if (!leaked.has(m[0])) leaked.set(m[0], line.trim().slice(0, 120));
69 }
70 }
71
72 console.log(`package: ${PKG}`);
73 console.log(`fixtures: ${fixtures.size} credential-shaped literals found in repo sources`);
74
75 if (leaked.size === 0) {
76 console.log("\nCLEAN — every credential printed by the suite is one of its own fixtures.");
77 process.exit(0);
78 }
79
80 console.log(`\nLEAKED — ${leaked.size} value(s) printed that exist nowhere in the sources:\n`);
81 for (const [value, context] of leaked) {
82 console.log(` ${redact(value)}`);
83 console.log(` context: ${context.replace(value, "<redacted>")}\n`);
84 }
85 console.log("A value the suite did not write can only have come from the machine's");
86 console.log("real credential store, which means the test escaped its sandbox.");
87 process.exit(1);
88
88 lines Plain Text