Respect proxy variables throughout the daemon (#8177)

* fix(server): respect environment proxies across daemon traffic

Install proxy routing at daemon and plugin process startup, with a separate HTTP agent for outbound WebSockets. Always exempt loopback so local provider bridges remain reachable.

* test(server): run usage source regressions in CI

* fix(server): validate proxy settings without blocking startup

Normalize scheme-less proxies and share HTTP-only validation across fetch and WebSockets. Record ignored variables without credentials in the daemon log. Separate built-in and directory plugin regressions through a typed fixture.

* refactor(server): treat ignored proxy variables as unset

* fix(server): read Windows proxy environment entries once

* fix(server): apply proxy policy to connected host links

* test(server): isolate proxy fixture credential directories

* fix(server): keep proxy routing and authentication consistent

Normalize Windows environment names and wildcard bypass rules at the network boundary. Compare bypass ports numerically and keep Basic proxy credentials on the CONNECT pool, including empty credential fields.
This commit is contained in:
Mohamed Boudra
2026-10-30 17:57:57 +07:00
committed by GitHub
parent 385c29b655
commit 787edb1bf9
13 changed files with 628 additions and 8 deletions
+14 -6
View File
@@ -2,6 +2,7 @@ export { createHostChannelTransport } from "../hosts/index.js";
import { HostTransportBudget } from "./buffering.js";
export { HostTransportBudget } from "./buffering.js";
import { homedir } from "node:os";
import type { Agent } from "node:http";
import { WebSocket } from "ws";
import { parseHostTarget, type HostTarget } from "@getpaseo/protocol/host-target";
import { createSshTunnel, type SshTunnel } from "./ssh-tunnel.js";
@@ -19,10 +20,15 @@ export { parseHostTarget };
export type { SshTunnel } from "./ssh-tunnel.js";
export { createSshTunnel, resolveSshFailureDetail } from "./ssh-tunnel.js";
export interface HostTransportOptions {
webSocketAgent?: (url: string) => Agent | undefined;
}
/** A factory owns one attempt, including a fresh SSH process and its teardown. */
export function createHostTransportFactory(
connection: string,
budget = new HostTransportBudget(),
options: HostTransportOptions = {},
): DaemonTransportFactory {
const target = parseHostTarget(connection);
// Rises to "protected" once the daemon negotiates it, so a later attempt
@@ -68,12 +74,13 @@ export function createHostTransportFactory(
return;
}
const socketPath = socketTarget.socketPath?.replace(/^~(?=\/)/, homedir());
let factory = createWebSocketTransportFactory((url, options) => {
let factory = createWebSocketTransportFactory((url, socketOptions) => {
const socket = new WebSocket(
socketPath && url.startsWith("ws+unix:") ? `ws+unix://${socketPath}:/ws` : url,
options?.protocols,
socketOptions?.protocols,
{
headers: options?.headers,
headers: socketOptions?.headers,
agent: socketPath ? undefined : options.webSocketAgent?.(url),
...(socketPath ? { socketPath } : {}),
maxPayload: 100 * 1024 * 1024,
},
@@ -191,14 +198,15 @@ export function createHostTransportFactory(
}
export function createHostClient(
connection: string,
config: Omit<DaemonClientConfig, "url" | "transportFactory">,
config: Omit<DaemonClientConfig, "url" | "transportFactory"> & HostTransportOptions,
budget?: HostTransportBudget,
): DaemonClient {
const target = parseHostTarget(connection);
const { webSocketAgent, ...clientConfig } = config;
return new DaemonClient({
...config,
...clientConfig,
url: "ws://host-connector/ws",
...(target.password ? { password: target.password } : {}),
transportFactory: createHostTransportFactory(connection, budget),
transportFactory: createHostTransportFactory(connection, budget, { webSocketAgent }),
});
}