#!/usr/bin/env node /** * Secret scanner — fails if known secret patterns appear in tracked files. * Run before committing: node scripts/check-secrets.mjs * Exit 0 = clean, exit 1 = secrets found. * * Patterns (loose on purpose — catches real keys, tolerates placeholders): * - sb_secret_* (Supabase service role) unless the token is a placeholder * - sk-[A-Za-z0-9]{20,} (OpenAI/Anthropic-style) * - AKIA[0-9A-Z]{16} (AWS) */ import { execSync } from "node:child_process"; const patterns = [ { name: "supabase-service-role", re: /sb_secret_[A-Za-z0-9_]{10,}/ }, { name: "openai-style-key", re: /sk-[A-Za-z0-9]{20,}/ }, { name: "aws-access-key", re: /AKIA[0-9A-Z]{16}/ }, { name: "bearer-jwt", re: /eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}/ }, ]; // Files that are allowed to mention placeholder-shaped values const placeholders = new Set([ "sb_secret_placeholder", "sk-your-key-here", "sk-ant-your-key-here", ]); let tracked; try { tracked = execSync("git ls-files", { encoding: "utf8" }) .split("\n") .filter(Boolean); } catch { console.error("Not a git repo — run from the repository root."); process.exit(1); } const findings = []; for (const file of tracked) { if (/package-lock\.json$/.test(file)) continue; // lockfiles: skip (noise) let content; try { content = require("node:fs").readFileSync(file, "utf8"); } catch { continue; // binary / unreadable } for (const { name, re } of patterns) { const match = content.match(re); if (match) { const value = match[0]; if (placeholders.has(value)) continue; findings.push({ file, name, value }); } } } if (findings.length === 0) { console.log("✅ No secrets found in tracked files."); process.exit(0); } console.error("❌ Possible secrets found in tracked files:"); for (const { file, name, value } of findings) { console.error(` - ${file} [${name}]: ${value}`); } console.error("\nScrub the value, rotate the key if it was ever live, then re-run."); process.exit(1);