mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-09 17:28:51 +00:00
fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620)
Some checks failed
CI / Change Classification (push) Has been cancelled
Publish Fork Image to GHCR / Build and Push Fork Image (push) Has been cancelled
OpenSSF Scorecard / Scorecard analysis (push) Has been cancelled
semgrep / semgrep (push) Has been cancelled
CI / E2E Tests (4/9) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Quality Gates (Extended) (push) Has been cancelled
CI / Docs Sync (Strict) (push) Has been cancelled
CI / Docs Lint (prose — advisory) (push) Has been cancelled
CI / i18n UI Coverage (push) Has been cancelled
CI / i18n Validation (all languages) (push) Has been cancelled
CI / PR Test Policy (push) Has been cancelled
Publish to Docker Hub / Resolve Docker release metadata (push) Has been cancelled
CI / Quality Ratchet (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Package Artifact (push) Has been cancelled
CI / Electron Package Smoke (push) Has been cancelled
CI / Unit Tests (1/8) (push) Has been cancelled
CI / Unit Tests (2/8) (push) Has been cancelled
CI / Unit Tests (3/8) (push) Has been cancelled
CI / Unit Tests (4/8) (push) Has been cancelled
CI / Unit Tests (5/8) (push) Has been cancelled
CI / Unit Tests (6/8) (push) Has been cancelled
CI / Unit Tests (7/8) (push) Has been cancelled
CI / Unit Tests (8/8) (push) Has been cancelled
CI / Vitest (MCP / autoCombo / UI components) (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / SonarQube (push) Has been cancelled
CI / PR Coverage Comment (push) Has been cancelled
CI / E2E Tests (1/9) (push) Has been cancelled
CI / E2E Tests (2/9) (push) Has been cancelled
CI / E2E Tests (3/9) (push) Has been cancelled
CI / E2E Tests (5/9) (push) Has been cancelled
CI / E2E Tests (6/9) (push) Has been cancelled
CI / E2E Tests (7/9) (push) Has been cancelled
CI / E2E Tests (8/9) (push) Has been cancelled
CI / E2E Tests (9/9) (push) Has been cancelled
CI / Integration Tests (1/2) (push) Has been cancelled
CI / Integration Tests (2/2) (push) Has been cancelled
CI / Security Tests (push) Has been cancelled
CI / CI Dashboard (push) Has been cancelled
Publish to Docker Hub / Build Docker (linux/amd64) (push) Has been cancelled
Publish to Docker Hub / Build Docker (linux/arm64) (push) Has been cancelled
Publish to Docker Hub / Publish multi-arch manifests (push) Has been cancelled
Some checks failed
CI / Change Classification (push) Has been cancelled
Publish Fork Image to GHCR / Build and Push Fork Image (push) Has been cancelled
OpenSSF Scorecard / Scorecard analysis (push) Has been cancelled
semgrep / semgrep (push) Has been cancelled
CI / E2E Tests (4/9) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Quality Gates (Extended) (push) Has been cancelled
CI / Docs Sync (Strict) (push) Has been cancelled
CI / Docs Lint (prose — advisory) (push) Has been cancelled
CI / i18n UI Coverage (push) Has been cancelled
CI / i18n Validation (all languages) (push) Has been cancelled
CI / PR Test Policy (push) Has been cancelled
Publish to Docker Hub / Resolve Docker release metadata (push) Has been cancelled
CI / Quality Ratchet (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Package Artifact (push) Has been cancelled
CI / Electron Package Smoke (push) Has been cancelled
CI / Unit Tests (1/8) (push) Has been cancelled
CI / Unit Tests (2/8) (push) Has been cancelled
CI / Unit Tests (3/8) (push) Has been cancelled
CI / Unit Tests (4/8) (push) Has been cancelled
CI / Unit Tests (5/8) (push) Has been cancelled
CI / Unit Tests (6/8) (push) Has been cancelled
CI / Unit Tests (7/8) (push) Has been cancelled
CI / Unit Tests (8/8) (push) Has been cancelled
CI / Vitest (MCP / autoCombo / UI components) (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / SonarQube (push) Has been cancelled
CI / PR Coverage Comment (push) Has been cancelled
CI / E2E Tests (1/9) (push) Has been cancelled
CI / E2E Tests (2/9) (push) Has been cancelled
CI / E2E Tests (3/9) (push) Has been cancelled
CI / E2E Tests (5/9) (push) Has been cancelled
CI / E2E Tests (6/9) (push) Has been cancelled
CI / E2E Tests (7/9) (push) Has been cancelled
CI / E2E Tests (8/9) (push) Has been cancelled
CI / E2E Tests (9/9) (push) Has been cancelled
CI / Integration Tests (1/2) (push) Has been cancelled
CI / Integration Tests (2/2) (push) Has been cancelled
CI / Security Tests (push) Has been cancelled
CI / CI Dashboard (push) Has been cancelled
Publish to Docker Hub / Build Docker (linux/amd64) (push) Has been cancelled
Publish to Docker Hub / Build Docker (linux/arm64) (push) Has been cancelled
Publish to Docker Hub / Publish multi-arch manifests (push) Has been cancelled
fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump. undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green. Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.)
This commit is contained in:
parent
44d501ae6a
commit
9cd18bf9a1
3 changed files with 621 additions and 815 deletions
|
|
@ -472,6 +472,12 @@ export function createProxyDispatcher(proxyUrl: string): Dispatcher {
|
|||
// valid; the cast suppresses the spurious missing-`port` error.
|
||||
dispatcher = new ProxyAgent({
|
||||
uri: cleanUri,
|
||||
// undici 8.6+ forwards plain-HTTP requests through the proxy as an origin
|
||||
// request (GET http://host/…) instead of a CONNECT tunnel; upstream proxies
|
||||
// that only speak CONNECT then reject it (501). OmniRoute tunnels ALL proxied
|
||||
// traffic (HTTP + HTTPS) via CONNECT, so force tunneling. Unknown option on
|
||||
// undici <8.6 → silently ignored (that version already tunneled by default).
|
||||
proxyTunnel: true,
|
||||
...proxyDispatcherOptions,
|
||||
...(family !== null
|
||||
? { proxyTls: { family, autoSelectFamily: false } as ProxyAgent.Options["proxyTls"] }
|
||||
|
|
|
|||
1355
package-lock.json
generated
1355
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -26,7 +26,12 @@ describe("proxyDispatcher SOCKS5 host handling", () => {
|
|||
|
||||
describe("proxyDispatcher family directive", () => {
|
||||
it("encodes family from a config object onto the URL", () => {
|
||||
const url = proxyConfigToUrl({ type: "http", host: "proxy.example.com", port: 8080, family: "ipv6" });
|
||||
const url = proxyConfigToUrl({
|
||||
type: "http",
|
||||
host: "proxy.example.com",
|
||||
port: 8080,
|
||||
family: "ipv6",
|
||||
});
|
||||
assert.ok(url!.includes("family=ipv6"), url!);
|
||||
});
|
||||
it("derives 6 for an explicit ipv6 directive on a hostname proxy", () => {
|
||||
|
|
@ -38,7 +43,10 @@ describe("proxyDispatcher family directive", () => {
|
|||
assert.equal(__resolveDispatcherFamilyForTest("http://proxy.example.com:8080"), null);
|
||||
});
|
||||
it("throws (fail-closed) when family=ipv6 contradicts a v4 literal", () => {
|
||||
assert.throws(() => __resolveDispatcherFamilyForTest("http://203.0.113.7:8080?family=ipv6"), /family/i);
|
||||
assert.throws(
|
||||
() => __resolveDispatcherFamilyForTest("http://203.0.113.7:8080?family=ipv6"),
|
||||
/family/i
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -97,3 +105,66 @@ describe("proxyDispatcher connection pool", () => {
|
|||
assert.equal(closeCount, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("proxyDispatcher CONNECT tunneling (undici 8.6+ proxyTunnel)", () => {
|
||||
it("tunnels a plain-HTTP proxied request via CONNECT, not origin-forwarding", async () => {
|
||||
const http = await import("node:http");
|
||||
const net = await import("node:net");
|
||||
const { createProxyDispatcher } = await import("../../open-sse/utils/proxyDispatcher.ts");
|
||||
const { fetch: undiciFetch } = await import("undici");
|
||||
|
||||
// Upstream HTTP target.
|
||||
const target = http.createServer((_req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end("ok");
|
||||
});
|
||||
await new Promise((r) => target.listen(0, r));
|
||||
const targetPort = (target.address() as { port: number }).port;
|
||||
|
||||
// Proxy that ONLY speaks CONNECT: 501 on a forwarded origin request, tunnels on CONNECT.
|
||||
let sawConnect = false;
|
||||
let sawForward = false;
|
||||
const proxy = http.createServer((_req, res) => {
|
||||
sawForward = true;
|
||||
res.writeHead(501);
|
||||
res.end("CONNECT only");
|
||||
});
|
||||
proxy.on("connect", (req, socket) => {
|
||||
sawConnect = true;
|
||||
const [host, port] = String(req.url).split(":");
|
||||
const upstream = net.connect(Number(port), host, () => {
|
||||
socket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
||||
upstream.pipe(socket);
|
||||
socket.pipe(upstream);
|
||||
});
|
||||
upstream.on("error", () => socket.destroy());
|
||||
});
|
||||
await new Promise((r) => proxy.listen(0, r));
|
||||
const proxyPort = (proxy.address() as { port: number }).port;
|
||||
|
||||
try {
|
||||
const dispatcher = createProxyDispatcher(`http://127.0.0.1:${proxyPort}`);
|
||||
const res = await undiciFetch(`http://127.0.0.1:${targetPort}/token`, {
|
||||
method: "POST",
|
||||
// @ts-expect-error undici dispatcher option
|
||||
dispatcher,
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
assert.equal(res.status, 200, "request must succeed via CONNECT tunnel");
|
||||
assert.equal(
|
||||
sawConnect,
|
||||
true,
|
||||
"proxy must receive a CONNECT (tunnel), not a forwarded request"
|
||||
);
|
||||
assert.equal(
|
||||
sawForward,
|
||||
false,
|
||||
"proxy must NOT receive a forwarded origin request (undici 8.6+ regression)"
|
||||
);
|
||||
} finally {
|
||||
proxy.close();
|
||||
target.close();
|
||||
clearDispatcherCache();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue