Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -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.
22 changes: 22 additions & 0 deletions packages/cli-kit/src/private/node/api/headers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli-kit/src/private/node/api/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function sanitizedHeadersOutput(headers: Record<string, string>): string
const sanitized: Record<string, string> = {}
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]!
}
})
Expand Down
Loading