Skip to content

http: match subdomains for plain NO_PROXY entries - #65617

Open
dibenkobit wants to merge 5 commits into
nodejs:mainfrom
dibenkobit:http-no-proxy-suffix
Open

http: match subdomains for plain NO_PROXY entries#65617
dibenkobit wants to merge 5 commits into
nodejs:mainfrom
dibenkobit:http-no-proxy-suffix

Conversation

@dibenkobit

@dibenkobit dibenkobit commented Aug 28, 2026

Copy link
Copy Markdown

NO_PROXY=example.com only exact-matched the hostname in the
http/https built-ins, so requests to subdomains still used the proxy.
Make plain domain entries match the host and its subdomains, as fetch()
already does, while preserving a label boundary.

The new plain-entry suffix match ignores empty entries and only applies
when both the entry and request host are not IP literals. Existing
leading-dot and wildcard behavior is unchanged.

Fixes: #65616
Refs: #57872
Assisted-by: Grok 4.6 Extra High
Assisted-by: Fable 5

cc @nodejs/http

NO_PROXY=example.com only exact-matched the hostname in the
http(s) builtins, so requests to subdomains still used the proxy.
fetch() already matches the host and its subdomains.

Reuse the existing suffix matcher (label boundary) for plain
entries as well as leading-dot ones.

Fixes: nodejs#65616
Assisted-by: Grok 4.6 Extra High
Signed-off-by: Nikita Snetkov <lukyanish@gmail.com>
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added http Issues and PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels Aug 28, 2026
@codecov

codecov Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.06%. Comparing base (9f04fcd) to head (b19633f).
⚠️ Report is 33 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65617      +/-   ##
==========================================
- Coverage   90.07%   90.06%   -0.01%     
==========================================
  Files         751      751              
  Lines      254916   254918       +2     
  Branches    48133    48129       -4     
==========================================
- Hits       229605   229588      -17     
- Misses      16496    16498       +2     
- Partials     8815     8832      +17     
Files with missing lines Coverage Δ
lib/internal/http.js 92.36% <100.00%> (+0.02%) ⬆️

... and 34 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread lib/internal/http.js Outdated
const suffix = entry.substring(1);
// Strip a leading "." if present, then match as a suffix with a
// label boundary. "*.example.com" is handled below (subdomains only).
if (!entry.startsWith('*.')) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we limit suffix matching to domain entries?
As written, NO_PROXY=127.0.0.1 also bypasses foo.127.0.0.1, although IP entries are documented as exact matches. Empty entries from trailing commas can also match hostnames ending in a dot.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will look into that

@dibenkobit dibenkobit Sep 1, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in the latest fixup to keep the change scoped to plain entries:

  1. The new suffix match ignores empty entries, so an empty entry from a
    trailing comma cannot match a hostname ending in a dot.
  2. It only applies when both the entry and request host are not IP
    literals, so NO_PROXY=127.0.0.1 does not match foo.127.0.0.1.

Existing leading-dot and wildcard behavior is unchanged. Both cases are
covered by regression tests.


// The request should go through the proxy (not bypass it),
// because badexample.com is not a subdomain of example.com.
assert.match(stdout, /Status Code: 200/);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test seems to pass whether the proxy is used or bypassed, since both paths reach the same server. Could we also assert that the custom lookup for badexample.com was not used?
For example:

assert.doesNotMatch(
  stdout,
  /Resolving lookup for badexample\.com/,
);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, will fix

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added it to all three negative blocks

Limit NO_PROXY suffix matching to domain entries and domain hosts:

- drop empty entries (e.g. from trailing commas) at parse time, so
  they can no longer match hostnames ending with a dot
- IP entries only match a host exactly; IP hosts can only be bypassed
  by exact IP, IP:port, or IP range entries
- degenerate "." and "*." entries no longer match every host ending
  with a dot

Assisted-by: Claude Fable 5
Signed-off-by: Nikita Snetkov <lukyanish@gmail.com>
Assert that the negative NO_PROXY domain tests actually go through
the proxy: both paths return 200 from the same server, so assert on
the absence of the custom lookup log line instead.

Assisted-by: Claude Fable 5
Signed-off-by: Nikita Snetkov <lukyanish@gmail.com>
Reword the NO_PROXY IP-matching note: the previous wording
contradicted the IP range and host:port forms listed above it.

Assisted-by: Claude Fable 5
Signed-off-by: Nikita Snetkov <lukyanish@gmail.com>
@dibenkobit
dibenkobit marked this pull request as draft September 1, 2026 12:45
Keep the change scoped to plain domain entries. Preserve the existing
leading-dot and wildcard behavior, while preventing the new suffix
match from applying to empty entries or IP literals.

Signed-off-by: Nikita Snetkov <lukyanish@gmail.com>
@dibenkobit
dibenkobit marked this pull request as ready for review September 1, 2026 13:44
@dibenkobit
dibenkobit requested a review from inoway46 September 1, 2026 13:44
@mcollina
mcollina requested review from joyeecheung and removed request for inoway46 September 1, 2026 15:17
Comment thread doc/api/http.md
@@ -4615,7 +4619,7 @@ Proxy URLs can use either HTTP or HTTPS protocols:
The `NO_PROXY` environment variable supports several formats:

* `*` - Bypass proxy for all hosts
* `example.com` - Exact host name match
* `example.com` - Host and subdomain match (matches `sub.example.com`)
* `.example.com` - Domain suffix match (matches `sub.example.com`)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would make example.com and .example.com match. This does not seem something we might want to do

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

Labels

http Issues and PRs related to the http subsystem. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NO_PROXY=example.com does not bypass subdomains for http.request(), unlike fetch()

4 participants