diff --git a/src/cliproxy/auth/__tests__/oauth-trace-redactor.test.ts b/src/cliproxy/auth/__tests__/oauth-trace-redactor.test.ts index 6cec16db..8b48261c 100644 --- a/src/cliproxy/auth/__tests__/oauth-trace-redactor.test.ts +++ b/src/cliproxy/auth/__tests__/oauth-trace-redactor.test.ts @@ -53,12 +53,21 @@ describe('redactString', () => { }); test('redacts JSON-style token payloads in plain strings', () => { - const out = redactString('{"access_token":"AT_SECRET","refresh_token":"RT_SECRET"}'); + const out = redactString( + '{"access_token":"AT_SECRET","refresh_token":"RT_SECRET","token":"TOKEN_SECRET"}' + ); expect(out).not.toContain('AT_SECRET'); expect(out).not.toContain('RT_SECRET'); + expect(out).not.toContain('TOKEN_SECRET'); expect(out).toContain(REDACTED_PLACEHOLDER); }); + test('preserves JSON-style separator spacing while redacting token payloads', () => { + const out = redactString('{"access_token" : "AT_SECRET"}'); + expect(out).not.toContain('AT_SECRET'); + expect(out).toContain(`"access_token" : "${REDACTED_PLACEHOLDER}"`); + }); + test('redacts line-leading key/value formats', () => { const out = redactString( 'access_token=AT_SECRET refresh_token=RT_SECRET client_secret: CS_SECRET' @@ -68,7 +77,13 @@ describe('redactString', () => { expect(out).not.toContain('CS_SECRET'); expect(out).toContain(`access_token=${REDACTED_PLACEHOLDER}`); expect(out).toContain(`refresh_token=${REDACTED_PLACEHOLDER}`); - expect(out).toContain(`client_secret=${REDACTED_PLACEHOLDER}`); + expect(out).toContain(`client_secret: ${REDACTED_PLACEHOLDER}`); + }); + + test('preserves URL-style suffixes in line-leading key/value snippets', () => { + const out = redactString('access_token=AT_SECRET&keep=1'); + expect(out).not.toContain('AT_SECRET'); + expect(out).toBe(`access_token=${REDACTED_PLACEHOLDER}&keep=1`); }); }); diff --git a/src/cliproxy/auth/oauth-trace/redactor.ts b/src/cliproxy/auth/oauth-trace/redactor.ts index 7aee3bea..62c74902 100644 --- a/src/cliproxy/auth/oauth-trace/redactor.ts +++ b/src/cliproxy/auth/oauth-trace/redactor.ts @@ -41,15 +41,15 @@ const QUERY_PARAM_REGEX = new RegExp( const BEARER_REGEX = /Bearer\s+[A-Za-z0-9._\-~+/=]+/gi; const KV_SEPARATORS = String.raw`(?:=|:)`; -const VALUE_FRAGMENT = String.raw`[^\s,;\]}\"']+`; -const STRING_KV_KEYS = SENSITIVE_QUERY_KEYS.filter((key) => key !== 'authorization'); +const VALUE_FRAGMENT = String.raw`[^\s,;&\]}\"']+`; +const STRING_KV_KEYS = [...SENSITIVE_QUERY_KEYS, 'token'].filter((key) => key !== 'authorization'); const SENSITIVE_KEY_GROUP = `(${STRING_KV_KEYS.join('|')})`; const LEADING_KV_REGEX = new RegExp( - String.raw`(^|\s)${SENSITIVE_KEY_GROUP}\s*${KV_SEPARATORS}\s*${VALUE_FRAGMENT}`, + String.raw`(^|\s)${SENSITIVE_KEY_GROUP}(\s*${KV_SEPARATORS}\s*)${VALUE_FRAGMENT}`, 'gi' ); const QUOTED_JSON_KV_REGEX = new RegExp( - String.raw`([\"'])${SENSITIVE_KEY_GROUP}\1\s*:\s*([\"'])[^\\]*?\3`, + String.raw`([\"'])${SENSITIVE_KEY_GROUP}\1(\s*:\s*)([\"'])(?:\\.|(?!\4).)*?\4`, 'gi' ); @@ -61,10 +61,13 @@ export function redactString(s: string): string { .replace(BEARER_REGEX, `Bearer ${REDACTED}`) .replace( QUOTED_JSON_KV_REGEX, - (_full, quoteKey, key, quoteVal) => - `${quoteKey}${key}${quoteKey}:${quoteVal}${REDACTED}${quoteVal}` + (_full, quoteKey, key, separator, quoteVal) => + `${quoteKey}${key}${quoteKey}${separator}${quoteVal}${REDACTED}${quoteVal}` ) - .replace(LEADING_KV_REGEX, (_full, prefix, key) => `${prefix}${key}=${REDACTED}`); + .replace( + LEADING_KV_REGEX, + (_full, prefix, key, separator) => `${prefix}${key}${separator}${REDACTED}` + ); } /** Redact a parsed URL by name; returns redacted href or original on parse error. */ diff --git a/src/cliproxy/quota/quota-manager.ts b/src/cliproxy/quota/quota-manager.ts index 39841eb6..4493aeb6 100644 --- a/src/cliproxy/quota/quota-manager.ts +++ b/src/cliproxy/quota/quota-manager.ts @@ -524,8 +524,9 @@ export async function preflightCheck(provider: CLIProxyProvider): Promise * Fix image analysis configuration issues */ export async function fixImageAnalysisConfig(): Promise { - const { updateConfig, loadOrCreateUnifiedConfig } = - await import('../../config/config-loader-facade'); + const { updateConfig, loadOrCreateUnifiedConfig } = await import( + '../../config/config-loader-facade' + ); const config = loadOrCreateUnifiedConfig(); let fixed = false;