From 1cd5dea530d7c1362cba5218a05bb5392c456816 Mon Sep 17 00:00:00 2001 From: gonzaloriestra <14979109+gonzaloriestra@users.noreply.github.com> Date: Wed, 3 Jun 2026 00:34:41 +0000 Subject: [PATCH] [Security] Harden URL sanitization Hardens the URL sanitization logic to redact Basic Auth credentials (username and password) and expands the sensitive parameter list. The matching is now case-insensitive. --- .../cli-kit/src/private/node/api/urls.test.ts | 49 +++++++++++++++++++ packages/cli-kit/src/private/node/api/urls.ts | 21 ++++++-- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/packages/cli-kit/src/private/node/api/urls.test.ts b/packages/cli-kit/src/private/node/api/urls.test.ts index 62492fb2eb7..9967497a7b7 100644 --- a/packages/cli-kit/src/private/node/api/urls.test.ts +++ b/packages/cli-kit/src/private/node/api/urls.test.ts @@ -56,6 +56,11 @@ describe('sanitizeURL', () => { 'client_secret', 'code', 'token', + 'api_key', + 'secret', + 'password', + 'sig', + 'signature', ])('sanitizes %s query parameter', (param) => { // Given const url = `https://example.com?${param}=secret-value` @@ -79,4 +84,48 @@ describe('sanitizeURL', () => { 'https://example.com/?access_token=****&refresh_token=****&device_code=****&subject_token=****&other=keep', ) }) + + test('sanitizes query parameters case-insensitively', () => { + // Given + const url = 'https://example.com?TOKEN=abc123&Access_Token=def456' + + // When + const sanitizedUrl = sanitizeURL(url) + + // Then + expect(sanitizedUrl).toBe('https://example.com/?TOKEN=****&Access_Token=****') + }) + + test('sanitizes username and password in the URL', () => { + // Given + const url = 'https://user:pass@example.com/path?token=abc123' + + // When + const sanitizedUrl = sanitizeURL(url) + + // Then + expect(sanitizedUrl).toBe('https://****:****@example.com/path?token=****') + }) + + test('sanitizes only password in the URL', () => { + // Given + const url = 'https://:pass@example.com/path' + + // When + const sanitizedUrl = sanitizeURL(url) + + // Then + expect(sanitizedUrl).toBe('https://:****@example.com/path') + }) + + test('sanitizes only username in the URL', () => { + // Given + const url = 'https://user@example.com/path' + + // When + const sanitizedUrl = sanitizeURL(url) + + // Then + expect(sanitizedUrl).toBe('https://****@example.com/path') + }) }) diff --git a/packages/cli-kit/src/private/node/api/urls.ts b/packages/cli-kit/src/private/node/api/urls.ts index fd1297f7c58..b026967ac3b 100644 --- a/packages/cli-kit/src/private/node/api/urls.ts +++ b/packages/cli-kit/src/private/node/api/urls.ts @@ -12,6 +12,11 @@ const SENSITIVE_QUERY_PARAMS = [ 'client_secret', 'code', 'token', + 'api_key', + 'secret', + 'password', + 'sig', + 'signature', ] /** @@ -21,10 +26,20 @@ const SENSITIVE_QUERY_PARAMS = [ */ export function sanitizeURL(url: string): string { const parsedUrl = new URL(url) - for (const param of SENSITIVE_QUERY_PARAMS) { - if (parsedUrl.searchParams.has(param)) { - parsedUrl.searchParams.set(param, '****') + + if (parsedUrl.username) { + parsedUrl.username = '****' + } + if (parsedUrl.password) { + parsedUrl.password = '****' + } + + const keys = Array.from(parsedUrl.searchParams.keys()) + for (const key of keys) { + if (SENSITIVE_QUERY_PARAMS.includes(key.toLowerCase())) { + parsedUrl.searchParams.set(key, '****') } } + return parsedUrl.toString() }