From 8a573c56e366d8234da804ce8f79b4e9589f1fb2 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Thu, 6 Aug 2026 22:55:33 -0300 Subject: [PATCH] fix(proxy): NO_PROXY now bypasses context-level proxy in resolveProxyForRequest (#9551) Closes #9551 --- .../fixes/9551-proxyfetch-context-bypass.md | 1 + open-sse/utils/proxyFetch.ts | 4 ++ ...proxyfetch-no-proxy-context-bypass.test.ts | 61 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 changelog.d/fixes/9551-proxyfetch-context-bypass.md create mode 100644 tests/unit/9551-proxyfetch-no-proxy-context-bypass.test.ts diff --git a/changelog.d/fixes/9551-proxyfetch-context-bypass.md b/changelog.d/fixes/9551-proxyfetch-context-bypass.md new file mode 100644 index 0000000000..eeefa9d7e6 --- /dev/null +++ b/changelog.d/fixes/9551-proxyfetch-context-bypass.md @@ -0,0 +1 @@ +- fix(proxy): NO_PROXY now bypasses context-level proxy in resolveProxyForRequest (#9551) diff --git a/open-sse/utils/proxyFetch.ts b/open-sse/utils/proxyFetch.ts index 6fa2c8c29c..a5080c6fbb 100644 --- a/open-sse/utils/proxyFetch.ts +++ b/open-sse/utils/proxyFetch.ts @@ -382,6 +382,10 @@ export function resolveProxyForRequest(targetUrl) { const contextProxy = proxyContext.getStore(); if (contextProxy) { + // #9551: NO_PROXY must bypass context-proxy too + if (target && noProxyMatch(targetUrl)) { + return { source: "direct", proxyUrl: null }; + } return { source: "context", proxyUrl: proxyConfigToUrl(contextProxy) }; } diff --git a/tests/unit/9551-proxyfetch-no-proxy-context-bypass.test.ts b/tests/unit/9551-proxyfetch-no-proxy-context-bypass.test.ts new file mode 100644 index 0000000000..04a3fd2b17 --- /dev/null +++ b/tests/unit/9551-proxyfetch-no-proxy-context-bypass.test.ts @@ -0,0 +1,61 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { runWithProxyContext, resolveProxyForRequest } from "../../open-sse/utils/proxyFetch.ts"; + +async function withEnv( + overrides: Record, + fn: () => unknown +): Promise { + const previous = new Map(); + + for (const [key, value] of Object.entries(overrides)) { + previous.set(key, process.env[key]); + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + + try { + return await fn(); + } finally { + for (const [key, value] of previous.entries()) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } +} + +test("[9551] BUG: context-proxy ignores NO_PROXY for non-local domains", async () => { + await withEnv( + { + NO_PROXY: "ark.cn-beijing.volces.com", + HTTP_PROXY: undefined, + }, + async () => { + await runWithProxyContext({ type: "http", host: "127.0.0.1", port: 7897 }, () => { + const resolved = resolveProxyForRequest("https://ark.cn-beijing.volces.com/api/v3/models"); + assert.equal(resolved.source, "direct", "NO_PROXY should bypass context proxy"); + }); + } + ); +}); + +test("[9551] resolveProxyForRequest: context-proxy respects NO_PROXY=*", async () => { + await withEnv( + { + NO_PROXY: "*", + HTTP_PROXY: undefined, + }, + async () => { + await runWithProxyContext({ type: "http", host: "127.0.0.1", port: 7897 }, () => { + const resolved = resolveProxyForRequest("https://api.openai.com/v1/chat/completions"); + assert.equal(resolved.source, "direct", "NO_PROXY=* should bypass context proxy"); + }); + } + ); +});