(
input: string,
findingIds: string[],
opts: ScanOptions = {},
)
| 392 | * the skill drops the user to manual edit rather than silently mangling output. |
| 393 | */ |
| 394 | export function applyRedactions( |
| 395 | input: string, |
| 396 | findingIds: string[], |
| 397 | opts: ScanOptions = {}, |
| 398 | ): RedactResult { |
| 399 | const ids = new Set(findingIds); |
| 400 | const { findings } = scan(input, opts); |
| 401 | const targets = findings |
| 402 | .filter((f) => ids.has(f.id) && f.autoRedactable) |
| 403 | .map((f) => ({ f, ...locateSpan(input, f) })) |
| 404 | .filter((t) => t.start >= 0); |
| 405 | |
| 406 | // Right-to-left so earlier offsets remain valid after splicing. |
| 407 | targets.sort((a, b) => b.start - a.start); |
| 408 | |
| 409 | const skipped: Finding[] = []; |
| 410 | const diffLines: string[] = []; |
| 411 | let body = input; |
| 412 | |
| 413 | for (const t of targets) { |
| 414 | const pat = PATTERNS_BY_ID[t.f.id]; |
| 415 | const token = pat?.redactToken ?? "<REDACTED>"; |
| 416 | if (inStructuralToken(body, t.start, t.end)) { |
| 417 | skipped.push(t.f); |
| 418 | continue; |
| 419 | } |
| 420 | const before = lineContaining(body, t.start); |
| 421 | body = body.slice(0, t.start) + token + body.slice(t.end); |
| 422 | const after = lineContaining(body, t.start); |
| 423 | diffLines.push(`- ${before}`); |
| 424 | diffLines.push(`+ ${after}`); |
| 425 | } |
| 426 | |
| 427 | return { body, diff: diffLines.reverse().join("\n"), skipped }; |
| 428 | } |
| 429 | |
| 430 | /** |
| 431 | * Patterns whose regex captures only a MARKER, not the secret payload itself |
no test coverage detected