Repository Public
1import { execFileSync } from "node:child_process";2import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";3import path from "node:path";45/**6 * Fails when a secret could reach a browser or the repository:7 * 1. no xAI key pattern and no value of a secret env var in the built client chunks (.next/static);8 * 2. no key pattern in any git-tracked file;9 * 3. .env is not tracked.10 */11const KEY = /xai-[A-Za-z0-9]{40,}/;12const root = process.cwd();13const problems: string[] = [];1415function walk(dir: string, out: string[] = []): string[] {16 if (!existsSync(dir)) return out;17 for (const name of readdirSync(dir)) {18 const p = path.join(dir, name);19 if (statSync(p).isDirectory()) walk(p, out); else out.push(p);20 }21 return out;22}2324// Secret values present in the environment (never printed).25const secretValues = ["XAI_API_KEY", "APP_SECRET", "DATABASE_URL"].map((k) => {26 if (process.env[k]) return process.env[k]!;27 const env = existsSync(path.join(root, ".env")) ? readFileSync(path.join(root, ".env"), "utf8") : "";28 return env.match(new RegExp(`^${k}=(.*)$`, "m"))?.[1]?.trim() ?? "";29}).filter((v) => v.length >= 16);3031const staticDir = path.join(root, ".next/static");32const chunks = walk(staticDir).filter((f) => /\.(js|css|map|json|html)$/.test(f));33if (!chunks.length) problems.push(".next/static is empty: run the build before the scan");34for (const f of chunks) {35 const text = readFileSync(f, "utf8");36 if (KEY.test(text)) problems.push(`API key pattern in client bundle: ${path.relative(root, f)}`);37 for (const v of secretValues) if (text.includes(v)) problems.push(`secret env value in client bundle: ${path.relative(root, f)}`);38 if (/XAI_API_KEY|APP_SECRET|DATABASE_URL/.test(text)) problems.push(`secret env name referenced in client bundle: ${path.relative(root, f)}`);39}4041let tracked: string[] = [];42try { tracked = execFileSync("git", ["ls-files"], { cwd: root, encoding: "utf8" }).split("\n").filter(Boolean); } catch { /* not a git checkout */ }43for (const f of tracked) {44 if (/^\.env(\..+)?$/.test(f) && f !== ".env.example") problems.push(`environment file is tracked by git: ${f}`);45 const p = path.join(root, f);46 if (!existsSync(p) || statSync(p).size > 2_000_000) continue;47 const text = readFileSync(p, "utf8");48 if (KEY.test(text)) problems.push(`API key pattern in tracked file: ${f}`);49 for (const v of secretValues) if (text.includes(v)) problems.push(`secret env value in tracked file: ${f}`);50}5152if (problems.length) { console.error(`secrets scan FAILED\n${[...new Set(problems)].map((p) => ` - ${p}`).join("\n")}`); process.exit(1); }53console.log(`secrets scan ok: ${chunks.length} client files, ${tracked.length} tracked files, ${secretValues.length} secret values checked`);54