#!/usr/bin/env node

// NERF Pre-Commit Security Scan Hook
// Runs basic security checks before git commits:
// - Detects hardcoded secrets (API keys, tokens, passwords)
// - Flags dangerous patterns (eval, exec, innerHTML)
// - Checks for .env files being committed
//
// Designed for Claude Code PreCommit hook type.
// Reads staged diff from stdin, outputs warnings as JSON.

"use strict";

const fs = require("fs");

const SECRET_PATTERNS = [
  { name: "AWS Access Key", pattern: /AKIA[0-9A-Z]{16}/g },
  { name: "AWS Secret Key", pattern: /(?:aws_secret_access_key|secret_key)\s*[=:]\s*['"]?[A-Za-z0-9/+=]{40}/gi },
  { name: "Generic API Key", pattern: /(?:api[_-]?key|apikey)\s*[=:]\s*['"]?[A-Za-z0-9_\-]{20,}/gi },
  { name: "Private Key", pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/g },
  { name: "GitHub Token", pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/g },
  { name: "Slack Token", pattern: /xox[baprs]-[0-9a-zA-Z-]{10,}/g },
  { name: "JWT", pattern: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g },
  { name: "Hardcoded Password", pattern: /(?:password|passwd|pwd)\s*[=:]\s*['"][^'"]{8,}['"]/gi },
];

const DANGEROUS_PATTERNS = [
  { name: "eval()", pattern: /\beval\s*\(/g, severity: "warn" },
  { name: "innerHTML assignment", pattern: /\.innerHTML\s*=/g, severity: "warn" },
  { name: "child_process.exec with user input", pattern: /exec\s*\(\s*[`$]/g, severity: "warn" },
  { name: "SQL string concatenation", pattern: /(?:SELECT|INSERT|UPDATE|DELETE)\s.*\+\s*(?:req\.|user|input)/gi, severity: "warn" },
];

async function main() {
  let input = "";

  // Read stdin (staged diff)
  if (!process.stdin.isTTY) {
    const chunks = [];
    for await (const chunk of process.stdin) {
      chunks.push(chunk);
    }
    input = Buffer.concat(chunks).toString("utf-8");
  }

  if (!input.trim()) {
    // No input, pass through
    process.stdout.write(JSON.stringify({ decision: "approve" }) + "\n");
    return;
  }

  const findings = [];

  // Check for secrets
  for (const { name, pattern } of SECRET_PATTERNS) {
    pattern.lastIndex = 0;
    if (pattern.test(input)) {
      findings.push({
        severity: "error",
        message: `Potential secret detected: ${name}`,
      });
    }
  }

  // Check for dangerous patterns (only in added lines)
  const addedLines = input
    .split("\n")
    .filter((line) => line.startsWith("+") && !line.startsWith("+++"));

  for (const { name, pattern, severity } of DANGEROUS_PATTERNS) {
    for (const line of addedLines) {
      pattern.lastIndex = 0;
      if (pattern.test(line)) {
        findings.push({
          severity,
          message: `Dangerous pattern: ${name}`,
        });
        break; // One finding per pattern
      }
    }
  }

  // Check for .env files
  if (input.includes("diff --git a/.env") || input.includes("+++ b/.env")) {
    findings.push({
      severity: "error",
      message: ".env file staged for commit: secrets may be exposed",
    });
  }

  if (findings.length === 0) {
    process.stdout.write(JSON.stringify({ decision: "approve" }) + "\n");
  } else {
    const hasErrors = findings.some((f) => f.severity === "error");
    process.stdout.write(
      JSON.stringify({
        decision: hasErrors ? "block" : "approve",
        reason: findings.map((f) => `[${f.severity}] ${f.message}`).join("\n"),
      }) + "\n"
    );
  }
}

main().catch(() => {
  // Never block commits on hook failure
  process.stdout.write(JSON.stringify({ decision: "approve" }) + "\n");
});
