import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { spawnSync } from "node:child_process"; import { afterEach, describe, it, expect } from "vitest"; import { collectFiles, formatFinding, isKnownLocalCredential, scanText, isAllowlistedPath, lineHasPlaceholder, PATTERNS, } from "./verify-secrets.mjs"; const temporaryDirectories = []; afterEach(() => { for (const directory of temporaryDirectories.splice(0)) { fs.rmSync(directory, { recursive: true, force: true }); } }); describe("verify-secrets pattern detection", () => { it("detects a GitHub PAT in plain text", () => { const sample = `const token = "ghp_AbCdEfGhIjKlMnOpQrStUv0123456789";`; const findings = scanText(sample, { filename: "apps/web/secret.ts" }); expect(findings.length).toBeGreaterThanOrEqual(1); expect(findings.some((f) => f.patternId === "github-pat")).toBe(true); }); it("detects a GitHub fine-grained PAT", () => { const sample = `const t = "github_pat_11ABCDEFG0123456789abcdef";`; const findings = scanText(sample, { filename: "apps/api/foo.ts" }); expect(findings.some((f) => f.patternId === "github-fine-grained-pat")).toBe( true, ); }); it("detects a GitLab PAT", () => { const sample = `const t = "glpat-abcDEF1234567890_xyzQR";`; const findings = scanText(sample, { filename: "apps/web/x.ts" }); expect(findings.some((f) => f.patternId === "gitlab-pat")).toBe(true); }); it("detects common package, model-hosting, Google, and age credentials", () => { const samples = [ ["npm-access-token", "npm_abcdefghijklmnopqrstuvwxyz0123456789"], ["pypi-access-token", "pypi-abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGH"], ["huggingface-token", "hf_abcdefghijklmnopqrstuvwxyz0123456789"], ["google-api-key", "AIzaabcdefghijklmnopqrstuvwxyz123456789"], ["age-secret-key", "AGE-SECRET-KEY-1ABCDEFGHIJKLMNOPQRSTUVWXYZ"], ]; for (const [patternId, sample] of samples) { expect(scanText(String(sample), { filename: "src/config.ts" }).some((finding) => finding.patternId === patternId)).toBe(true); } }); it("detects unquoted secret assignments and credentials embedded in URLs", () => { const sample = [ "CLIENT_SECRET=abcdef0123456789abcdef0123456789", "DATABASE_URL=postgresql://service:super-secret-password@db.internal/app", ].join("\n"); const findings = scanText(sample, { filename: "infra/app.env" }); expect(findings.some((finding) => finding.patternId === "generic-high-entropy-assignment")).toBe(true); expect(findings.some((finding) => finding.patternId === "embedded-url-credentials")).toBe(true); }); it("does not classify long source identifiers as unquoted secret values", () => { const samples = [ "const token = existingPortalDomainVerificationTokenHash;", "token: E2E_CUSTOMER_01_SECRET_IDENTIFIER,", ]; for (const sample of samples) { expect(scanText(sample, { filename: "src/domain.ts" })).toHaveLength(0); } }); it("detects an AWS Access Key ID", () => { const sample = `AKIAIOSFODNN7EXAMPLE2`; const findings = scanText(sample, { filename: "apps/web/aws.ts" }); // Note: this sample contains "example" placeholder so it should be skipped. expect(findings.length).toBe(0); }); it("detects a real AWS Access Key ID on a non-placeholder line", () => { const sample = `const k = "AKIAZZZZZZZZZZZZZZZZ";`; const findings = scanText(sample, { filename: "apps/web/aws.ts" }); expect(findings.some((f) => f.patternId === "aws-access-key-id")).toBe(true); }); it("detects payment processor live secret keys but not test keys", () => { const live = `const k = "sk_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaa";`; const test = `const k = "sk_test_aaaaaaaaaaaaaaaaaaaaaaaaaaaa";`; const liveFindings = scanText(live, { filename: "apps/web/s.ts" }); const testFindings = scanText(test, { filename: "apps/web/s.ts" }); expect(liveFindings.some((f) => f.patternId === "payment-secret-live")).toBe( true, ); expect( testFindings.some((f) => f.patternId === "payment-secret-live"), ).toBe(false); }); it("detects payment processor webhook secrets only when the line contains 'live'", () => { const withLive = `const k = "whsec_aaaaaaaaaaaaaaaaaaaaaaaaaaaa"; // live prod webhook`; const withoutLive = `const k = "whsec_aaaaaaaaaaaaaaaaaaaaaaaaaaaa";`; const a = scanText(withLive, { filename: "apps/web/w.ts" }); const b = scanText(withoutLive, { filename: "apps/web/w.ts" }); expect(a.some((f) => f.patternId === "payment-webhook-live")).toBe(true); expect(b.some((f) => f.patternId === "payment-webhook-live")).toBe(false); }); it("detects Slack bot tokens", () => { const sample = `const t = "xoxb-1234567890-1234567890-1234567890-abcdef1234567890";`; const findings = scanText(sample, { filename: "apps/web/slack.ts" }); expect(findings.some((f) => f.patternId === "slack-bot-token")).toBe(true); }); it("detects a PEM private key header", () => { const sample = `-----BEGIN RSA PRIVATE KEY-----`; const findings = scanText(sample, { filename: "apps/web/key.pem" }); expect(findings.some((f) => f.patternId === "private-key-header")).toBe( true, ); }); it("detects a JWT-shaped string", () => { const sample = `const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";`; const findings = scanText(sample, { filename: "apps/web/jwt.ts" }); expect(findings.some((f) => f.patternId === "jwt-like")).toBe(true); }); it("detects generic high-entropy assignments to secret-named identifiers", () => { const sample = `const password = "abcdef0123456789abcdef0123456789ZZ";`; const findings = scanText(sample, { filename: "apps/web/pw.ts" }); expect( findings.some((f) => f.patternId === "generic-high-entropy-assignment"), ).toBe(true); }); }); describe("verify-secrets placeholder false-positive suppression", () => { it("ignores lines containing 'placeholder'", () => { const sample = `const token = "ghp_ZZZZZZZZZZZZZZZZZZZZ"; // placeholder only`; expect(scanText(sample, { filename: "x.ts" })).toHaveLength(0); }); it("ignores lines containing 'example'", () => { const sample = `# example: AKIAIOSFODNN7EXAMPL1`; expect(scanText(sample, { filename: "x.ts" })).toHaveLength(0); }); it("ignores 'your-' style placeholders", () => { const sample = `PAYMENT_KEY=sk_live_your-real-payment-secret-here-1234`; expect(scanText(sample, { filename: "x.ts" })).toHaveLength(0); }); it("ignores angle-bracket placeholders", () => { const sample = `TOKEN=ghp_`; expect(scanText(sample, { filename: "x.ts" })).toHaveLength(0); }); it("ignores shell/JS interpolation", () => { const sample = "const k = `ghp_${process.env.GITHUB_TOKEN}aaaaaaaaaaaaaaaaaaaaa`;"; expect(scanText(sample, { filename: "x.ts" })).toHaveLength(0); }); it("ignores 'REDACTED' / 'xxxxx' / 'dummy' tokens", () => { expect(lineHasPlaceholder("ghp_REDACTED_VALUE")).toBe(true); expect(lineHasPlaceholder("AKIAxxxxxYYYYYY")).toBe(true); expect(lineHasPlaceholder("token=dummy_value_here")).toBe(true); }); }); describe("verify-secrets allowlist semantics", () => { it("treats .env.example as allowlisted", () => { const p = path.join(process.cwd(), "apps", "web", ".env.example"); expect(isAllowlistedPath(p)).toBe(true); }); it("treats .env.production.example as allowlisted", () => { expect( isAllowlistedPath("/repo/.env.production.example"), ).toBe(true); }); it("treats .env.test as allowlisted", () => { expect(isAllowlistedPath("/repo/.env.test")).toBe(true); }); it("treats test/spec/fixture/mock/stub paths as allowlisted", () => { expect(isAllowlistedPath("/repo/apps/web/x.test.ts")).toBe(true); expect(isAllowlistedPath("/repo/apps/web/x.spec.ts")).toBe(true); expect(isAllowlistedPath("/repo/scripts/fixtures/foo.json")).toBe(true); expect(isAllowlistedPath("/repo/apps/web/mocks/server.ts")).toBe(true); expect(isAllowlistedPath("/repo/apps/web/stubs/foo.ts")).toBe(true); }); it("does not allowlist incidental substrings that merely contain test words", () => { expect(isAllowlistedPath("/repo/apps/web/attestation/source.ts")).toBe(false); expect(isAllowlistedPath("/repo/apps/web/contest/source.ts")).toBe(false); expect(isAllowlistedPath("/repo/apps/web/test/source.ts")).toBe(true); }); it("does not allowlist arbitrary source files", () => { expect(isAllowlistedPath("/repo/apps/web/routes/login.tsx")).toBe(false); expect(isAllowlistedPath("/repo/packages/db/src/client.ts")).toBe(false); }); }); describe("verify-secrets registry shape", () => { it("exposes all expected pattern ids", () => { const ids = PATTERNS.map((p) => p.id); expect(ids).toEqual( expect.arrayContaining([ "github-pat", "github-fine-grained-pat", "gitlab-pat", "npm-access-token", "pypi-access-token", "huggingface-token", "google-api-key", "aws-access-key-id", "payment-secret-live", "payment-webhook-live", "slack-bot-token", "private-key-header", "age-secret-key", "jwt-like", "generic-high-entropy-assignment", "embedded-url-credentials", ]), ); }); }); describe("verify-secrets tracked-file coverage", () => { it("scans tracked hidden and infrastructure paths but not untracked files", () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "hivegrid-secrets-")); temporaryDirectories.push(root); spawnSync("git", ["init", "-q"], { cwd: root }); for (const relativePath of [".gitea/workflows/ci.yml", "infra/app.env", "runtime/api/index.js"]) { const absolutePath = path.join(root, relativePath); fs.mkdirSync(path.dirname(absolutePath), { recursive: true }); fs.writeFileSync(absolutePath, "tracked"); } fs.writeFileSync(path.join(root, "untracked.txt"), "not scanned"); spawnSync("git", ["add", ".gitea/workflows/ci.yml", "infra/app.env", "runtime/api/index.js"], { cwd: root }); const relativeFiles = collectFiles(root).map((file) => path.relative(root, file)).sort(); expect(relativeFiles).toEqual([".gitea/workflows/ci.yml", "infra/app.env", "runtime/api/index.js"]); }); it("redacts matched values from scanner output", () => { const secret = "ghp_AbCdEfGhIjKlMnOpQrStUv0123456789"; const output = formatFinding( { file: "/repo/app.ts", line: 1, patternId: "github-pat", match: secret }, "/repo", ); expect(output).not.toContain(secret); expect(output).toContain("redacted length="); }); it("recognizes only the canonical loopback Postgres test credential", () => { const finding = { patternId: "embedded-url-credentials", match: "postgresql://postgres:postgres@localhost:5432/hivegrid", }; expect(isKnownLocalCredential(finding)).toBe(true); expect( isKnownLocalCredential({ ...finding, match: "postgresql://hivegrid:hivegrid@127.0.0.1:5432/hivegrid", }), ).toBe(true); expect( isKnownLocalCredential({ ...finding, match: "postgresql://postgres:not-the-test-value@localhost:5432/hivegrid", }), ).toBe(false); expect( isKnownLocalCredential({ ...finding, match: "postgresql://postgres:postgres@db.internal:5432/hivegrid", }), ).toBe(false); }); });