#!/usr/bin/env node import { execFileSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { extname } from "node:path"; const skippedExtensions = new Set([ ".avif", ".bin", ".br", ".gif", ".ico", ".jpg", ".jpeg", ".lock", ".mp3", ".mp4", ".onnx", ".pdf", ".png", ".tar", ".tgz", ".webp", ".woff", ".woff2", ".zip", ]); const skippedFiles = new Set(["pnpm-lock.yaml"]); const secretPatterns = [ { pattern: /sk-(?:proj-)?[A-Za-z0-9_-]{24,}/, reason: "OpenAI-style API key" }, { pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/, reason: "GitHub token" }, { pattern: /AKIA[0-9A-Z]{16}/, reason: "AWS access key id" }, { pattern: /xox[baprs]-[A-Za-z0-9-]{24,}/, reason: "Slack token" }, { pattern: /-----BEGIN (?:RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----/, reason: "private key block" }, { pattern: /\bAuthorization\s*[:=]\s*Bearer\s+[A-Za-z0-9._-]{24,}/i, reason: "Authorization bearer token" }, { pattern: /\bCookie\s*[:=]\s*[^"'`\s]{40,}/i, reason: "Cookie header value" }, ]; const allowedExampleLine = (line) => { const normalized = line.toLowerCase(); return ( normalized.includes("xxxx") || normalized.includes("placeholder") || normalized.includes("replace-with") || normalized.includes("change-me") || normalized.includes("example") || normalized.includes("dummy") || normalized.includes("redaction") || normalized.includes("masked") || normalized.includes("begin private key-----\"") || normalized.includes("begin private key-----'") ); }; const trackedFiles = execFileSync("git", ["ls-files"], { encoding: "utf8" }) .split(/\r?\n/) .filter(Boolean) .filter((file) => !skippedFiles.has(file)) .filter((file) => !skippedExtensions.has(extname(file).toLowerCase())); const violations = []; for (const file of trackedFiles) { let source; try { source = readFileSync(file, "utf8"); } catch { continue; } if (source.includes("\u0000")) { continue; } const lines = source.split(/\r?\n/); lines.forEach((line, index) => { if (allowedExampleLine(line)) { return; } for (const { pattern, reason } of secretPatterns) { if (pattern.test(line)) { violations.push(`${file}:${index + 1}: ${reason}: ${line.trim()}`); } } }); } if (violations.length > 0) { console.error("Secret hygiene check failed. Remove real credentials or replace them with placeholders."); console.error(violations.join("\n")); process.exit(1); } console.log(`Secret hygiene check passed for ${trackedFiles.length} tracked text files.`);