diff --git a/src/cliproxy/auth/__tests__/oauth-trace-redactor.test.ts b/src/cliproxy/auth/__tests__/oauth-trace-redactor.test.ts index beec9578..6cec16db 100644 --- a/src/cliproxy/auth/__tests__/oauth-trace-redactor.test.ts +++ b/src/cliproxy/auth/__tests__/oauth-trace-redactor.test.ts @@ -51,6 +51,25 @@ describe('redactString', () => { test('empty input passthrough', () => { expect(redactString('')).toBe(''); }); + + test('redacts JSON-style token payloads in plain strings', () => { + const out = redactString('{"access_token":"AT_SECRET","refresh_token":"RT_SECRET"}'); + expect(out).not.toContain('AT_SECRET'); + expect(out).not.toContain('RT_SECRET'); + expect(out).toContain(REDACTED_PLACEHOLDER); + }); + + test('redacts line-leading key/value formats', () => { + const out = redactString( + 'access_token=AT_SECRET refresh_token=RT_SECRET client_secret: CS_SECRET' + ); + expect(out).not.toContain('AT_SECRET'); + expect(out).not.toContain('RT_SECRET'); + 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}`); + }); }); describe('redactUrl', () => { diff --git a/src/cliproxy/auth/oauth-trace/redactor.ts b/src/cliproxy/auth/oauth-trace/redactor.ts index 328d9744..7aee3bea 100644 --- a/src/cliproxy/auth/oauth-trace/redactor.ts +++ b/src/cliproxy/auth/oauth-trace/redactor.ts @@ -40,12 +40,31 @@ 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 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}`, + 'gi' +); +const QUOTED_JSON_KV_REGEX = new RegExp( + String.raw`([\"'])${SENSITIVE_KEY_GROUP}\1\s*:\s*([\"'])[^\\]*?\3`, + 'gi' +); + /** Redact sensitive query-param values inside any string. Idempotent. */ export function redactString(s: string): string { if (!s) return s; return s .replace(QUERY_PARAM_REGEX, (_full, key) => `${key}=${REDACTED}`) - .replace(BEARER_REGEX, `Bearer ${REDACTED}`); + .replace(BEARER_REGEX, `Bearer ${REDACTED}`) + .replace( + QUOTED_JSON_KV_REGEX, + (_full, quoteKey, key, quoteVal) => + `${quoteKey}${key}${quoteKey}:${quoteVal}${REDACTED}${quoteVal}` + ) + .replace(LEADING_KV_REGEX, (_full, prefix, key) => `${prefix}${key}=${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 4493aeb6..39841eb6 100644 --- a/src/cliproxy/quota/quota-manager.ts +++ b/src/cliproxy/quota/quota-manager.ts @@ -524,9 +524,8 @@ 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;