diff --git a/src/lib/observability/__tests__/redact-payload.test.ts b/src/lib/observability/__tests__/redact-payload.test.ts index 713f47897..0f9491efb 100644 --- a/src/lib/observability/__tests__/redact-payload.test.ts +++ b/src/lib/observability/__tests__/redact-payload.test.ts @@ -163,4 +163,18 @@ describe("redactSensitiveFields", () => { benignField: "kept", }); }); + + it("redacts the webhook shared secret (headerValue) and the Live Activity token", () => { + const body = { + headerValue: "super-secret-webhook-key", + liveActivityPushToken: "abc123", + webhookUrl: "https://example.com/hook", + }; + expect(redactSensitiveFields(body)).toEqual({ + headerValue: "[redacted]", + // Covered by the existing /token/i pattern. + liveActivityPushToken: "[redacted]", + webhookUrl: "https://example.com/hook", + }); + }); }); diff --git a/src/lib/observability/redact-payload.ts b/src/lib/observability/redact-payload.ts index 1ee5992e8..6b7a1f4a7 100644 --- a/src/lib/observability/redact-payload.ts +++ b/src/lib/observability/redact-payload.ts @@ -65,6 +65,13 @@ export const SENSITIVE_KEY_PATTERNS: readonly RegExp[] = [ /progesteronetest/i, /contraceptive/i, /cervicalmucus/i, + // v1.17.1 — the webhook shared secret. `headerValue` lives only inside the + // AES-GCM-encrypted `NotificationChannel.config` blob and the GET masks it + // to `hasHeaderValue`, so no current path excerpts it. Defence-in-depth: if + // a future change routes a webhook body through `buildPayloadDiagnostic`, + // the secret redacts instead of landing verbatim. The Live Activity push + // token is already covered by `/token/i`. + /headervalue/i, ]; const REDACTED = "[redacted]";