diff --git a/src/logging/diagnostic-support-redaction.ts b/src/logging/diagnostic-support-redaction.ts index caa926f8c7e..60bb7926e69 100644 --- a/src/logging/diagnostic-support-redaction.ts +++ b/src/logging/diagnostic-support-redaction.ts @@ -20,7 +20,8 @@ const COOKIE_HEADER_RE = /\b(Cookie|Set-Cookie)\s*:\s*[^\r\n]+/giu; const AWS_ACCESS_KEY_ID_RE = /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu; const JWT_RE = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/gu; const URL_USERINFO_RE = /\b([a-z][a-z0-9+.-]*:\/\/)([^/@\s:?#]+)(?::([^/@\s?#]+))?@/giu; -const URL_PARAM_RE = /([?&])([^=&\s]+)=([^&#\s]+)/giu; +const URL_SENSITIVE_PARAM_RE = + /([?&])(api[-_]?key|apikey|access[-_]?token|auth[-_]?token|client[-_]?secret|hook[-_]?token|refresh[-_]?token|token|key|secret|password|pass|passwd|auth|signature)=([^&#\s)"'<>]+)(?:&[^#\s)"'<>]*)?/giu; const EMAIL_RE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu; const MATRIX_USER_ID_RE = /@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+/gu; const MATRIX_ROOM_ID_RE = /![A-Za-z0-9._=-]+:[A-Za-z0-9.-]+/gu; @@ -306,7 +307,7 @@ function redactUrlSecretsForSupport(value: string): string { .replace(URL_USERINFO_RE, (_match, scheme: string, _username: string, password?: string) => password ? `${scheme}:@` : `${scheme}@`, ) - .replace(URL_PARAM_RE, (match, prefix: string, key: string) => + .replace(URL_SENSITIVE_PARAM_RE, (match, prefix: string, key: string) => isSensitiveUrlQueryParamName(key) ? `${prefix}${key}=` : match, ); } diff --git a/src/logging/redact.test.ts b/src/logging/redact.test.ts index 355b6afc54b..0d50c1a1442 100644 --- a/src/logging/redact.test.ts +++ b/src/logging/redact.test.ts @@ -61,6 +61,24 @@ describe("redactSensitiveText", () => { expect(output).toBe("gog gmail watch serve --hook-token abcdef…ghij"); }); + it("masks sensitive URL query parameters", () => { + const input = "connect https://user.example/sync?access_token=abcdef1234567890ghij&safe=value"; + const output = redactSensitiveText(input, { + mode: "tools", + patterns: defaults, + }); + expect(output).toBe("connect https://user.example/sync?access_token=abcdef…ghij&safe=value"); + }); + + it("masks short URL query tokens fully", () => { + const input = "cdp=https://browserless.example.com/?token=supersecret123"; + const output = redactSensitiveText(input, { + mode: "tools", + patterns: defaults, + }); + expect(output).toBe("cdp=https://browserless.example.com/?token=***"); + }); + it("masks JSON fields", () => { const input = '{"token":"abcdef1234567890ghij"}'; const output = redactSensitiveText(input, { diff --git a/src/logging/redact.ts b/src/logging/redact.ts index e377f26161f..cc13c2e2c9e 100644 --- a/src/logging/redact.ts +++ b/src/logging/redact.ts @@ -18,6 +18,8 @@ const DEFAULT_REDACT_PATTERNS: string[] = [ String.raw`"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"`, // CLI flags. String.raw`--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1`, + // URL query credentials. + String.raw`/([?&](?:api[-_]?key|apikey|access[-_]?token|auth[-_]?token|client[-_]?secret|hook[-_]?token|refresh[-_]?token|token|key|secret|password|pass|passwd|auth|signature)=)([^&#\s)"'<>]+)/giu`, // Authorization headers. String.raw`Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)`, String.raw`\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b`,