Files
HiveGrid-Wireframes/hivegrid-cloud/scripts/verify-secrets.test.mjs
T

179 lines
7.0 KiB
JavaScript

import { describe, it, expect } from "vitest";
import path from "node:path";
import {
scanText,
isAllowlistedPath,
lineHasPlaceholder,
PATTERNS,
} from "./verify-secrets.mjs";
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 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 Stripe 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 === "stripe-secret-live")).toBe(
true,
);
expect(
testFindings.some((f) => f.patternId === "stripe-secret-live"),
).toBe(false);
});
it("detects Stripe 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 === "stripe-webhook-live")).toBe(true);
expect(b.some((f) => f.patternId === "stripe-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 = `STRIPE_KEY=sk_live_your-real-stripe-secret-here-1234`;
expect(scanText(sample, { filename: "x.ts" })).toHaveLength(0);
});
it("ignores angle-bracket placeholders", () => {
const sample = `TOKEN=ghp_<paste-token-here-with-padding>`;
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.fly.example and .env.test as allowlisted", () => {
expect(isAllowlistedPath("/repo/.env.fly.example")).toBe(true);
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 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",
"aws-access-key-id",
"stripe-secret-live",
"stripe-webhook-live",
"slack-bot-token",
"private-key-header",
"jwt-like",
"generic-high-entropy-assignment",
]),
);
});
});