Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

push-policy-secret-scan.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

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