Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263import { execFileSync } from "node:child_process";import { readFileSync, statSync } from "node:fs";import { join } from "node:path";import { formatFindings, scanText, shouldScanPath, type Finding } from "./secretScan";
export interface ScanOptions { cwd: string; /** Scan only these tracked paths. Default: every tracked file. */ paths?: string[]; maxBytes?: number;}
export interface ScanDeps { listTracked(cwd: string): string[]; readFile(path: string): string; fileSize(path: string): number;}
export interface ScanResult { /** 0 clean, 1 secrets found, 2 the scan could not run. */ exitCode: 0 | 1 | 2; output: string; findings: Finding[]; skipped: string[];}
const DEFAULT_MAX_BYTES = 1_000_000;
export const realScanDeps = (cwd: string): ScanDeps => ({ listTracked: () => execFileSync("git", ["ls-files", "-z"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }) .split("\0") .filter((p) => p !== ""), readFile: (p) => readFileSync(join(cwd, p), "utf8"), fileSize: (p) => statSync(join(cwd, p)).size,});
export function runSecretScan(options: ScanOptions, deps: ScanDeps = realScanDeps(options.cwd)): ScanResult { const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES; let candidates: string[]; try { candidates = options.paths && options.paths.length > 0 ? options.paths : deps.listTracked(options.cwd); } catch (err) { const first = (err instanceof Error ? err.message : String(err)).split("\n").find((l) => l.trim() !== "") ?? "unknown error"; return { exitCode: 2, output: `cannot list tracked files: ${first.replace(/^Command failed: .*$/, "git ls-files failed")}`, findings: [], skipped: [] }; } const findings: Finding[] = []; const skipped: string[] = []; for (const path of candidates.filter(shouldScanPath)) { try { if (deps.fileSize(path) > maxBytes) { skipped.push(`${path} (too large)`); continue; } findings.push(...scanText(deps.readFile(path), path)); } catch { skipped.push(`${path} (unreadable)`); } } const lines = [formatFindings(findings), ...skipped.map((s) => `skipped: ${s}`)]; return { exitCode: findings.length > 0 ? 1 : 0, output: lines.join("\n"), findings, skipped };}