Skip to content

fix(xhr): reject navigation-canceled requests as ECONNABORTED - #11094

Merged
jasonsaayman merged 8 commits into
axios:v1.xfrom
ari-token-security:fix/xhr-status-0-navigation-cancel
Jul 30, 2026
Merged

jasonsaayman merged 8 commits into
axios:v1.xfrom
ari-token-security:fix/xhr-status-0-navigation-cancel

Conversation

@ari-token-security

@ari-token-security ari-token-security commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Firefox 152 stopped firing error and abort for XHRs canceled by a document navigation, emitting only readystatechange and loadend (bugzilla 1505389, and Mozilla upstreamed a conformance test for it in wpt#59603). Since onerror/onabort are what reject and null the request handle, and that null is the only thing making onloadend bail, onloadend became the only handler that runs. settle() then resolved the canceled request through its !response.status branch, so callers received a successful response carrying status: 0 and an empty body.

This rejects that case in the XHR adapter instead, with ECONNABORTED / Request aborted. That is the same error Firefox 151 produced for the identical cancellation, so an application sees no change in outcome across the Firefox update. Mozilla confirmed the old behavior on the bug ("only the onabort callback is called instead of onerror") and the patch that changed it is titled "Fires readystatechange and loadend intead of abort".

lib/core/settle.js is deliberately untouched, because it is shared with the fetch adapter where an opaque no-cors response legitimately carries status 0.

Patch-level behavior change, not a breaking API change. Browser requests whose response reaches loadend with status 0 previously resolved with an empty body and now reject with ECONNABORTED / Request aborted. Two consequences worth stating explicitly:

  • Reads over file:, which some environments report as status 0 on success, still resolve. The exemption looks at the scheme of the request URL, at the scheme of the page origin for relative URLs (which inherit it), and at responseURL where implemented.
  • The rejection is not suppressed by validateStatus. It never consulted it for status 0 anyway, since !response.status short-circuits ahead of it, and onerror/onabort reject unconditionally for the same reason: there is no response to validate.

This is XHR-only. The fetch and Node HTTP adapters do not have the equivalent failure mode, and are unchanged.

Linked issue

Closes #11093

Changes

  • lib/adapters/xhr.js: reject with ECONNABORTED when onloadend is reached with status 0, excluding file: reads.
  • tests/browser/requests.browser.test.js: a rejection test for the non-file: case, and a resolve test for a relative file: read, placed next to the existing file-protocol test.
  • tests/unit/adapters/xhr.test.js: unit tests that drive the real adapter in Node with stubbed globals, covering the relative file: read without responseURL raised in review, plus reject and resolve controls for the other exemption sources.
  • PRE_RELEASE_CHANGELOG.md: entry under Bug Fixes.

Checklist

  • Tests added or updated (or N/A with reason)
  • Docs/types updated if public API changed (index.d.ts and index.d.cts) - N/A, no public API change. The existing ECONNABORTED description ("timed out ... or aborted by the browser or its plugin") already covers this case.
  • No breaking changes (or called out explicitly above)

Validation

Run against this branch, on top of v1.x at 5097193 (1.19.0):

npm run lint                            exit 0
npm run build                           exit 0
npm run test:vitest:browser:headless    591 passed (chromium, firefox, webkit)

npm run test:vitest:unit has 2 failures in tests/unit/adapters/fetch.test.js, both of which reproduce identically on a pristine checkout of v1.x, so they are unrelated to this change.

Each new assertion was mutation-tested: removing the guard fails the rejection tests, and dropping any of the three file: exemption sources fails a corresponding test.

Note that Playwright's bundled Firefox is 151, which predates the behavior being fixed, so the regression cannot be reproduced through the browser runner. The tests use the existing MockXMLHttpRequest, whose finish() already fires only onloadend, which is exactly the Firefox 152 shape. The browser harness also cannot serve a page over file:, so the page-origin exemption is covered by the unit tests instead, which stub the globals before importing axios so the real adapter runs with a file: origin.

🏄 Investigated and written with AI assistance, reviewed before opening.


Summary by cubic

Reject navigation-canceled XHRs with status 0 as ECONNABORTED to prevent empty successes on Firefox 152+. Successful file: reads still resolve, including relative paths inferred from the page’s file: origin or via responseURL.

  • Description

    • XHR adapter now rejects on loadend with status 0 unless it’s a file: read (checks normalized request URL, page origin for relative URLs, and responseURL).
    • Added normalizeURLForProtocolCheck to match WHATWG URL preprocessing; used by XHR protocol checks and imported by buildFullPath.
    • Leaves lib/core/settle.js unchanged to avoid impacting fetch’s opaque no-cors responses.
    • Rejection occurs before validateStatus, since no response was received.
  • Testing and Impact

    • Browser tests: reject non-file: status 0; resolve absolute/relative file: reads, including via responseURL.
    • Unit tests: cover page-origin inference, URL normalization (leading spaces/C0 controls), and that validateStatus is not called.
    • Docs: update /docs/ browser/XHR section to note status 0 on loadend rejects as ECONNABORTED, except successful file: reads; clarify validateStatus is not applied without a response.
    • Semantic version: Patch (XHR-only bug fix; Node and fetch adapters unchanged).

Written for commit 27b9d97. Summary will update on new commits.

Review in cubic

onerror and onabort are what reject a request that never received a
response, and they null the request handle, which is the only thing
that makes onloadend bail. Firefox 152 stopped firing either of them
for requests canceled by a document navigation, emitting only
readystatechange and loadend, so onloadend became the only handler
that runs and settle() resolved the request through its
!response.status branch. Callers got a successful response carrying
status 0 and an empty body, which then fails any JSON parsing or
schema validation downstream.

Firefox 151 raised abort for the same cancellation, which this adapter
reports as ECONNABORTED, so rejecting with that code leaves the
outcome an application sees unchanged across the update. Mozilla
confirmed the old behavior on the bug, and the patch that changed it
is titled "Fires readystatechange and loadend intead of abort".

The file: exception is keyed on both the request URL and responseURL.
A page loaded from file: may request a relative path that carries no
scheme, and responseURL is unavailable in some of the environments
where a successful file: read is reported as status 0.

Ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1505389

@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.

All reported issues were addressed across 3 files

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

Re-trigger cubic

Comment thread lib/adapters/xhr.js Outdated
@greptile-apps

greptile-apps Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Greptile Summary

Rejects status-zero XHR completions as aborted while preserving successful file: reads.

  • Normalizes request URLs before protocol detection so browser-trimmed network URLs are not mistaken for relative file: URLs.
  • Retains exemptions based on the request URL, page origin, or XHR responseURL.
  • Adds browser and unit coverage for rejection, file-origin inheritance, explicit network URLs, and validateStatus behavior.
  • Documents the behavior change in the prerelease changelog.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failures remain.

T-Rex T-Rex Logs

What T-Rex did

  • The unit tests for the xhr adapter were executed and completed with exit code 0.
  • The verbose test run verified that relative and absolute file resolution checks for tests/unit/adapters/xhr.test.js passed, and it confirmed the non-file status-0 rejection and that the test asserts rejection before validateStatus can accept status 0.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
lib/adapters/xhr.js Adds status-zero rejection and correctly applies browser-style URL preprocessing before the file-protocol exemption.
lib/helpers/normalizeURLForProtocolCheck.js Extracts reusable WHATWG-style preprocessing for protocol checks.
lib/core/buildFullPath.js Replaces the local URL-normalization implementation with the shared helper without changing behavior.
tests/browser/requests.browser.test.js Adds browser-level coverage for status-zero rejection and responseURL-based file reads.
tests/unit/adapters/xhr.test.js Covers file-origin fallback, explicit network URLs with browser-trimmed prefixes, and unconditional aborted-request rejection.

Reviews (5): Last reviewed commit: "fix(xhr): normalize URL before status 0 ..." | Re-trigger Greptile

Comment thread lib/adapters/xhr.js Outdated
A relative request URL carries no scheme, so a read from a page served
over file: was only exempted from the status-0 rejection when the
engine exposed responseURL. Fall back to the scheme of platform.origin,
which a relative URL inherits, so such reads keep resolving when
responseURL is unavailable.

Unit tests drive the real adapter with stubbed globals to pin all
three exemption sources and both rejection paths.
…gation-cancel

# Conflicts:
#	PRE_RELEASE_CHANGELOG.md
@jasonsaayman jasonsaayman added the commit::fix The PR is related to a bugfix label Jul 29, 2026
Comment thread lib/adapters/xhr.js Outdated
@jasonsaayman
jasonsaayman merged commit c3f553c into axios:v1.x Jul 30, 2026
27 checks passed
markharding pushed a commit to futo-org/Harbor that referenced this pull request Oct 16, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [axios](https://axios-http.com) ([source](https://github.com/axios/axios)) | [`1.18.1` → `1.20.0`](https://renovatebot.com/diffs/npm/axios/1.18.1/1.20.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/axios/1.20.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/axios/1.18.1/1.20.0?slim=true) |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/99) for more information.

---

### Release Notes

<details>
<summary>axios/axios (axios)</summary>

### [`v1.20.0`](https://github.com/axios/axios/releases/tag/v1.20.0)

[Compare Source](axios/axios@v1.19.0...v1.20.0)

#### v1.20.0 — August 19, 2026

This release hardens runtime option handling, adds RFC 9110 status-code aliases, fixes Node.js and XHR reliability issues, and refreshes project tooling and documentation.

#### ⚠️ Breaking Changes & Deprecations

- HTTP Status Naming: Added ContentTooLarge (413) and UnprocessableContent (422), while retaining PayloadTooLarge and UnprocessableEntity as backward-compatible deprecated aliases. ([#&#8203;11082](axios/axios#11082))

#### 🔒 Security Fixes

- Runtime Option Handling: Hardened behavioral configuration reads against shared and foreign prototype pollution and normalized unsafe interceptor replacement objects. This also clarifies Fetch redirect and custom implementation behavior, HTTP/2 DNS and proxy handling, CIDR-based NO_PROXY matching, and malformed data URI rejection; see the PR for documented compatibility effects. ([#&#8203;11141](axios/axios#11141))

#### 🐛 Bug Fixes

- Interceptor Lifecycle: Prevented unbounded handler-array growth by trimming trailing ejected interceptors without changing iteration semantics, and kept interceptor operations safe when the public handlers field is nullish. ([#&#8203;11087](axios/axios#11087), [#&#8203;11118](axios/axios#11118))
- Request Error Preservation: Prevented custom Error.prepareStackTrace implementations that return non-string values from replacing the original request failure with an unrelated TypeError. ([#&#8203;11109](axios/axios#11109))
- XHR Reliability: Navigation-canceled requests now reject with ECONNABORTED instead of resolving with status 0, while successful downloads flush their final progress callback during the live loadend dispatch. ([#&#8203;11094](axios/axios#11094), [#&#8203;11121](axios/axios#11121))
- Node.js Socket Memory: Removed request-context retention from per-socket error listeners, preventing completed response data from being pinned for the lifetime of pooled keep-alive sockets. ([#&#8203;11091](axios/axios#11091))
- Core Methods and HTTP Errors: Prevented structural method-header buckets from leaking into outgoing headers, standardized invalid DNS lookup and httpVersion failures as AxiosError.ERR_BAD_OPTION_VALUE, and corrected the timeoutErrorMessage merge strategy. ([#&#8203;11096](axios/axios#11096))

#### 🔧 Maintenance & Chores

- Dependencies: Updated fast-uri, postcss, js-yaml, mocha, development-tooling groups, and GitHub Actions dependencies. ([#&#8203;11092](axios/axios#11092), [#&#8203;11098](axios/axios#11098), [#&#8203;11099](axios/axios#11099), [#&#8203;11106](axios/axios#11106), [#&#8203;11107](axios/axios#11107), [#&#8203;11122](axios/axios#11122), [#&#8203;11123](axios/axios#11123), [#&#8203;11126](axios/axios#11126), [#&#8203;11127](axios/axios#11127), [#&#8203;11133](axios/axios#11133), [#&#8203;11140](axios/axios#11140), [#&#8203;11143](axios/axios#11143), [#&#8203;11144](axios/axios#11144))
- Documentation: Applied the v1.19.0 documentation updates, added the missing fs import to the README stream example, introduced localized global search, and repaired the interceptor test link. ([#&#8203;11101](axios/axios#11101), [#&#8203;11113](axios/axios#11113), [#&#8203;11097](axios/axios#11097), [#&#8203;11119](axios/axios#11119))
- Sponsorship: Updated sponsorship links and data and added ScrapingBee as a sponsor. ([#&#8203;11124](axios/axios#11124), [#&#8203;11136](axios/axios#11136), [#&#8203;11137](axios/axios#11137))
- CI and Release: Switched ESM smoke tests to locked dependencies and synchronized package and runtime version metadata for v1.20.0. ([#&#8203;11128](axios/axios#11128), [#&#8203;11152](axios/axios#11152))

#### 🌟 New Contributors

We are thrilled to welcome our new contributors. Thank you for helping improve axios:

- [@&#8203;yens1](https://github.com/yens1) ([#&#8203;11109](axios/axios#11109))
- [@&#8203;Sasireddy001](https://github.com/Sasireddy001) ([#&#8203;11113](axios/axios#11113))
- [@&#8203;ari-token-security](https://github.com/ari-token-security) ([#&#8203;11094](axios/axios#11094))
- [@&#8203;timothyokooboh](https://github.com/timothyokooboh) ([#&#8203;11097](axios/axios#11097))
- [@&#8203;gi9439041-png](https://github.com/gi9439041-png) ([#&#8203;11119](axios/axios#11119))
- [@&#8203;Hashim1999164](https://github.com/Hashim1999164) ([#&#8203;11082](axios/axios#11082))
- [@&#8203;v-dev-cl](https://github.com/v-dev-cl) ([#&#8203;11091](axios/axios#11091))
- [@&#8203;r0h1tb](https://github.com/r0h1tb) ([#&#8203;11118](axios/axios#11118))
- [@&#8203;ostapondo](https://github.com/ostapondo) ([#&#8203;11121](axios/axios#11121))

Full Changelog (<axios/axios@v1.19.0...v1.20.0>)

### [`v1.19.0`](https://github.com/axios/axios/blob/HEAD/CHANGELOG.md#v1190--July-22-2026)

[Compare Source](axios/axios@v1.18.1...v1.19.0)

This release raises the form-data security floor, adds configuration and type-system capabilities, and fixes NO_PROXY matching, interceptor errors, progress reporting, and serialization edge cases.

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 9am on tuesday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMzMuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjE2NC41IiwidGFyZ2V0QnJhbmNoIjoiZGV2ZWxvcCIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiLCJyZW5vdmF0ZSJdfQ==-->

Reviewed-on: https://code.futo.org/harbor/harbor/pulls/1808
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.

XHR adapter resolves navigation-canceled requests as success on Firefox 152+ (status 0, empty body)

2 participants