From 2d5b4cfac1ea19fd239cd5466873bd18cf61cf36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 27 May 2026 00:05:05 +0200 Subject: [PATCH] fix(observability): extend the redaction denylist with passphrase, otp, recovery, dsn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wide-event `received_shape_excerpt` surface walks every body key through `SENSITIVE_KEY_PATTERNS` before the eventual JSON.stringify. The original set covered passwords, tokens, secrets, API keys, the Authorization header, CSRF state, and nonces. Four additions worth keeping out of observability sinks: - `/passphrase/i` — backup-encryption passphrases (off-host backups use one) and any future user-facing recovery-phrase flows. - `/otp/i` — TOTP / email-OTP one-time codes for future second-factor surfaces. - `/recovery/i` — passkey-recovery codes and account-recovery flows. - `/dsn/i` — Glitchtip / Sentry-style DSNs are `https://@host` shapes where the key is auth material; today they enter the system via admin settings but a future debug surface might quote one back in a body excerpt. Test pins the four new patterns alongside the existing required set, and a positive-redaction case covers all four in one shot. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../__tests__/redact-payload.test.ts | 21 +++++++++++++++++++ src/lib/observability/redact-payload.ts | 9 ++++++++ 2 files changed, 30 insertions(+) diff --git a/src/lib/observability/__tests__/redact-payload.test.ts b/src/lib/observability/__tests__/redact-payload.test.ts index 40db5cc1e..cf9194a5a 100644 --- a/src/lib/observability/__tests__/redact-payload.test.ts +++ b/src/lib/observability/__tests__/redact-payload.test.ts @@ -115,16 +115,37 @@ describe("redactSensitiveFields", () => { // one of the documented patterns. const required = [ "password", + "passphrase", "token", "secret", "apikey", "authorization", "csrfstate", "nonce", + "otp", + "recoveryCode", + "glitchtipDsn", ]; for (const key of required) { const matched = SENSITIVE_KEY_PATTERNS.some((re) => re.test(key)); expect(matched, `pattern set should match ${key}`).toBe(true); } }); + + it("redacts the credential-adjacent additions (passphrase / otp / recovery / dsn)", () => { + const body = { + backupPassphrase: "correct horse battery staple", + emailOtp: "123456", + recoveryCode: "abcd-efgh-ijkl", + glitchtipDsn: "https://abc@glitchtip.example/1", + benignField: "kept", + }; + expect(redactSensitiveFields(body)).toEqual({ + backupPassphrase: "[redacted]", + emailOtp: "[redacted]", + recoveryCode: "[redacted]", + glitchtipDsn: "[redacted]", + benignField: "kept", + }); + }); }); diff --git a/src/lib/observability/redact-payload.ts b/src/lib/observability/redact-payload.ts index 4e8b4967b..420fa177f 100644 --- a/src/lib/observability/redact-payload.ts +++ b/src/lib/observability/redact-payload.ts @@ -26,6 +26,7 @@ export const SENSITIVE_KEY_PATTERNS: readonly RegExp[] = [ /password/i, + /passphrase/i, /token/i, /secret/i, /apikey/i, @@ -34,6 +35,14 @@ export const SENSITIVE_KEY_PATTERNS: readonly RegExp[] = [ /csrfstate/i, /csrf[_-]state/i, /nonce/i, + // Credential-adjacent keys that aren't already covered by the broader + // `token` / `secret` patterns. TOTP / email-OTP one-time codes, + // account-recovery codes, and the observability DSNs that double as + // auth (Glitchtip / Sentry-style `https://@host`) all carry + // material worth keeping out of wide-event excerpts. + /otp/i, + /recovery/i, + /dsn/i, ]; const REDACTED = "[redacted]";