From 587fd464fb5958f3d732042fe340b4c8b2fd913a Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Sun, 28 Jun 2026 04:36:11 -0700 Subject: [PATCH] fix(observability): redact field summary secrets --- src/selfhost/sentry.ts | 8 +++++++- test/unit/selfhost-sentry.test.ts | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/selfhost/sentry.ts b/src/selfhost/sentry.ts index 9017ce8b62..51bba54aaf 100644 --- a/src/selfhost/sentry.ts +++ b/src/selfhost/sentry.ts @@ -130,12 +130,18 @@ const SUMMARY_SKIP_KEYS = new Set([ function summarizeLogFields(obj: Record): string { return Object.entries(obj) .filter(([k, v]) => !SUMMARY_SKIP_KEYS.has(k) && v !== null) - .map(([k, v]) => `${k}=${typeof v === "object" ? JSON.stringify(v) : String(v)}`) + .map(([k, v]) => `${k}=${summarizeLogFieldValue(k, v)}`) .filter((part) => part.length <= 90) // a long blob (id/body) belongs in the context, not the title .slice(0, 5) // a few salient fields, not a dump .join(", "); } +function summarizeLogFieldValue(key: string, value: unknown): string { + if (SECRET_KEY.test(key)) return "[redacted]"; + if (typeof value !== "object") return String(value); + return JSON.stringify(scrubEvent({ extra: structuredClone(value) }).extra); +} + /** Forward a structured console line to Sentry when it is an ERROR-level log. The engine logs operational * failures (orb_broker_unavailable, gate-check errors, relay drops, …) as JSON strings, often via console.error. * No-op when Sentry is off, the line isn't a JSON object string, or its level isn't error/fatal — routine logs diff --git a/test/unit/selfhost-sentry.test.ts b/test/unit/selfhost-sentry.test.ts index 50bad0517f..1d1eac28e9 100644 --- a/test/unit/selfhost-sentry.test.ts +++ b/test/unit/selfhost-sentry.test.ts @@ -299,6 +299,25 @@ describe("forwardStructuredLogToSentry — central console.log → Sentry error "project=JSONbored/gittensory, closePrecision=0.6, floor=0.8", ); }); + + it("redacts secret-keyed values in field-only error summaries (regression)", async () => { + await initSentry({ SENTRY_DSN: "d" } as unknown as NodeJS.ProcessEnv); + forwardStructuredLogToSentry( + JSON.stringify({ + level: "error", + event: "field_only_secret", + apiKey: "sensitive-value", + password: "sensitive-value", + project: "demo", + details: { authToken: "nested-sensitive", count: 2 }, + }), + ); + + expect(lastCapturedError().name).toBe("field_only_secret"); + expect(lastCapturedError().message).toBe( + 'apiKey=[redacted], password=[redacted], project=demo, details={"authToken":"[redacted]","count":2}', + ); + }); }); describe("installStructuredLogForwarding — central console sink instrumentation (#1468)", () => {