Skip to content

fix(no_proxy): canonicalise IPv4 shorthand/octal/hex in shouldBypassProxy#11029

Merged
jasonsaayman merged 6 commits into
axios:v1.xfrom
AnandSundar:fix/no-proxy-ipv4-normalization
Jun 29, 2026
Merged

fix(no_proxy): canonicalise IPv4 shorthand/octal/hex in shouldBypassProxy#11029
jasonsaayman merged 6 commits into
axios:v1.xfrom
AnandSundar:fix/no-proxy-ipv4-normalization

Conversation

@AnandSundar

@AnandSundar AnandSundar commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

fix(no_proxy): canonicalise IPv4 shorthand/octal/hex in shouldBypassProxy

🏄

Summary

shouldBypassProxy compared NO_PROXY entries against the request hostname
as raw strings. Node's URL parser, however, accepts IPv4 in shorthand
(127.1), octal (0177.0.0.1), and hex (0x7f.0.0.1) form and silently
canonicalises it to dotted-decimal before the hostname reaches the helper.
That asymmetry let a NO_PROXY entry like 127.1 fail to match a request to
http://127.0.0.1/, and — more importantly — let an entry written in
canonical form silently fail to match a request URL whose host was written
in shorthand/octal/hex. The latter is a parser-differential bypass:
NO_PROXY=127.0.0.1 is supposed to keep loopback traffic off the proxy,
but a request to http://0177.0.0.1/ (Node-canonicalised to 127.0.0.1)
did not match the canonical entry and so went through the proxy.

This change adds a normalizeIPAddress helper that mirrors Node's per-part
parsing (hex prefix → base 16, 0-prefixed multi-digit → base 8, otherwise
base 10) and routes the result through the existing normalizeNoProxyHost
so both sides of the comparison see the same canonical dotted-decimal form.
The helper is fail-safe: out-of-range octets, invalid hex digits, 1-part
inputs (which Node treats as a 32-bit integer, e.g. 1270.0.0.127),
and 5+ parts return the input unchanged so the policy falls through to
non-bypass.

Closes the NO_PROXY parser-differential gap that v1.18.0's URL-and-request
hardening pass (#11000) did not cover.

Maintainers: this may be worth tracking as a follow-up advisory to
GHSA-pjwm-pj3p-43mv / CVE-2025-62718 (the prior IPv4-mapped-IPv6 fix in
this same helper). Happy to revise wording or coordinate a CVE/GHSA
assignment if so — flagging here so the work can route whichever way
you prefer.

Changes

  • lib/helpers/shouldBypassProxy.js: add parseIPv4Octet and
    normalizeIPAddress; route normalizeNoProxyHost through the helper
    before falling back to the existing IPv4-mapped IPv6 unmap.
  • tests/unit/helpers/shouldBypassProxy.test.js: 12 new cases in a new
    describe('IPv4 shorthand / octal / hex normalization', …) block,
    covering each shorthand/octal/hex form on both sides of the comparison
    and three fail-safe cases (out-of-range, invalid hex digit, 1-part).
  • PRE_RELEASE_CHANGELOG.md: one-line entry under Bug Fixes per the
    repo's contributor guide (which prefers the pre-release changelog over
    CHANGELOG.md for unreleased changes).

Test plan

  • npx vitest run tests/unit/helpers/shouldBypassProxy.test.js
    52/52 pass (40 pre-existing + 12 new).
  • npm run test:vitest — full suite green.

Checklist

  • Tests added (12 new cases appended to the existing suite).
  • No public API change; no index.d.ts/index.d.cts change needed.
  • No breaking changes.
  • No new runtime dependencies.
  • No changes to package-lock.json (the existing lockfile is
    consistent with the existing package.json after a clean
    npm ci).

Summary by cubic

Fixes NO_PROXY mismatches by normalizing IPv4 shorthand, octal, and hex hostnames in shouldBypassProxy to dotted-decimal to match Node’s URL parser. Also adds safe tail expansion for shorthand forms and rejects invalid zero-prefixed decimals and out-of-range tails.

Description

  • Summary of changes
    • Added normalizeIPAddress and parseIPv4Octet to canonicalize IPv4 shorthand, octal (0177.0.0.1), and hex (0x7f.0.0.1) to dotted-decimal.
    • Expanded shorthand-tail parsing: parse tail as a full IPv4 number (hex/octal/decimal) and pack low-byte-right; e.g. 127.65535127.0.255.255.
    • Fail-safe rejections: invalid zero-prefixed decimals (e.g. 08), out-of-range octets/tails, and 1-part or 5+ parts.
    • Routed normalizeNoProxyHost through the new helper before IPv4-mapped-IPv6 unmapping.
    • Updated pre-release changelog; clarified a test comment and added an inline URL parse assertion.
  • Reasoning
    • Aligns NO_PROXY comparison with Node host canonicalization to prevent false negatives and proxy-bypass via parser differentials.
  • Additional context
    • No public API changes or new dependencies.

Docs

Add a note in /docs/ that NO_PROXY IPv4 entries are normalized to dotted-decimal and match shorthand, octal, and hex forms accepted by Node’s parser. Include brief examples for multi-byte tail expansion and list fail-safes (invalid digits, out-of-range tails, single-part inputs).

Testing

  • Added 21 unit tests in tests/unit/helpers/shouldBypassProxy.test.js covering:
    • Cross-form matches (entry ↔ request) for shorthand, octal, hex.
    • Multi-byte tail expansion in 2- and 3-part inputs (decimal/hex/octal).
    • Fail-safes (out-of-range octet/tail, invalid hex digit, invalid zero-prefixed decimals, single-part inputs).
    • Port preservation across normalization and an inline URL hostname assertion.
  • Full suite passes.

Semantic version impact

Patch: bug fix with no API changes.

Written for commit 00ab59b. Summary will update on new commits.

Review in cubic

…roxy

Node's URL parser accepts IPv4 in shorthand (127.1), octal (0177.0.0.1), and
hex (0x7f.0.0.1) form and canonicalises it to dotted-decimal before the
hostname reaches `shouldBypassProxy`. NO_PROXY entries, by contrast, were
compared as raw strings, so an entry like `NO_PROXY=127.1` failed to match a
request to `http://127.0.0.1/` — and symmetrically, an entry written in
canonical form failed to match a request URL written in shorthand/octal/hex.

This change adds a `normalizeIPAddress` helper that mirrors Node's
per-part parsing (hex prefix → base 16, 0-prefixed multi-digit → base 8,
otherwise base 10) and routes the result through the existing
`normalizeNoProxyHost` so both sides of the comparison see the same canonical
dotted-decimal form. The helper is fail-safe: out-of-range octets, invalid hex
digits, 1-part inputs (which Node treats as a 32-bit integer), and 5+ parts
return the input unchanged so the policy falls through to non-bypass.

Adds 12 unit tests covering each shorthand/octal/hex form on both sides of
the comparison, plus fail-safe cases (out-of-range, invalid hex digit,
1-part input).
…ntry

Backfill the PR number that was a placeholder at commit time.

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found and verified against the latest diff

Confidence score: 2/5

  • lib/helpers/shouldBypassProxy.js normalizes 2/3-part IPv4 hosts differently than Node’s WHATWG URL parser, so NO_PROXY matching can diverge for multi-byte integer tails or hex/octal forms; merging as-is risks incorrect proxy bypass behavior for affected host inputs — align normalization with WHATWG parsing and add targeted tests for those address variants before merging.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread lib/helpers/shouldBypassProxy.js Outdated
Cubic-bot P1 review on PR axios#11029 found that the shorthand-tail parser in
`normalizeIPAddress` handled only single-digit decimal padding, while
Node's URL parser parses the tail as a full IPv4 number (hex/octal/
decimal) and packs it low-byte-right into the remaining octets. Concrete
cases that Node canonicalises but the previous implementation rejected:
127.65535 -> 127.0.255.255, 127.0x00ff -> 127.0.0.255, 127.0177 ->
127.0.0.127, 127.0.65535 -> 127.0.255.255, 0.0.0xff -> 0.0.0.255.

Replace the decimal-only pad-and-split with the cubic-suggested
implementation: parse the tail via the existing `parseIPv4Octet` helper
(which already supports hex/octal/decimal per part), cap at
`maxTail = (1 << (8 * tailSlots)) - 1`, and pack into the remaining
octets from the right via `tailOctets[i] = v & 0xff; v >>= 8`.
Out-of-range tails and non-numeric tails (which `parseIPv4Octet` returns
`null` for) are returned unchanged so the policy falls through to
non-bypass, preserving the fail-safe posture.

The 1-part rejection (Node's 32-bit-integer semantics) is preserved
deliberately: `NO_PROXY=0x7f` does not match `http://127.0.0.127/`, and
a new test locks that asymmetry in.

Adds 9 unit tests: 5 for the new shorthand-tail cases on both sides of
the comparison, 1 for the 1-part hex lock-in, and 2 out-of-range
fail-safe cases. All 12 prior shorthand/octal/hex tests and all 40
pre-existing tests in the file continue to pass.

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread tests/unit/helpers/shouldBypassProxy.test.js
AnandSundar added a commit to AnandSundar/axios that referenced this pull request Jun 22, 2026
…parse

Cubic-bot P3 review on commit ed73218 (PR axios#11029) flagged that the
test comment at tests/unit/helpers/shouldBypassProxy.test.js:471
incorrectly claimed the URL http://127.0.0.0:7777/ fails to parse. Node
parses it cleanly (127.0.0.0:7777 is canonical IPv4 with port); the
test passed for the right reason -- the entry NO_PROXY=127.16777216 is
what gets returned unchanged because tail 16777216 (= 2^24) exceeds the
3-octet tail capacity 2^24 - 1, so the comparison falls through.

Rewrite the comment to describe the entry-side mechanism accurately,
and add an inline expect(new URL(...)).hostname === '127.0.0.0'
assertion inside the same it() block so the URL-side parse is locked
in. The misleading-comment class is now observable: any future change
that breaks URL parsing of canonical IPv4 will fail this test loudly,
not silently.

The 3-part counterpart test (lines 478-484) already had a correct
fail-safe comment and is untouched.

PRE_RELEASE_CHANGELOG.md gets a one-sentence append inside the
existing **NO_PROXY IPv4 normalization:** entry referencing the cubic
review.

All 61 tests in the file pass (60 prior + the rewritten it() block
with its new inline assertion).

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread PRE_RELEASE_CHANGELOG.md Outdated
…parse

Cubic-bot P3 review on commit ed73218 (PR axios#11029) flagged that the
test comment at tests/unit/helpers/shouldBypassProxy.test.js:471
incorrectly claimed the URL http://127.0.0.0:7777/ fails to parse. Node
parses it cleanly (127.0.0.0:7777 is canonical IPv4 with port); the
test passed for the right reason -- the entry NO_PROXY=127.16777216 is
what gets returned unchanged because tail 16777216 (= 2^24) exceeds the
3-octet tail capacity 2^24 - 1, so the comparison falls through.

Rewrite the comment to describe the entry-side mechanism accurately,
and add an inline expect(new URL(...)).hostname === '127.0.0.0'
assertion inside the same it() block so the URL-side parse is locked
in. The misleading-comment class is now observable: any future change
that breaks URL parsing of canonical IPv4 will fail this test loudly,
not silently.

The 3-part counterpart test (lines 478-484) already had a correct
fail-safe comment and is untouched.

All 61 tests in the file pass (60 prior + the rewritten it() block
with its new inline assertion).
@AnandSundar AnandSundar force-pushed the fix/no-proxy-ipv4-normalization branch from c586827 to 0e86fa0 Compare June 22, 2026 23:56
@jasonsaayman jasonsaayman added the commit::fix The PR is related to a bugfix label Jun 29, 2026
@jasonsaayman jasonsaayman merged commit 9e5c894 into axios:v1.x Jun 29, 2026
26 checks passed
@AnandSundar

Copy link
Copy Markdown
Contributor Author

@jasonsaayman Thank you for reviewing my PR and merging with master. I appreciate it.

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

Labels

commit::fix The PR is related to a bugfix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants