Files
HealthLog/src/lib/logging/redact.ts
T
Marc-André BombeckandClaude Opus 4.7 e903d9a2c2 fix(logging): scrub OpenAI / Anthropic API keys in redactSecrets
P11. Adds a `sk-(ant-)?[A-Za-z0-9_-]+` rule so any sk- / sk-ant- token that
finds its way into an error message or stack trace gets redacted before
it reaches Loki or Glitchtip. We never log these on purpose, but a
mis-typed config or a dumped request body could carry one — defense in
depth on the egress path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 02:00:01 +02:00

40 lines
1.7 KiB
TypeScript

/**
* Redact secrets from strings before they leave the process boundary
* (Loki, Glitchtip, console). Applied in `WideEventBuilder.setError()`
* and `reportToGlitchtip()` so every error reaching observability is
* scrubbed once, centrally.
*
* Patterns:
* - `Bearer <token>` — generic API tokens incl. our `hlk_` /
* `hlr_` formats and external OAuth bearers (Withings, AI providers).
* - `bot<digits>:<token>` — Telegram bot URLs embed the token in the
* path: `https://api.telegram.org/bot1234567:ABC-…/sendMessage`.
* If `fetch()` rejects with a `cause` that surfaces the URL (some
* Node runtimes), the token would land in error reports.
* - `?secret=…` and `?code=…` — query-string leaks for legacy
* Withings webhooks and OAuth callbacks (already scrubbed at the
* URL level in `reportToGlitchtip`, but error.message can carry
* the URL too).
* - `sk-…` and `sk-ant-…` — OpenAI / Anthropic API keys. We never
* log them on purpose, but a misconfigured client error or a
* dump of the request body could carry one. Scrub before egress.
*
* The substitution is intentionally generic ([REDACTED]) — we don't
* want partial revelation of token entropy.
*/
export function redactSecrets(input: string): string {
return input
.replace(/Bearer\s+\S+/gi, "Bearer [REDACTED]")
.replace(/bot\d+:[A-Za-z0-9_-]+/g, "bot[REDACTED]")
.replace(/sk-(?:ant-)?[A-Za-z0-9_-]+/g, "[REDACTED]")
.replace(
/([?&])(secret|code|token|api[_-]?key)=[^&\s]+/gi,
"$1$2=[REDACTED]",
);
}
/** Apply `redactSecrets` to a possibly-undefined string. */
export function redactOptional(s: string | undefined): string | undefined {
return s === undefined ? undefined : redactSecrets(s);
}