From 2a9745c9a1ea4459eb5d2442e0072a9492432674 Mon Sep 17 00:00:00 2001 From: Marcus Castro Date: Sun, 8 Feb 2026 13:03:54 -0300 Subject: [PATCH] fix(config): redact resolved field in config snapshots The newly added 'resolved' field contains secrets after ${ENV} substitution. This commit ensures redactConfigSnapshot also redacts the resolved field to prevent credential leaks in config.get responses. --- src/config/redact-snapshot.test.ts | 12 ++++++++++++ src/config/redact-snapshot.ts | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/config/redact-snapshot.test.ts b/src/config/redact-snapshot.test.ts index 56774f2cd..4590272a4 100644 --- a/src/config/redact-snapshot.test.ts +++ b/src/config/redact-snapshot.test.ts @@ -12,6 +12,7 @@ function makeSnapshot(config: Record, raw?: string): ConfigFile exists: true, raw: raw ?? JSON.stringify(config), parsed: config, + resolved: config as ConfigFileSnapshot["resolved"], valid: true, config: config as ConfigFileSnapshot["config"], hash: "abc123", @@ -188,12 +189,23 @@ describe("redactConfigSnapshot", () => { expect(parsed.channels.discord.token).toBe(REDACTED_SENTINEL); }); + it("redacts resolved object as well", () => { + const config = { + gateway: { auth: { token: "supersecrettoken123456" } }, + }; + const snapshot = makeSnapshot(config); + const result = redactConfigSnapshot(snapshot); + const resolved = result.resolved as Record>>; + expect(resolved.gateway.auth.token).toBe(REDACTED_SENTINEL); + }); + it("handles null raw gracefully", () => { const snapshot: ConfigFileSnapshot = { path: "/test", exists: false, raw: null, parsed: null, + resolved: {} as ConfigFileSnapshot["resolved"], valid: false, config: {} as ConfigFileSnapshot["config"], issues: [], diff --git a/src/config/redact-snapshot.ts b/src/config/redact-snapshot.ts index a40ac3950..378f6ec0c 100644 --- a/src/config/redact-snapshot.ts +++ b/src/config/redact-snapshot.ts @@ -137,12 +137,15 @@ export function redactConfigSnapshot(snapshot: ConfigFileSnapshot): ConfigFileSn const redactedConfig = redactConfigObject(snapshot.config); const redactedRaw = snapshot.raw ? redactRawText(snapshot.raw, snapshot.config) : null; const redactedParsed = snapshot.parsed ? redactConfigObject(snapshot.parsed) : snapshot.parsed; + // Also redact the resolved config (contains values after ${ENV} substitution) + const redactedResolved = redactConfigObject(snapshot.resolved); return { ...snapshot, config: redactedConfig, raw: redactedRaw, parsed: redactedParsed, + resolved: redactedResolved, }; }