Skip to content

Host daemon WebSocket connections ignore HTTPS_PROXY/NO_PROXY, so a daemon behind a CONNECT proxy never enrolls #4957

Open

Description

@lebryk

Summary

The host daemon's own WebSocket connections ignore HTTPS_PROXY / HTTP_PROXY / NO_PROXY, even with NODE_USE_ENV_PROXY=1. On a machine whose only route out is an HTTP CONNECT proxy (no direct egress, often no external DNS), the daemon cannot reach the server: the server WebSocket times out ("Server connection timed out after 60000ms") and the machine never enrolls. The same applies to the bb connect tunnel and the machine auth proxy's upstream requests. I expected the daemon to use the proxy the environment declares, as fetch does under NODE_USE_ENV_PROXY=1.

Versions and environment

  • bb main @ 17b19716c5a00bf29a6b51798ddb07b72acaf259 (bb-app 0.45.0); first seen on 0.43.0
  • Linux host daemon (remote machine), Node 24; also reproduced on macOS with Node 25
  • Outbound access only through an HTTP CONNECT proxy, exported as HTTPS_PROXY / HTTP_PROXY with NO_PROXY for local ranges

Steps to reproduce

Universal repro, no restricted network needed: point the proxy variables at a dead port. If the proxy were honoured, both requests would fail with ECONNREFUSED.

cd apps/host-daemon   # uses the daemon's own ws dependency (ws 8.21.0)
cat > /tmp/ws-proxy-repro.mjs <<'JS'
import { createRequire } from "node:module";
const require = createRequire(process.cwd() + "/package.json");
const { WebSocket } = require("ws");
const fetchResult = await fetch("https://example.com", { signal: AbortSignal.timeout(10000) })
  .then((r) => `status ${r.status}`, (e) => `ERR ${e.cause?.code ?? e.message}`);
console.log("fetch:", fetchResult);
const ws = new WebSocket("wss://ws.postman-echo.com/raw");
ws.on("open", () => { console.log("ws: open (direct connection, proxy ignored)"); ws.close(); });
ws.on("error", (e) => console.log("ws: ERR", e.code ?? e.message));
JS
HTTPS_PROXY=http://127.0.0.1:1 HTTP_PROXY=http://127.0.0.1:1 NODE_USE_ENV_PROXY=1 node /tmp/ws-proxy-repro.mjs

On a proxy-only machine: install a host daemon with the proxy variables set; it never enrolls.

Expected vs actual

fetch: ERR ECONNREFUSED
ws: open (direct connection, proxy ignored)

Expected: ws fails the same way (or, with a working proxy, tunnels through it). Actual: ws dials its own socket and ignores the proxy, because it only uses a proxy when given an agent.

Evidence

The three daemon dial sites construct sockets without an agent:

What you ruled out

Proposed fix (prototype)

A fork branch, rebased on the commit above: RIMTHAN-LAB@90bc852 (branch host-daemon-env-proxy).

  • apps/host-daemon/src/env-proxy-agent.ts: proxyAgentFor(url, env) returns a small CONNECT-tunnel agent (http and https proxies, Basic Proxy-Authorization from the proxy URL's userinfo, TLS to the target layered over the tunnel) or undefined for a direct connection. NO_PROXY follows curl's rules (*, host, .suffix, *.suffix, host:port, bracketed IPv6) plus CIDR ranges. Loopback targets always connect directly, so local server/daemon traffic is never sent to a proxy (the concern raised in bb's own outbound fetch() ignores HTTP_PROXY/HTTPS_PROXY: provider usage and Codex subscription calls fail (HTTP 403) behind a mandatory proxy #3239).
  • The three dial sites resolve the agent at dial time. With no proxy set, behaviour is unchanged.
  • No wire change, so no HOST_DAEMON_PROTOCOL_VERSION bump. docs/configuration.md documents the variables.
  • Tests (src/env-proxy-agent.test.ts) run a local CONNECT proxy and local ws/wss servers: a proxied connection reaches a host only the proxy can resolve, wss keeps TLS end to end, no proxy and NO_PROXY connect directly, loopback bypasses the proxy, and a refused tunnel fails without echoing credentials. Two of them fail on main and pass with the change. vitest for env-proxy-agent, machine-auth-proxy, connect-tunnel and server-connection passes (48 tests), host-daemon tsc reports no errors in its sources, and oxlint/oxfmt are clean on the touched files.

I'd be glad to open this as a PR once a maintainer approves it.

Suggested priority and effort

Medium: it blocks every daemon on a proxy-only network and there is no workaround short of a transparent proxy. Effort is low; the prototype is self-contained in apps/host-daemon.

AGENT GENERATED

Activity

  1. added theissue type on Oct 5, 2026
  2. added
    hostHost daemon, process lifecycle, memory, event loop
    connectBuilt-in plugin: connect
    confirmed-reproBug reproduced again from a clean trusted checkout; see linked report
    on Oct 5, 2026
  3. bb-slop-cop commented on Oct 5, 2026

    @bb-slop-cop
    Contributor

    🚨 SLOP COP 🚨 · new-issue-autopilot

    • Classification: Bug · Priority: Medium · Effort: Medium · Labels: host, connect.
    • Report: https://get-bb.github.io/reports/issues/4957.html
    • Verdict: REPRODUCED · Confidence: high. The real daemon WebSocket constructor bypasses the declared proxy in two clean checkouts of trusted main.
    • Root cause: ws supplies createConnection; without an explicit agent, Node skips its environment-aware global agent and resolves the destination directly. All four ws/wss variants fail the focused routing regression; 38 relevant existing tests pass.
    • Reproduction label: confirmed-repro. Correction: machine-auth HTTP upstream requests already honor the proxy on Node 24.5.0 with native proxy opt-in.
    • No PR: Complete support for the Node 22 baseline needs CONNECT/TLS and bypass-policy security review, outside this rule's simple-fix scope.

    AGENT GENERATED

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmed-reproBug reproduced again from a clean trusted checkout; see linked reportconnectBuilt-in plugin: connecthostHost daemon, process lifecycle, memory, event loop

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions