CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | /**
* Tests for the unconditional pre-receive secret scan added to
* src/lib/push-policy.ts (2026-07-16). Previously a critical secret
* (AWS/GitHub/Anthropic/Stripe key, PEM private key) pushed directly to a
* repo was only caught after the fact by the async post-receive scan in
* security-scan.ts — the secret was already in the object store by then.
* This scan runs inside the same pre-receive hook that already enforces
* ruleset pack-content rules, and rejects the push before objects are
* promoted.
*
* The hook's eval.js is a standalone, no-project-imports script (see
* buildEvalScript()'s doc comment) that runs via a real `bun run` subprocess
* inside a temp hooksPath dir — these tests exercise the actual generated
* script exactly as production does, rather than re-implementing its logic.
*/
import { describe, expect, it } from "bun:test";
import { writeFileSync } from "fs";
import { join } from "path";
import {
hookSecretPatternTypes,
installPackInspectionHook,
} from "../lib/push-policy";
import { SECRET_PATTERNS } from "../lib/security-scan";
describe("hook secret patterns stay in sync with security-scan.ts", () => {
it("covers every critical-severity pattern in SECRET_PATTERNS, and nothing else", () => {
const criticalTypes = SECRET_PATTERNS.filter((p) => p.severity === "critical").map((p) => p.type).sort();
const hookTypes = hookSecretPatternTypes().sort();
expect(hookTypes).toEqual(criticalTypes);
});
});
describe("pre-receive secret scan (real eval.js subprocess)", () => {
async function runEval(contentLines: string[]): Promise<{ active: string[]; stderr: string }> {
const hook = await installPackInspectionHook([], { secretScan: true });
expect(hook).not.toBeNull();
if (!hook) throw new Error("unreachable");
const dir = hook.env.GIT_CONFIG_VALUE_0;
try {
const commitsPath = join(dir, "commits.txt");
const sizesPath = join(dir, "sizes.txt");
const contentsPath = join(dir, "contents.txt");
writeFileSync(commitsPath, "");
writeFileSync(sizesPath, "");
writeFileSync(
contentsPath,
contentLines
.map((l) => l) // already "<path>\t<base64>" per line
.join("\n") + (contentLines.length ? "\n" : "")
);
const evalScriptPath = join(dir, "eval.js");
const rulesJsonPath = join(dir, "rules.json");
const proc = Bun.spawnSync([
"bun",
"run",
evalScriptPath,
"--",
rulesJsonPath,
commitsPath,
sizesPath,
contentsPath,
]);
const stdout = new TextDecoder().decode(proc.stdout).trim();
const stderr = new TextDecoder().decode(proc.stderr);
const active = stdout
.split("\n")
.filter(Boolean)
.filter((line) => line.startsWith("active\t"))
.map((line) => line.slice("active\t".length));
return { active, stderr };
} finally {
await hook.cleanup();
}
}
function fileLine(path: string, content: string): string {
return `${path}\t${Buffer.from(content, "utf8").toString("base64")}`;
}
it("blocks a critical secret (AWS access key) in a normal source file", async () => {
const { active, stderr } = await runEval([
fileLine("src/config.ts", "const AWS_KEY = 'AKIAABCDEFGH1234IJKL';\n"),
]);
expect(stderr).toBe("");
expect(active.length).toBe(1);
expect(active[0]).toContain("AWS Access Key");
expect(active[0]).toContain("src/config.ts:1");
});
it("does not leak the secret value itself into the rejection message", async () => {
const { active } = await runEval([
fileLine("src/config.ts", "const AWS_KEY = 'AKIAABCDEFGH1234IJKL';\n"),
]);
expect(active[0]).not.toContain("AKIAABCDEFGH1234IJKL");
});
it("does not block an obvious placeholder value", async () => {
const { active } = await runEval([
fileLine("src/config.ts", "const AWS_KEY = 'AKIAEXAMPLE1234EXAMP'; // example only\n"),
]);
expect(active.length).toBe(0);
});
it("does not scan skip-listed paths (build output, lockfiles)", async () => {
const { active } = await runEval([
fileLine("dist/bundle.js", "const AWS_KEY = 'AKIAABCDEFGH1234IJKL';\n"),
]);
expect(active.length).toBe(0);
});
it("does not block a clean file with no secret patterns", async () => {
const { active } = await runEval([
fileLine("README.md", "# Hello\n\nJust a normal readme with no secrets.\n"),
]);
expect(active.length).toBe(0);
});
it("scans multiple files and reports one violation per match", async () => {
const { active } = await runEval([
fileLine("a.ts", "AKIAABCDEFGH1234IJKL\n"),
fileLine("b.ts", "-----BEGIN RSA PRIVATE KEY-----\n"),
fileLine("c.ts", "nothing interesting here\n"),
]);
expect(active.length).toBe(2);
});
});
|