(path: string)
| 149 | * applies to staged git diffs). |
| 150 | */ |
| 151 | export function secretScanFile(path: string): SecretScanResult { |
| 152 | if (!existsSync(path)) { |
| 153 | return { scanned: false, findings: [], scanner: "error" }; |
| 154 | } |
| 155 | if (!gitleaksAvailable()) { |
| 156 | return { scanned: false, findings: [], scanner: "missing" }; |
| 157 | } |
| 158 | try { |
| 159 | // gitleaks detect --no-git --source <path> --report-format json --report-path - |
| 160 | // Returns 0 on clean, 1 on findings, 126/127 on bad invocation. |
| 161 | const out = execFileSync( |
| 162 | "gitleaks", |
| 163 | ["detect", "--no-git", "--source", path, "--report-format", "json", "--report-path", "/dev/stdout", "--exit-code", "0"], |
| 164 | { encoding: "utf-8", env: process.env, maxBuffer: 16 * 1024 * 1024 } |
| 165 | ); |
| 166 | const trimmed = out.trim(); |
| 167 | if (!trimmed) return { scanned: true, findings: [], scanner: "gitleaks" }; |
| 168 | const parsed = JSON.parse(trimmed) as Array<{ |
| 169 | RuleID: string; |
| 170 | Description: string; |
| 171 | StartLine: number; |
| 172 | Match?: string; |
| 173 | Secret?: string; |
| 174 | }>; |
| 175 | const findings: SecretFinding[] = (parsed || []).map((f) => ({ |
| 176 | rule_id: f.RuleID || "unknown", |
| 177 | description: f.Description || "", |
| 178 | line: f.StartLine || 0, |
| 179 | redacted_match: redactMatch(f.Secret || f.Match || ""), |
| 180 | })); |
| 181 | return { scanned: true, findings, scanner: "gitleaks" }; |
| 182 | } catch (err) { |
| 183 | return { |
| 184 | scanned: false, |
| 185 | findings: [], |
| 186 | scanner: "error", |
| 187 | }; |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | function redactMatch(s: string): string { |
| 192 | if (!s) return ""; |
no test coverage detected