/** * Secret detection + redaction for user-submitted text. * * Why this exists: a user pasted a GitHub PAT (`ghp_...`) directly into * chat as a way to give Franklin authenticated access to the GitHub API. * The model correctly refused to use the raw value and warned the user, * but by then the token had already entered: * - the LLM API request body (sent to the gateway + upstream provider) * - the persisted session file on disk * - any later compaction summary (which would re-send it to the model) * * What the user actually wants is for Franklin to **remember the credential * and keep using it**, not refuse it. So this module's job is two-fold: * * 1. Strip the raw value out of the conversation so it never reaches * the model, history, or disk. * 2. Stash it on `process.env` under a predictable name so subsequent * Bash / WebFetch tool calls can reference it via `$GITHUB_TOKEN`, * `$ANTHROPIC_API_KEY`, etc. — no chat round-trip needed. * * Conservative pattern set: each entry matches a token format with an * unambiguous prefix + length, so false positives are rare. Anything that * could plausibly be a normal long string (random hex, base64 blobs) is * deliberately not in here. False positives are worse than missed * detections — silently mangling a hex hash a user pasted would be * confusing and there's no recovery path. */ export interface RedactionMatch { label: string; description: string; /** First 4 chars of the secret + ellipsis — for user-facing display. */ preview: string; /** Suggested env var name (e.g. GITHUB_TOKEN). */ envVar: string; /** The actual secret value. INTERNAL USE ONLY — never log this. */ value: string; } export interface RedactionResult { /** Input with each secret replaced by [REDACTED:label]. */ redactedText: string; /** What got redacted. Includes raw `value` for the caller to stash. */ matches: RedactionMatch[]; } /** * Scan `input` for secret patterns and return a redacted copy plus a * description of what was caught. Secrets are replaced with the literal * string `[REDACTED: