Repository navigation
fix: zai-web connection-test cookie probe now honors the configured proxy (#7058) #7192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
diegosouzapw
merged 1 commit into
release/v3.8.49
from
fix/7058-zai-web-proxy-connection-test
Jul 15, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| - fix(providers): web-cookie connection-test/cookie-validation probe (zai-web and every other registry-entry web-cookie provider) now honors the configured HTTP/SOCKS proxy — the `/models` probe routed through `directHttpsRequest`'s hardcoded native-fetch bypass, silently skipping proxy resolution even though the executor's actual chat traffic already respected it (#7058) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| // Regression test for #7058 — zai-web (and every other entry-bearing web-cookie | ||
| // provider) never honored a configured HTTP/SOCKS proxy during connection-test / | ||
| // cookie validation. | ||
| // | ||
| // Root cause: validateWebCookieProvider() probed `${baseUrl}/models` via | ||
| // directHttpsRequest(), which hardcodes `bypassProxyPatch: true` — forcing | ||
| // safeOutboundFetch to use the pre-patch native fetch and skip proxy-context/ | ||
| // env-var resolution entirely. That bypass was introduced in #3226 as a narrow, | ||
| // documented exception for a single NVIDIA NIM workaround | ||
| // (see tests/unit/proxy-bypass-scope-guard-3226.test.ts) but validateWebCookieProvider | ||
| // adopted it as its default transport from inception (#4023), silently extending the | ||
| // bypass to every web-cookie provider with a registry entry (zai-web among them). | ||
| // | ||
| // This test proves the cookie-validation probe reaches a local forward proxy | ||
| // (via a real CONNECT tunnel — the same mechanism undici uses for both HTTP and | ||
| // HTTPS targets) when one is configured via HTTP_PROXY, exactly like the | ||
| // specialty web-cookie validators (chatgpt-web, grok-web, ...) already do via | ||
| // validationRead/validationWrite. | ||
| import test from "node:test"; | ||
| import assert from "node:assert/strict"; | ||
| import http from "node:http"; | ||
| import net from "node:net"; | ||
|
|
||
| const { validateWebCookieProvider } = await import("../../src/lib/providers/validation.ts"); | ||
| const { REGISTRY } = await import("../../open-sse/config/providerRegistry.ts"); | ||
| const { clearDispatcherCache } = await import("../../open-sse/utils/proxyDispatcher.ts"); | ||
|
|
||
| const zaiWebEntry = REGISTRY["zai-web"] as { baseUrl?: string } | undefined; | ||
| const ORIGINAL_BASE_URL = zaiWebEntry?.baseUrl; | ||
| const ORIGINAL_HTTP_PROXY = process.env.HTTP_PROXY; | ||
|
|
||
| test.after(() => { | ||
| if (zaiWebEntry && ORIGINAL_BASE_URL !== undefined) { | ||
| zaiWebEntry.baseUrl = ORIGINAL_BASE_URL; | ||
| } | ||
| if (ORIGINAL_HTTP_PROXY === undefined) { | ||
| delete process.env.HTTP_PROXY; | ||
| } else { | ||
| process.env.HTTP_PROXY = ORIGINAL_HTTP_PROXY; | ||
| } | ||
| clearDispatcherCache(); | ||
| }); | ||
|
|
||
| test("zai-web cookie validation routes through the configured HTTP_PROXY (#7058)", async () => { | ||
| assert.ok(zaiWebEntry, "zai-web must have a providerRegistry entry for this test to be meaningful"); | ||
|
|
||
| // Stand-in for chat.z.ai's /models probe target. | ||
| const target = http.createServer((_req, res) => { | ||
| res.writeHead(200, { "content-type": "application/json" }); | ||
| res.end("{}"); | ||
| }); | ||
| await new Promise<void>((resolve) => target.listen(0, () => resolve())); | ||
| const targetPort = (target.address() as net.AddressInfo).port; | ||
|
|
||
| // Minimal forward proxy that only speaks CONNECT (like a real corporate proxy) and | ||
| // always tunnels to the local target above, regardless of the requested host — this | ||
| // lets the "upstream" host be a non-resolvable placeholder without any real DNS | ||
| // dependency, while still proving the request actually reached the proxy. | ||
| let sawConnect = false; | ||
| const proxy = http.createServer((_req, res) => { | ||
| res.writeHead(501); | ||
| res.end("CONNECT only"); | ||
| }); | ||
| proxy.on("connect", (_req, socket) => { | ||
| sawConnect = true; | ||
| const upstream = net.connect(targetPort, "127.0.0.1", () => { | ||
| socket.write("HTTP/1.1 200 Connection Established\r\n\r\n"); | ||
| upstream.pipe(socket); | ||
| socket.pipe(upstream); | ||
| }); | ||
| upstream.on("error", () => socket.destroy()); | ||
| socket.on("error", () => upstream.destroy()); | ||
| }); | ||
| await new Promise<void>((resolve) => proxy.listen(0, () => resolve())); | ||
| const proxyPort = (proxy.address() as net.AddressInfo).port; | ||
|
|
||
| // A non-local-looking hostname: isLocalAddress()/resolveProxyForRequest() force a | ||
| // direct connection for any 127.*/localhost/LAN target, which would defeat this test. | ||
| zaiWebEntry!.baseUrl = "http://zai-web-validation-probe-7058.invalid"; | ||
| process.env.HTTP_PROXY = `http://127.0.0.1:${proxyPort}`; | ||
| clearDispatcherCache(); | ||
|
|
||
| try { | ||
| const result = await validateWebCookieProvider({ provider: "zai-web", apiKey: "token=fake" }); | ||
|
|
||
| assert.equal( | ||
| sawConnect, | ||
| true, | ||
| "BUG #7058: zai-web cookie validation never reached the configured HTTP_PROXY " + | ||
| "(bypassProxyPatch:true unconditionally uses the native, unpatched fetch)" | ||
| ); | ||
| assert.equal(result.valid, true, `expected a valid session, got ${JSON.stringify(result)}`); | ||
| } finally { | ||
| target.close(); | ||
| proxy.close(); | ||
| clearDispatcherCache(); | ||
| } | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
proxy.listen(0)fails or any error occurs during the setup before thetryblock, thetargetserver will remain open and leak its socket/port. Wrapping the server creation, listening, and testing inside a singletry...finallyblock ensures that bothtargetandproxyservers are always closed properly, preventing resource leaks in the test suite.