diff --git a/src/cliproxy/ai-providers/__tests__/codex-reasoning-proxy-extended-context.test.ts b/src/cliproxy/ai-providers/__tests__/codex-reasoning-proxy-extended-context.test.ts index 9b156985..78e9056a 100644 --- a/src/cliproxy/ai-providers/__tests__/codex-reasoning-proxy-extended-context.test.ts +++ b/src/cliproxy/ai-providers/__tests__/codex-reasoning-proxy-extended-context.test.ts @@ -296,6 +296,39 @@ describe('CodexReasoningProxy extended-context compatibility', () => { expect((capturedBody?.reasoning as JsonRecord | undefined)?.effort).toBeUndefined(); }); + it('rejects oversized non-2xx upstream error bodies', async () => { + const upstream = http.createServer((_req, res) => { + res.writeHead(500, { 'Content-Type': 'application/json' }); + const chunk = 'x'.repeat(1024 * 1024); + for (let i = 0; i < 11; i += 1) { + res.write(chunk); + } + res.end(); + }); + cleanupServers.push(upstream); + + const upstreamPort = await listenOnRandomPort(upstream); + const proxy = new CodexReasoningProxy({ + upstreamBaseUrl: `http://127.0.0.1:${upstreamPort}`, + modelMap: { defaultModel: 'gpt-5.4' }, + defaultEffort: 'medium', + }); + + const proxyPort = await proxy.start(); + const response = await postJson( + `http://127.0.0.1:${proxyPort}/api/provider/codex/v1/messages`, + { + model: 'gpt-5.4', + messages: [], + } + ); + + proxy.stop(); + + expect(response.statusCode).toBe(502); + expect(response.body.error).toContain('Upstream error response exceeded 10MB limit'); + }); + it('keeps fast service tier when disableEffort is enabled', async () => { let capturedBody: JsonRecord | null = null; diff --git a/src/cliproxy/ai-providers/codex-reasoning-proxy.ts b/src/cliproxy/ai-providers/codex-reasoning-proxy.ts index a8502a82..fe91e40b 100644 --- a/src/cliproxy/ai-providers/codex-reasoning-proxy.ts +++ b/src/cliproxy/ai-providers/codex-reasoning-proxy.ts @@ -680,9 +680,24 @@ export class CodexReasoningProxy { return; } + const maxErrorResponseSize = 10 * 1024 * 1024; // 10MB + let totalResponseBytes = 0; + let responseTooLarge = false; const chunks: Buffer[] = []; - upstreamRes.on('data', (chunk: Buffer) => chunks.push(chunk)); + upstreamRes.on('data', (chunk: Buffer) => { + totalResponseBytes += chunk.length; + if (totalResponseBytes > maxErrorResponseSize) { + responseTooLarge = true; + upstreamRes.destroy(new Error('Upstream error response exceeded 10MB limit')); + return; + } + chunks.push(chunk); + }); upstreamRes.on('end', async () => { + if (responseTooLarge) { + reject(new Error('Upstream error response exceeded 10MB limit')); + return; + } try { const responseBody = Buffer.concat(chunks).toString('utf8'); const unsupportedError =