mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(cli): bypass proxy for loopback addresses at CLI entrypoint (#868)
Bun's fetch and node:http honor HTTP_PROXY/HTTPS_PROXY env vars, which can route loopback traffic through a configured proxy (e.g. Surge/Clash). When NO_PROXY doesn't explicitly exclude localhost, this breaks loopback communication: SessionStart hooks fail to arrive (transcripts don't sync, web UI stays empty), runner control client times out, and MCP server connections fail. Normalize NO_PROXY at the CLI entrypoint to always cover loopback (localhost, 127.0.0.1, ::1). Child processes inherit the patched env so their loopback traffic is covered too. Non-loopback traffic continues using the configured proxy. Supersedes the runner control client workaround from #563.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { ensureLoopbackProxyBypass } from './proxyEnv';
|
||||
|
||||
describe('ensureLoopbackProxyBypass', () => {
|
||||
it('adds loopback hosts when NO_PROXY is unset', () => {
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
ensureLoopbackProxyBypass(env);
|
||||
expect(env.NO_PROXY).toBe('localhost,127.0.0.1,::1');
|
||||
expect(env.no_proxy).toBe('localhost,127.0.0.1,::1');
|
||||
});
|
||||
|
||||
it('preserves existing entries and appends missing loopback hosts', () => {
|
||||
const env: NodeJS.ProcessEnv = { NO_PROXY: 'npmjs.org' };
|
||||
ensureLoopbackProxyBypass(env);
|
||||
expect(env.NO_PROXY).toBe('npmjs.org,localhost,127.0.0.1,::1');
|
||||
});
|
||||
|
||||
it('does not duplicate already-present hosts', () => {
|
||||
const env: NodeJS.ProcessEnv = { NO_PROXY: 'LOCALHOST, 127.0.0.1' };
|
||||
ensureLoopbackProxyBypass(env);
|
||||
expect(env.NO_PROXY).toBe('LOCALHOST,127.0.0.1,::1');
|
||||
});
|
||||
|
||||
it('reads lowercase no_proxy when NO_PROXY is unset', () => {
|
||||
const env: NodeJS.ProcessEnv = { no_proxy: 'example.com,localhost,127.0.0.1,::1' };
|
||||
ensureLoopbackProxyBypass(env);
|
||||
expect(env.NO_PROXY).toBe('example.com,localhost,127.0.0.1,::1');
|
||||
});
|
||||
|
||||
it('leaves a wildcard NO_PROXY untouched', () => {
|
||||
const env: NodeJS.ProcessEnv = { NO_PROXY: '*' };
|
||||
ensureLoopbackProxyBypass(env);
|
||||
expect(env.NO_PROXY).toBe('*');
|
||||
expect(env.no_proxy).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Loopback proxy bypass.
|
||||
*
|
||||
* The CLI talks to itself and to local agents over loopback HTTP (hook
|
||||
* forwarder -> hook server, control client -> runner, claude -> local MCP
|
||||
* server). Bun's fetch AND its node:http implementation honor the
|
||||
* HTTP_PROXY/HTTPS_PROXY env vars, so when the user's shell exports a proxy
|
||||
* (e.g. Surge/Clash with `HTTP_PROXY=http://127.0.0.1:1080`) without
|
||||
* excluding localhost in NO_PROXY, every loopback request is routed through
|
||||
* the proxy — which may forward "127.0.0.1" to a remote node where nothing
|
||||
* is listening. Symptom: SessionStart hooks never arrive, transcripts never
|
||||
* sync, web UI stays empty.
|
||||
*
|
||||
* Fix: make sure NO_PROXY always covers loopback. Children (claude, runner,
|
||||
* hook-forwarder) inherit the patched env, so their loopback traffic is
|
||||
* covered too. Non-loopback traffic keeps using the configured proxy.
|
||||
*/
|
||||
|
||||
const LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '::1'];
|
||||
|
||||
export function ensureLoopbackProxyBypass(env: NodeJS.ProcessEnv = process.env): void {
|
||||
const existing = env.NO_PROXY ?? env.no_proxy ?? '';
|
||||
const entries = existing
|
||||
.split(',')
|
||||
.map((entry) => entry.trim())
|
||||
.filter((entry) => entry.length > 0);
|
||||
|
||||
const present = new Set(entries.map((entry) => entry.toLowerCase()));
|
||||
if (present.has('*')) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const host of LOOPBACK_HOSTS) {
|
||||
if (!present.has(host)) {
|
||||
entries.push(host);
|
||||
}
|
||||
}
|
||||
|
||||
const merged = entries.join(',');
|
||||
env.NO_PROXY = merged;
|
||||
env.no_proxy = merged;
|
||||
}
|
||||
Reference in New Issue
Block a user