// Copyright (c) 2026 defconxt. All rights reserved.
// Licensed under AGPL-3.0 — see LICENSE file for details.
// NERF is a trademark of defconxt.

/**
 * Blind Hunter — Pattern-based vulnerability detection layer.
 *
 * Scans source code for known-bad patterns using regex matching.
 * Language-aware: applies different rulesets based on detected language.
 * No context about intent — pure signal extraction to avoid anchoring bias.
 *
 * @module review/layers/blind-hunter
 */

import { ReviewFinding, Severity } from '../engine.js';

// ---------------------------------------------------------------------------
// Pattern definitions
// ---------------------------------------------------------------------------

/**
 * @typedef {object} VulnPattern
 * @property {string} id           - Unique pattern identifier
 * @property {string} title        - Finding title
 * @property {RegExp} pattern      - Detection regex
 * @property {string} severity     - Severity level
 * @property {string[]} cweIds     - Associated CWEs
 * @property {string} description  - What this pattern catches
 * @property {string} remediation  - Fix guidance
 * @property {string[]} languages  - Which languages this applies to ('*' = all)
 * @property {string[]} [tags]     - Additional tags
 * @property {RegExp} [exclude]    - Lines matching this are false positives
 */

/** @type {VulnPattern[]} */
const PATTERNS = [
  // ── Injection ─────────────────────────────────────────────────────────
  {
    id: 'BH-SQLI-001',
    title: 'SQL injection — string concatenation in query',
    pattern: /(?:query|execute|exec|run)\s*\(\s*[`'"].*?\$\{|(?:query|execute|exec|run)\s*\(\s*['"].*?\+\s*(?:req\.|params\.|body\.|query\.|input|user)/gi,
    severity: Severity.CRITICAL,
    cweIds: ['CWE-89'],
    description: 'SQL query built with string concatenation or template literals containing user input.',
    remediation: 'Use parameterized queries or prepared statements.',
    languages: ['*'],
    tags: ['owasp-a03', 'T1190'],
  },
  {
    id: 'BH-SQLI-002',
    title: 'SQL injection — raw query with variable',
    pattern: /\.(?:raw|rawQuery|unsafe)\s*\(\s*[`'"]/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-89'],
    description: 'Raw SQL query method used. Verify that all inputs are parameterized.',
    remediation: 'Replace raw queries with the ORM\'s parameterized query builder.',
    languages: ['javascript', 'typescript', 'python', 'ruby'],
    tags: ['owasp-a03'],
  },
  {
    id: 'BH-CMDI-001',
    title: 'Command injection — shell execution with variable',
    pattern: /(?:exec|execSync|spawn|spawnSync|system|popen|subprocess\.(?:call|run|Popen))\s*\(\s*(?:[`'"].*?\$\{|.*?\+\s*(?:req\.|params\.|body\.|input|user|arg))/gi,
    severity: Severity.CRITICAL,
    cweIds: ['CWE-78'],
    description: 'Shell command built with user-controlled input.',
    remediation: 'Use execFile/execFileSync with argument arrays, or validate/escape input strictly.',
    languages: ['*'],
    tags: ['owasp-a03', 'T1059'],
  },
  {
    id: 'BH-CMDI-002',
    title: 'Command injection — shell: true with variable args',
    pattern: /shell\s*:\s*true/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-78'],
    description: 'Process spawned with shell: true. Shell metacharacters in arguments can lead to injection.',
    remediation: 'Remove shell: true and pass arguments as an array.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a03'],
  },
  {
    id: 'BH-PATH-001',
    title: 'Path traversal — user input in file path',
    pattern: /(?:readFile|writeFile|createReadStream|createWriteStream|readdir|unlink|rmSync|open)\s*\(\s*(?:[`'"].*?\$\{|.*?\+\s*(?:req\.|params\.|body\.|query\.|input|user))/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-22'],
    description: 'File operation uses user-controlled path without sanitization.',
    remediation: 'Validate path with path.resolve() and ensure it stays within an allowed base directory.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a03', 'T1083'],
  },
  {
    id: 'BH-PATH-002',
    title: 'Path traversal — Python file open with user input',
    pattern: /open\s*\(\s*(?:f['"'].*?\{|.*?\+\s*(?:request\.|input|user|arg))/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-22'],
    description: 'File open() with user-controlled path.',
    remediation: 'Use pathlib and validate the resolved path against an allowed directory.',
    languages: ['python'],
    tags: ['owasp-a03'],
  },

  // ── XSS ───────────────────────────────────────────────────────────────
  {
    id: 'BH-XSS-001',
    title: 'XSS — innerHTML assignment',
    pattern: /\.innerHTML\s*=\s*(?!['"`]\s*$)/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-79'],
    description: 'Setting innerHTML with dynamic content enables XSS.',
    remediation: 'Use textContent, or sanitize with DOMPurify before innerHTML assignment.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a03'],
  },
  {
    id: 'BH-XSS-002',
    title: 'XSS — document.write usage',
    pattern: /document\.write\s*\(/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-79'],
    description: 'document.write() can inject unsanitized content into the DOM.',
    remediation: 'Use DOM APIs (createElement, appendChild) instead.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a03'],
  },
  {
    id: 'BH-XSS-003',
    title: 'XSS — dangerouslySetInnerHTML',
    pattern: /dangerouslySetInnerHTML/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-79'],
    description: 'React dangerouslySetInnerHTML bypasses built-in XSS protection.',
    remediation: 'Sanitize content with DOMPurify before passing to dangerouslySetInnerHTML.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a03'],
  },

  // ── Eval / Code Execution ─────────────────────────────────────────────
  {
    id: 'BH-EVAL-001',
    title: 'Code injection — eval() usage',
    pattern: /\beval\s*\(/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-95'],
    description: 'eval() executes arbitrary code. If input is user-controlled, this is code injection.',
    remediation: 'Replace eval with JSON.parse, Function constructor with validated input, or a safe expression parser.',
    languages: ['javascript', 'typescript', 'python'],
    tags: ['owasp-a03', 'T1059'],
    exclude: /\/\/.*eval|\/\*.*eval|\*.*eval|\.eslint|no-eval/,
  },
  {
    id: 'BH-EVAL-002',
    title: 'Code injection — new Function() constructor',
    pattern: /new\s+Function\s*\(/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-95'],
    description: 'new Function() is functionally equivalent to eval().',
    remediation: 'Use a safe expression evaluator or structured approach.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a03'],
  },
  {
    id: 'BH-EVAL-003',
    title: 'Code injection — exec() with dynamic input',
    pattern: /\bexec\s*\(\s*(?:f['"']|.*?\+\s*|.*?\%)/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-95'],
    description: 'Python exec() with formatted string or concatenation.',
    remediation: 'Avoid exec(). Use ast.literal_eval() for safe evaluation.',
    languages: ['python'],
    tags: ['owasp-a03'],
    exclude: /child_process|subprocess|execFile|execSync/,
  },

  // ── Secrets / Credentials ─────────────────────────────────────────────
  {
    id: 'BH-SEC-001',
    title: 'Hardcoded secret — API key or token pattern',
    pattern: /(?:api[_-]?key|api[_-]?secret|auth[_-]?token|access[_-]?token|secret[_-]?key|private[_-]?key)\s*[:=]\s*['"]\S{8,}['"]/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-798'],
    description: 'Hardcoded credential found in source code.',
    remediation: 'Move secrets to environment variables or a secrets manager.',
    languages: ['*'],
    tags: ['owasp-a02', 'T1552.001'],
    exclude: /example|placeholder|test|mock|dummy|YOUR_|CHANGE_ME|process\.env|os\.environ/i,
  },
  {
    id: 'BH-SEC-002',
    title: 'Hardcoded secret — AWS key pattern',
    pattern: /(?:AKIA[0-9A-Z]{16})/g,
    severity: Severity.CRITICAL,
    cweIds: ['CWE-798'],
    description: 'AWS access key ID detected in source code.',
    remediation: 'Rotate the key immediately, move to environment variables or IAM roles.',
    languages: ['*'],
    tags: ['owasp-a02', 'T1552.001'],
  },
  {
    id: 'BH-SEC-003',
    title: 'Hardcoded secret — password in code',
    pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{4,}['"]/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-798'],
    description: 'Hardcoded password found in source code.',
    remediation: 'Move passwords to environment variables or a secrets manager.',
    languages: ['*'],
    tags: ['owasp-a02', 'T1552.001'],
    exclude: /example|placeholder|test|mock|dummy|YOUR_|CHANGE_ME|process\.env|os\.environ|\*{3,}|\.{3,}/i,
  },
  {
    id: 'BH-SEC-004',
    title: 'Hardcoded secret — JWT or bearer token',
    pattern: /(?:eyJ[A-Za-z0-9_-]{20,}\.eyJ[A-Za-z0-9_-]{20,})/g,
    severity: Severity.HIGH,
    cweIds: ['CWE-798'],
    description: 'JWT token found hardcoded in source code.',
    remediation: 'Remove the token, rotate it, and load from environment or auth flow.',
    languages: ['*'],
    tags: ['owasp-a02', 'T1552.001'],
  },
  {
    id: 'BH-SEC-005',
    title: 'Hardcoded secret — private key block',
    pattern: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/gi,
    severity: Severity.CRITICAL,
    cweIds: ['CWE-798'],
    description: 'PEM private key found in source code.',
    remediation: 'Remove the key, rotate it, and store in a secrets manager or HSM.',
    languages: ['*'],
    tags: ['owasp-a02', 'T1552.001'],
  },

  // ── Insecure Crypto ───────────────────────────────────────────────────
  {
    id: 'BH-CRYPTO-001',
    title: 'Weak hash — MD5 usage',
    pattern: /(?:createHash\s*\(\s*['"]md5['"]|md5\s*\(|hashlib\.md5|MD5\.Create|Digest::MD5)/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-328'],
    description: 'MD5 is cryptographically broken. Not suitable for security purposes.',
    remediation: 'Use SHA-256 or better (SHA-3, BLAKE3) for integrity. Use bcrypt/scrypt/argon2 for passwords.',
    languages: ['*'],
    tags: ['owasp-a02'],
    exclude: /checksum|etag|cache|fingerprint|non-security/i,
  },
  {
    id: 'BH-CRYPTO-002',
    title: 'Weak hash — SHA-1 usage',
    pattern: /(?:createHash\s*\(\s*['"]sha1['"]|hashlib\.sha1|SHA1\.Create)/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-328'],
    description: 'SHA-1 is deprecated for security use due to practical collision attacks.',
    remediation: 'Use SHA-256 or better.',
    languages: ['*'],
    tags: ['owasp-a02'],
    exclude: /git|subresource|integrity/i,
  },
  {
    id: 'BH-CRYPTO-003',
    title: 'Insecure random — Math.random() for security',
    pattern: /Math\.random\s*\(\)/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-338'],
    description: 'Math.random() is not cryptographically secure. Predictable output.',
    remediation: 'Use crypto.randomBytes(), crypto.randomUUID(), or crypto.getRandomValues().',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a02'],
    exclude: /test|mock|demo|example|shuffle|color|animation/i,
  },

  // ── Prototype Pollution ───────────────────────────────────────────────
  {
    id: 'BH-PROTO-001',
    title: 'Prototype pollution — recursive merge without protection',
    pattern: /(?:Object\.assign|_\.merge|_\.extend|_\.defaults|deepMerge|deepExtend)\s*\(/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-1321'],
    description: 'Deep merge without __proto__/constructor/prototype filtering enables prototype pollution.',
    remediation: 'Use a merge function that skips __proto__, constructor, and prototype keys.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a03'],
  },
  {
    id: 'BH-PROTO-002',
    title: 'Prototype pollution — bracket notation with variable key',
    pattern: /\]\s*\[\s*(?:key|prop|name|attr|field)\s*\]/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-1321'],
    description: 'Nested bracket notation with variable key enables prototype pollution.',
    remediation: 'Validate keys against a whitelist or use Map instead of plain objects.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a03'],
  },

  // ── SSRF ──────────────────────────────────────────────────────────────
  {
    id: 'BH-SSRF-001',
    title: 'SSRF — fetch/request with user-controlled URL',
    pattern: /(?:fetch|axios|got|request|http\.get|urllib\.request)\s*\(\s*(?:[`'"].*?\$\{|.*?\+\s*(?:req\.|params\.|body\.|query\.|input|user|url))/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-918'],
    description: 'HTTP request made with user-controlled URL. Server-side request forgery risk.',
    remediation: 'Validate URLs against an allowlist of domains. Block internal/private IP ranges.',
    languages: ['*'],
    tags: ['owasp-a10', 'T1090'],
  },

  // ── Deserialization ───────────────────────────────────────────────────
  {
    id: 'BH-DESER-001',
    title: 'Unsafe deserialization — pickle/yaml load',
    pattern: /(?:pickle\.loads?|yaml\.(?:load|unsafe_load)\s*\((?!.*Loader\s*=\s*yaml\.SafeLoader))/gi,
    severity: Severity.CRITICAL,
    cweIds: ['CWE-502'],
    description: 'Unsafe deserialization can lead to remote code execution.',
    remediation: 'Use yaml.safe_load() instead of yaml.load(). Avoid pickle for untrusted data.',
    languages: ['python'],
    tags: ['owasp-a08', 'T1059'],
  },
  {
    id: 'BH-DESER-002',
    title: 'Unsafe deserialization — unserialize()',
    pattern: /\bunserialize\s*\(/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-502'],
    description: 'PHP unserialize() with user input enables object injection.',
    remediation: 'Use json_decode() instead, or validate input strictly before unserialize.',
    languages: ['php'],
    tags: ['owasp-a08'],
  },

  // ── Regex DoS ─────────────────────────────────────────────────────────
  {
    id: 'BH-REDOS-001',
    title: 'Regex DoS — catastrophic backtracking pattern',
    pattern: /new\s+RegExp\s*\(\s*(?:req\.|params\.|body\.|query\.|input|user)/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-1333'],
    description: 'User-controlled regex pattern enables ReDoS (Regular Expression DoS).',
    remediation: 'Never compile user input as regex. Use string matching or a regex sandbox.',
    languages: ['javascript', 'typescript', 'python'],
    tags: ['owasp-a06'],
  },

  // ── Misconfiguration ─────────────────────────────────────────────────
  {
    id: 'BH-MISC-001',
    title: 'CORS misconfiguration — wildcard origin',
    pattern: /(?:Access-Control-Allow-Origin['"]\s*:\s*['"]\*|origin\s*:\s*['"]\*|cors\s*\(\s*\))/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-942'],
    description: 'Wildcard CORS origin allows any website to make authenticated requests.',
    remediation: 'Restrict CORS to specific trusted origins.',
    languages: ['javascript', 'typescript', 'python'],
    tags: ['owasp-a05'],
  },
  {
    id: 'BH-MISC-002',
    title: 'Debug mode enabled in production',
    pattern: /(?:DEBUG\s*[:=]\s*(?:True|true|1|['"]true['"])|debug\s*:\s*true|NODE_ENV\s*[:=!]=\s*['"]development['"])/gi,
    severity: Severity.LOW,
    cweIds: ['CWE-489'],
    description: 'Debug mode may expose sensitive information in production.',
    remediation: 'Ensure DEBUG is disabled in production configuration.',
    languages: ['*'],
    tags: ['owasp-a05'],
    exclude: /test|spec|\.test\.|\.spec\.|example|if\s*\(/i,
  },
  {
    id: 'BH-MISC-003',
    title: 'Disabled TLS verification',
    pattern: /(?:rejectUnauthorized\s*:\s*false|verify\s*[:=]\s*False|CURLOPT_SSL_VERIFYPEER\s*,\s*(?:false|0)|InsecureSkipVerify\s*:\s*true)/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-295'],
    description: 'TLS certificate verification disabled. Enables MITM attacks.',
    remediation: 'Enable certificate verification. Use a custom CA bundle if needed.',
    languages: ['*'],
    tags: ['owasp-a07', 'T1557'],
  },

  // ── Dangerous APIs ────────────────────────────────────────────────────
  {
    id: 'BH-API-001',
    title: 'Disabled security header — helmet not used',
    pattern: /app\.disable\s*\(\s*['"]x-powered-by['"]\s*\)/gi,
    severity: Severity.INFO,
    cweIds: ['CWE-16'],
    description: 'Manual header removal instead of using helmet middleware.',
    remediation: 'Use helmet() middleware for comprehensive security headers.',
    languages: ['javascript', 'typescript'],
    tags: ['owasp-a05'],
  },
  {
    id: 'BH-API-002',
    title: 'Unvalidated redirect',
    pattern: /(?:res\.redirect|redirect)\s*\(\s*(?:req\.|params\.|body\.|query\.|input|user)/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-601'],
    description: 'Redirect destination from user input enables open redirect attacks.',
    remediation: 'Validate redirect URLs against an allowlist of paths or domains.',
    languages: ['javascript', 'typescript', 'python'],
    tags: ['owasp-a03'],
  },

  // ── Shell / Infrastructure ────────────────────────────────────────────
  {
    id: 'BH-SHELL-001',
    title: 'World-writable permissions',
    pattern: /chmod\s+(?:777|666|o\+w)/gi,
    severity: Severity.MEDIUM,
    cweIds: ['CWE-732'],
    description: 'World-writable file permissions allow any user to modify the file.',
    remediation: 'Use least-privilege permissions (e.g., 755 for dirs, 644 for files).',
    languages: ['shell', '*'],
    tags: ['T1222'],
  },
  {
    id: 'BH-SHELL-002',
    title: 'Curl pipe to shell — unsigned code execution',
    pattern: /curl\s+.*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)/gi,
    severity: Severity.HIGH,
    cweIds: ['CWE-494'],
    description: 'Downloading and executing scripts in one command without verification.',
    remediation: 'Download first, verify integrity (checksum/signature), then execute.',
    languages: ['shell', '*'],
    tags: ['T1105'],
  },
];

// ---------------------------------------------------------------------------
// Blind Hunter review function
// ---------------------------------------------------------------------------

/**
 * Run Blind Hunter pattern matching against source files.
 *
 * @param {import('../engine.js').SourceFile[]} sources
 * @param {object} [options]
 * @returns {Promise<ReviewFinding[]>}
 */
export async function blindHunterReview(sources, options = {}) {
  const findings = [];

  for (const source of sources) {
    const lines = source.content.split('\n');

    for (const pat of PATTERNS) {
      // Language filter
      if (!pat.languages.includes('*') && !pat.languages.includes(source.language)) {
        continue;
      }

      // Test each line
      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];

        // Skip comments (simple heuristic)
        const trimmed = line.trimStart();
        if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*')) {
          // Still check for secrets in comments — they shouldn't be there either
          if (!pat.id.startsWith('BH-SEC')) continue;
        }

        // Reset regex lastIndex for global patterns
        pat.pattern.lastIndex = 0;
        const match = pat.pattern.exec(line);
        if (!match) continue;

        // Check exclusion pattern
        if (pat.exclude) {
          pat.exclude.lastIndex = 0;
          if (pat.exclude.test(line)) continue;
        }

        findings.push(
          new ReviewFinding({
            title: pat.title,
            severity: pat.severity,
            layer: 'blind-hunter',
            file: source.path,
            line: i + 1,
            column: match.index + 1,
            description: pat.description,
            proof: line.trim().slice(0, 200),
            remediation: pat.remediation,
            cweIds: [...pat.cweIds],
            tags: pat.tags ? [...pat.tags] : [],
            language: source.language,
            meta: { patternId: pat.id },
          }),
        );
      }
    }
  }

  return findings;
}
