fix(adapter-utils): consume escape pairs inside a quoted header value
A double-quoted header value stopped at the first backslash, so a credential with an embedded escaped quote such as `"X-API-Key: abc\"def"` kept its tail in the recorded text. The double-quoted branch now consumes escape pairs and requires an unescaped opening quote, which keeps it off a serialized diagnostic where `\"` is the JSON escape. The single-quoted branch takes a backslash literally. Only the unquoted branch still stops at a backslash. Claude-Session: https://claude.ai/code/session_01RYigf3eMFJjey9iKRApPGE
This commit is contained in:
parent
3399fc784f
commit
d273feaa7f
|
|
@ -276,6 +276,35 @@ describe("redactCommandText header secrets", () => {
|
|||
expect(redactDiagnosticText(once)).toBe(once);
|
||||
});
|
||||
|
||||
it("redacts past an escaped quote inside a double-quoted header value", () => {
|
||||
// The shell escape does not end the argument, so the value runs on past it.
|
||||
const input = String.raw`curl -H "X-API-Key: abc\"def" https://example.test`;
|
||||
const output = redactCommandText(input);
|
||||
expect(output).not.toContain("def");
|
||||
expect(output).toBe(
|
||||
`curl -H "X-API-Key: ${REDACTED_COMMAND_TEXT_VALUE}" https://example.test`,
|
||||
);
|
||||
});
|
||||
|
||||
it("redacts a backslash inside a single-quoted header value", () => {
|
||||
// A shell single quote has no escapes, so the backslash is part of the value.
|
||||
const input = String.raw`curl -H 'X-API-Key: abc\def' https://example.test`;
|
||||
const output = redactCommandText(input);
|
||||
expect(output).not.toContain("abc");
|
||||
expect(output).toBe(
|
||||
`curl -H 'X-API-Key: ${REDACTED_COMMAND_TEXT_VALUE}' https://example.test`,
|
||||
);
|
||||
});
|
||||
|
||||
it("redacts a double-quoted value that is itself an escaped quoted string", () => {
|
||||
const input = String.raw`curl -H "Authorization: \"Bearer nested\"" https://example.test`;
|
||||
const output = redactCommandText(input);
|
||||
expect(output).not.toContain("nested");
|
||||
expect(output).toBe(
|
||||
`curl -H "Authorization: ${REDACTED_COMMAND_TEXT_VALUE}" https://example.test`,
|
||||
);
|
||||
});
|
||||
|
||||
it("redacts a header secret inside a diagnostic and keeps a JSON secret field working", () => {
|
||||
const input = 'command failed: curl -H "X-API-Key: abc" -> {"token":"opaque-value"}';
|
||||
const output = redactDiagnosticText(input);
|
||||
|
|
|
|||
|
|
@ -45,11 +45,17 @@ const COMMAND_AUTHORIZATION_BEARER_RE =
|
|||
// rule agree byte for byte with the bearer rule above, so
|
||||
// `Authorization: Bearer <value>` produces the same output as before.
|
||||
//
|
||||
// Every value branch excludes the backslash. That keeps the rule off an
|
||||
// escaped-quote opener such as `Authorization: \"Bearer ...\"` in a serialized
|
||||
// diagnostic, which the caller's own authorization rules already redact. A
|
||||
// quoted value must open with a non-blank character, so an empty header
|
||||
// argument such as `-H "X-API-Key: "` stays as it is.
|
||||
// Each branch treats the backslash the way its quoting context does. A
|
||||
// double-quoted value consumes escape pairs, so an escaped quote inside the
|
||||
// argument (`"X-API-Key: abc\"def"`) does not end the value early. Its opening
|
||||
// quote must itself be unescaped, which keeps the branch off a serialized
|
||||
// diagnostic such as `\"X-API-Key: ...\"`, where the closing `\"` must survive.
|
||||
// A single-quoted value takes a backslash literally, because a shell single
|
||||
// quote has no escapes. Only the unquoted branch stops at a backslash, so an
|
||||
// escaped-quote opener such as `Authorization: \"Bearer ...\"` is left to the
|
||||
// caller's own authorization rules. A quoted value must open with a non-blank
|
||||
// character, so an empty header argument such as `-H "X-API-Key: "` stays as it
|
||||
// is.
|
||||
//
|
||||
// The schemes come from the IANA HTTP Authentication Scheme Registry, plus
|
||||
// `AWS4-HMAC-SHA256` and `Token`, which are widely used but unregistered. A
|
||||
|
|
@ -89,8 +95,8 @@ const COMMAND_SECRET_HEADER_UNQUOTED_VALUE_PATTERN =
|
|||
"`" +
|
||||
String.raw`]+)`;
|
||||
const COMMAND_SECRET_HEADER_RE = new RegExp(
|
||||
String.raw`("${COMMAND_SECRET_HEADER_PREFIX_PATTERN})[^\s"\\][^"\\\r\n]*(")` +
|
||||
String.raw`|('${COMMAND_SECRET_HEADER_PREFIX_PATTERN})[^\s'\\][^'\\\r\n]*(')` +
|
||||
String.raw`(?<!\\)("${COMMAND_SECRET_HEADER_PREFIX_PATTERN})(?:\\.|[^\s"\\])(?:\\.|[^"\\\r\n])*(")` +
|
||||
String.raw`|('${COMMAND_SECRET_HEADER_PREFIX_PATTERN})[^\s'][^'\r\n]*(')` +
|
||||
String.raw`|(\b${COMMAND_SECRET_HEADER_PREFIX_PATTERN})${COMMAND_SECRET_HEADER_UNQUOTED_VALUE_PATTERN}`,
|
||||
"gi",
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue