fix: redact secrets nested inside metadata in servers/show
The redaction checked only top-level keys. That was sufficient while values were strings — a secret could not hide beneath one — but #1910 lets a value be an object, so `{ trace: { accessToken: "…" } }` printed in full: `trace` is not sensitive, so the whole subtree went out with it. `servers/show` output is meant to be safe to paste into an issue, so this is a regression this PR introduced rather than a pre-existing gap. `redactMetadataValue` walks the payload: a key matching `isSensitiveHeader` replaces its entire value (so a sensitive key holding an object cannot leak through its members either), anything else is descended into, and arrays are mapped elementwise since objects inside them can carry sensitive keys. Recursion is bounded — the payload came from `JSON.parse`, so it is a finite tree with no cycles. Tests cover a secret two levels under a non-sensitive key and one inside an array element. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
f3878f94a9
commit
85e4a29990
1 changed files with 54 additions and 10 deletions
|
|
@ -226,6 +226,15 @@ describe("showServerEntry / servers/show", () => {
|
|||
// the whole value, not walk into it (#1910).
|
||||
"X-Api-Key": { primary: "sk-live-1", fallback: "sk-live-2" },
|
||||
nested: { keep: true },
|
||||
// A secret buried under a non-sensitive key. A top-level-only check
|
||||
// would print `accessToken` in full.
|
||||
trace: {
|
||||
id: "t-1",
|
||||
accessToken: "sk-live-3",
|
||||
deeper: { refresh_token: "sk-live-4", ok: 1 },
|
||||
},
|
||||
// Objects inside an array are reached too.
|
||||
attempts: [{ password: "hunter2" }, { attempt: 2 }],
|
||||
},
|
||||
env: [
|
||||
{ key: "TOKEN", value: "secret" },
|
||||
|
|
@ -250,8 +259,15 @@ describe("showServerEntry / servers/show", () => {
|
|||
Authorization: "[redacted]",
|
||||
"X-Custom": "ok",
|
||||
"X-Api-Key": "[redacted]",
|
||||
// A structured value under a non-sensitive key survives intact.
|
||||
// A structured value under a non-sensitive key survives intact...
|
||||
nested: { keep: true },
|
||||
// ...but a sensitive key nested inside one does not.
|
||||
trace: {
|
||||
id: "t-1",
|
||||
accessToken: "[redacted]",
|
||||
deeper: { refresh_token: "[redacted]", ok: 1 },
|
||||
},
|
||||
attempts: [{ password: "[redacted]" }, { attempt: 2 }],
|
||||
});
|
||||
expect(sanitized.env).toEqual([
|
||||
{ key: "TOKEN", value: "[redacted]" },
|
||||
|
|
|
|||
Loading…
Reference in a new issue