Skip to content

[Bug]: NO_PROXY is read without the case/trim fallback applied to every other proxy variable, so an empty NO_PROXY shadows no_proxy #185

Description

@adityajha2005

Product or interface

Source build or repository tooling

(The defect is in CLI startup code: packages/tui/src/cli/main.ts:97 calls configureTuiNetworkProxy, so all CLI modes share it. I verified it only against the current source at 33b259b, not against a released package, so I am not claiming published-build behavior.)

Version

Source commit 33b259b, Node.js v24.21.0, pnpm 9.12.0

Platform

macOS

OS version and architecture

macOS 15 arm64 (Darwin 25.5.0)

Issue area

Other (proxy / network routing configuration at CLI startup)

Steps to reproduce

resolveTuiProxyConfiguration and shouldBypassTuiProxy are both exported, so this runs against the repository file in place. Nothing is copied or modified.

After pnpm install --frozen-lockfile:

import { resolveTuiProxyConfiguration, shouldBypassTuiProxy }
  from './packages/tui/src/cli/network-proxy.ts';

const P = 'http://proxy.corp:8080';
const cases = [
  ['A  no_proxy only (control)',       { https_proxy: P, no_proxy: 'internal.corp' }],
  ['B  NO_PROXY="" + no_proxy set',    { https_proxy: P, NO_PROXY: '',    no_proxy: 'internal.corp' }],
  ['C  NO_PROXY="   " + no_proxy set', { https_proxy: P, NO_PROXY: '   ', no_proxy: 'internal.corp' }],
  ['D  HTTPS_PROXY="" + https_proxy',  { HTTPS_PROXY: '', https_proxy: P }],
];

for (const [label, env] of cases) {
  const c = resolveTuiProxyConfiguration(env);
  console.log(label, c.noProxy,
    shouldBypassTuiProxy(new URL('https://internal.corp/v1'), c.noProxy));
}

Run with node repro.mjs.

Expected and actual behavior

Expected: no_proxy=internal.corp is honored, so https://internal.corp/v1 bypasses the proxy, the same way an empty uppercase HTTPS_PROXY falls through to lowercase https_proxy (case D).

Actual: when NO_PROXY is set but empty or whitespace-only, the lowercase no_proxy is discarded and the host is routed through the proxy.

resolveTuiProxyConfiguration reads four proxy variables. Three go through firstProxyValue, which trims, treats empty as unset, and falls back across spellings. NO_PROXY is the only one that does not:

// packages/tui/src/cli/network-proxy.ts:34-37
const allProxy   = firstProxyValue(environment, 'ALL_PROXY', 'all_proxy');
const httpProxy  = firstProxyValue(environment, 'HTTP_PROXY', 'http_proxy') ?? allProxy;
const httpsProxy = firstProxyValue(environment, 'HTTPS_PROXY', 'https_proxy') ?? allProxy ?? httpProxy;

// packages/tui/src/cli/network-proxy.ts:47
noProxy: withLoopbackNoProxy(environment.NO_PROXY ?? environment.no_proxy),

?? falls through only on null/undefined, so an empty NO_PROXY is treated as an authoritative empty bypass list. There is also no .trim(), so NO_PROXY=" " behaves the same way.

Why this looks unintended rather than deliberate: an empty HTTPS_PROXY does fall through to lowercase https_proxy, because firstProxyValue treats empty as "not set". Only NO_PROXY gives an empty value authority. Within a single function the same environment shape is interpreted two different ways.

The file has not changed since the initial source import (git log --oneline -- packages/tui/src/cli/network-proxy.tsc59cf53 only), so the recent uppercase/lowercase proxy parity work appears to have covered test fixtures and the subprocess environment without revisiting this resolver.

Environments that produce this shape: test/smoke.test.mjs:40,44 already constructs it. The fixture sets both NO_PROXY: "" and no_proxy: "". It is harmless there because both are empty, but it shows the shape is routine. A trailing space in a .env file, or tooling that exports a declared-but-unset variable, produces it too.

Impact: configureTuiNetworkProxy's noProxy feeds shouldBypassTuiProxy in TuiProxyDispatcher.dispatch, so internal hosts that should go direct are sent to the external proxy. Loopback stays safe because withLoopbackNoProxy always appends it.

Not a duplicate of the open proxy issues:

None concern NO_PROXY precedence inside resolveTuiProxyConfiguration.

Test coverage: no unit test covers resolveTuiProxyConfiguration, and no test file references the module. grep -rn "resolveTuiProxyConfiguration\|shouldBypassTuiProxy\|network-proxy" matches only the source file, main.ts, release/public-source.json, and one unrelated prose mention in a srt-macos.ts comment. The smoke tests exercise it indirectly through main.ts, which is presumably why those proxy variables are in the fixture, but the precedence behavior itself is unverified.

Suggested direction (suggestion only, not a patch): route NO_PROXY through the same helper as the others, for example firstProxyValue(environment, 'NO_PROXY', 'no_proxy')?.value, so empty and whitespace-only values fall through consistently. I am not a repository collaborator, so per CONTRIBUTING.md I am reporting this rather than opening a PR. Happy to supply further detail or regression cases.

Redacted error summary

A  no_proxy only (control)
    noProxy = internal.corp,localhost,127.0.0.1,::1
    shouldBypassTuiProxy('https://internal.corp/v1') = true

B  NO_PROXY="" + no_proxy set
    noProxy = localhost,127.0.0.1,::1
    shouldBypassTuiProxy('https://internal.corp/v1') = false      <-- routed through proxy

C  NO_PROXY="   " + no_proxy set
    noProxy = localhost,127.0.0.1,::1
    shouldBypassTuiProxy('https://internal.corp/v1') = false      <-- routed through proxy

D  HTTPS_PROXY="" + https_proxy set (control)
    httpsProxy = http://proxy.corp:8080                           <-- lowercase fallback honored

Before submitting

  • I have searched existing issues.
  • I have included my version and removed sensitive information.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions