From 7c82911a36b5a2352c630a7496db75104b74a1c1 Mon Sep 17 00:00:00 2001 From: gonzaloriestra <14979109+gonzaloriestra@users.noreply.github.com> Date: Mon, 18 May 2026 01:01:07 +0000 Subject: [PATCH] [Security] Fix locale-sensitive header redaction Replaced toLocaleLowerCase() with toLowerCase() in sanitizedHeadersOutput to ensure consistent redaction of sensitive headers regardless of the system locale. Added a regression test simulating the Turkish locale issue. --- .jules/sentinel.md | 6 +++++ .../src/private/node/api/headers.test.ts | 22 +++++++++++++++++++ .../cli-kit/src/private/node/api/headers.ts | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000000..08e66023835 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,6 @@ +## Sentinel's Journal + +## 2026-05-18 - Locale-sensitive header redaction +**Vulnerability:** Redaction of sensitive headers (like AUTHORIZATION) could be bypassed on systems with certain locales (e.g., Turkish) because `toLocaleLowerCase()` is non-deterministic for ASCII characters like 'I' (which becomes 'ı' in Turkish). +**Learning:** Standard string methods like `toLocaleLowerCase()` can introduce security bypasses when used for protocol-level or security-sensitive key matching if the environment's locale differs from the expected ASCII behavior. +**Prevention:** Always use locale-independent methods like `toLowerCase()` (or `toUpperCase()`) for matching keys, headers, or parameters that follow ASCII or fixed-format standards. diff --git a/packages/cli-kit/src/private/node/api/headers.test.ts b/packages/cli-kit/src/private/node/api/headers.test.ts index 13441e6a5a0..e6982b5a065 100644 --- a/packages/cli-kit/src/private/node/api/headers.test.ts +++ b/packages/cli-kit/src/private/node/api/headers.test.ts @@ -97,6 +97,28 @@ describe('common API methods', () => { - Content-Type: application/json" `) }) + + test('sanitizedHeadersOutput redacts AUTHORIZATION even when toLocaleLowerCase returns dotless i (Turkish locale simulation)', () => { + // Given + const headers = { + AUTHORIZATION: 'secret-token', + } + + // Simulate Turkish locale where 'I' becomes 'ı' (dotless i) + // 'AUTHORIZATION'.toLocaleLowerCase() -> 'authorızatıon' + const toLocaleLowerCaseSpy = vi.spyOn(String.prototype, 'toLocaleLowerCase').mockReturnValue('authorızatıon') + + try { + // When + const got = sanitizedHeadersOutput(headers) + + // Then + expect(got).not.toContain('AUTHORIZATION: secret-token') + expect(got).not.toContain('secret-token') + } finally { + toLocaleLowerCaseSpy.mockRestore() + } + }) }) describe('GraphQLClientError', () => { diff --git a/packages/cli-kit/src/private/node/api/headers.ts b/packages/cli-kit/src/private/node/api/headers.ts index 691505dc9e8..9dcaf8757ff 100644 --- a/packages/cli-kit/src/private/node/api/headers.ts +++ b/packages/cli-kit/src/private/node/api/headers.ts @@ -35,7 +35,7 @@ export function sanitizedHeadersOutput(headers: Record): string const sanitized: Record = {} const keywords = ['token', 'authorization', 'subject_token'] Object.keys(headers).forEach((header) => { - if (keywords.find((keyword) => header.toLocaleLowerCase().includes(keyword)) === undefined) { + if (keywords.find((keyword) => header.toLowerCase().includes(keyword)) === undefined) { sanitized[header] = headers[header]! } })