"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.grepSecretsProvider = void 0;
exports.gatherGrepSecretsResult = gatherGrepSecretsResult;
/**
 * Pattern-based secret scanner that COMPLEMENTS gitleaks.
 *
 * gitleaks is keyed to known token *formats* (AWS / GitHub / Stripe /
 * private keys) — it is excellent at those and deliberately does NOT
 * flag generic hardcoded credentials like `password = "hunter2"`,
 * because a naive entropy rule floods every codebase with false
 * positives. Verified empirically: gitleaks reports zero on
 * `password = "..."` / `api_key = "..."` assignments.
 *
 * That leaves a real gap: a developer who hardcodes a plain password
 * sails through the guardrail. This provider closes it. The patterns
 * split into two classes:
 *
 *   - GENERIC keyword-assignment secrets (`password`/`secret`/`token` =
 *     a quoted literal). gitleaks misses these, so they run ALWAYS —
 *     they are the complement of gitleaks coverage, not a fallback.
 *   - BRANDED token shapes (AWS keys, GitHub PATs, private keys).
 *     gitleaks covers these with higher precision, so they run ONLY
 *     when gitleaks is absent — full standalone fallback, no
 *     double-counting when both scanners are present.
 *
 * Scanning is in-process via the canonical `walkSourceFiles` walker
 * (not POSIX `grep -r`, which is unavailable on Windows and overflows
 * maxBuffer on large repos). Findings flow through the same SECRETS
 * capability + fingerprint + baseline path as gitleaks, so a hardcoded
 * password gates a push exactly like a leaked AWS key.
 */
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const tool_registry_1 = require("./tool-registry");
const suppressions_1 = require("./suppressions");
const walk_source_files_1 = require("./walk-source-files");
/**
 * Generic keyword-to-quoted-literal assignments. Case-insensitive; the
 * trailing `["'][^"']{3,}` anchor requires an actual quoted value of at
 * least 3 chars, which is what separates a real hardcoded secret from a
 * config read (`password = config.get("x")` — value isn't a literal),
 * a comparison (`if password ==`), or an empty placeholder
 * (`password = ""`). These run on every scan — gitleaks does not.
 */
const GENERIC_PATTERNS = [
    { regex: /password\s*[:=]\s*["'][^"']{3,}/i, rule: 'hardcoded-password' },
    { regex: /(?:api[_-]?key|apikey)\s*[:=]\s*["'][^"']{3,}/i, rule: 'hardcoded-api-key' },
    { regex: /(?:secret|token|passwd|pwd)\s*[:=]\s*["'][^"']{3,}/i, rule: 'hardcoded-secret' },
];
/**
 * Branded / structured token shapes. gitleaks detects these with higher
 * precision (and fewer false positives), so they only run as a
 * standalone fallback when gitleaks is unavailable.
 */
const BRANDED_PATTERNS = [
    { regex: /BEGIN.*PRIVATE KEY/, rule: 'private-key-in-source' },
    { regex: /AKIA[0-9A-Z]{16}/, rule: 'aws-access-key' },
    { regex: /ghp_[a-zA-Z0-9]{36}/, rule: 'github-token' },
    { regex: /sk-ant-[a-zA-Z0-9]/, rule: 'anthropic-api-key' },
];
function severityFor(rule) {
    return rule.includes('private-key') || rule.includes('password') ? 'critical' : 'high';
}
/**
 * Scan source files for hardcoded secrets gitleaks doesn't cover (plus
 * the branded fallback set when gitleaks is absent). Never returns null:
 * the generic patterns always contribute, so this provider runs on every
 * analysis rather than yielding wholesale to gitleaks.
 */
function gatherGrepSecretsResult(cwd) {
    const gitleaks = (0, tool_registry_1.findTool)(tool_registry_1.TOOL_DEFS.gitleaks, cwd);
    // Generic keyword-assignment patterns always run (gitleaks misses
    // them). Branded patterns only when gitleaks is absent (it covers them
    // better, and running both would double-count the same AWS key).
    const patterns = gitleaks.available
        ? GENERIC_PATTERNS
        : [...GENERIC_PATTERNS, ...BRANDED_PATTERNS];
    // Canonical walker: project-relative source paths with the resolved
    // exclusion set already applied. includeTests so a password hardcoded
    // in a test still surfaces (a real fixture is allowlisted as
    // `test-fixture`, not silently ignored).
    const files = (0, walk_source_files_1.walkSourceFiles)(cwd, { includeTests: true, includeAutogen: true });
    const raw = [];
    for (const rel of files) {
        let content;
        try {
            content = fs.readFileSync(path.join(cwd, rel), 'utf-8');
        }
        catch {
            continue;
        }
        const lines = content.split('\n');
        for (let i = 0; i < lines.length; i++) {
            for (const sp of patterns) {
                if (sp.regex.test(lines[i])) {
                    raw.push({ file: rel, line: i + 1, rule: sp.rule, severity: severityFor(sp.rule) });
                    break; // at most one finding per line
                }
            }
        }
    }
    // Apply `.dxkit-suppressions.json` under the same key gitleaks uses, so
    // a repo's existing suppressions cover both scanners.
    const suppressions = (0, suppressions_1.loadSuppressions)(cwd);
    const { kept, suppressed } = (0, suppressions_1.applySuppressions)(raw, suppressions.gitleaks, (d) => d.rule, (d) => d.file);
    return {
        schemaVersion: 1,
        tool: 'grep-secrets',
        findings: kept,
        suppressedCount: suppressed.length,
    };
}
exports.grepSecretsProvider = {
    source: 'grep-secrets',
    async gather(cwd) {
        return gatherGrepSecretsResult(cwd);
    },
};
//# sourceMappingURL=grep-secrets.js.map