diff --git a/packages/adapter-utils/src/command-redaction.test.ts b/packages/adapter-utils/src/command-redaction.test.ts index b58d37aad8..7be371ff22 100644 --- a/packages/adapter-utils/src/command-redaction.test.ts +++ b/packages/adapter-utils/src/command-redaction.test.ts @@ -342,6 +342,27 @@ describe("redactCommandText header secrets", () => { } }); + it("consumes an escaped-space continuation at every serialization depth", () => { + // A shell escape pair doubles its backslash with each serialization + // layer; the continuation reads the whole run as one pair. + const bases = [ + 'curl -H X-API-Key:"SECRET"\\ TAIL https://example.test', + 'curl -H X-API-Key:\\ SECRET https://example.test', + 'curl -H "X-API-Key: SECRET"\\ TAIL;echo safe', + ]; + for (const base of bases) { + let text = base; + for (let depth = 0; depth <= 2; depth += 1) { + if (depth > 0) text = JSON.stringify(text); + const output = redactCommandText(text); + expect(output).not.toContain("SECRET"); + expect(output).not.toContain("TAIL"); + if (depth > 0) expect(() => JSON.parse(output)).not.toThrow(); + expect(redactCommandText(output)).toBe(output); + } + } + }); + it("redacts a bare apikey header value", () => { // Supabase sends the key under an unhyphenated `apikey` header. expect(redactCommandText("apikey: abc")).toBe( diff --git a/packages/adapter-utils/src/command-redaction.ts b/packages/adapter-utils/src/command-redaction.ts index 0223179264..3a534525e0 100644 --- a/packages/adapter-utils/src/command-redaction.ts +++ b/packages/adapter-utils/src/command-redaction.ts @@ -135,13 +135,13 @@ const COMMAND_SHELL_QUOTED_SEGMENT_PATTERNS = [ String.raw`'[^'\r\n]*'`, String.raw`\$'(?:\\.|[^'\\\r\n])*'`, ] as const; -const COMMAND_SHELL_ESCAPE_PAIR_PATTERN = String.raw`\\[^\r\n]`; +const COMMAND_SHELL_ESCAPE_PAIR_PATTERN = String.raw`\\+[^\r\n]`; // An opening escape pair carries the first byte of an unquoted value, as in -// `X-API-Key:\ abc`. It excludes the escaped quote, so a `\"` opener falls to -// the escaped branches. A deeper run such as `\\\"` opens with an escaped -// backslash, which this pattern does accept; the escaped branches precede the -// unquoted one in the alternation and take that value first. -const COMMAND_SHELL_OPENING_ESCAPE_PAIR_PATTERN = String.raw`\\[^"\r\n]`; +// `X-API-Key:\ abc`. The backslash run may be longer inside a serialized +// command, where each layer doubles it. A run followed by a quote is excluded, +// so a `\"` opener at any depth falls to the escaped branches, which precede +// the unquoted one in the alternation. +const COMMAND_SHELL_OPENING_ESCAPE_PAIR_PATTERN = String.raw`\\+[^"\r\n]`; // The first segment of an unquoted value is a raw token, bounded only by // whitespace, a quote, a backtick, or a backslash. A raw HTTP diagnostic // carries an opaque credential the same way, so a `;`, `|`, or `&` inside it