Skip to content

fix(fetch): only send Sec-Fetch-Mode to potentially trustworthy URLs - #5738

Merged
tsctx merged 2 commits into
nodejs:mainfrom
kjsik11:fix/fetch-metadata-trustworthy
Sep 4, 2026
Merged

fix(fetch): only send Sec-Fetch-Mode to potentially trustworthy URLs#5738
tsctx merged 2 commits into
nodejs:mainfrom
kjsik11:fix/fetch-metadata-trustworthy

Conversation

@kjsik11

@kjsik11 kjsik11 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

This relates to...

Refs #1305, in particular #1305 (comment).

Spec: https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-append-the-fetch-metadata-headers-for-a-request

Rationale

appendFetchMetadata implements the Sec-Fetch-Mode sub-algorithm but skips step 1 of the outer algorithm, which the code marks as TODO:

To append the Fetch metadata headers for a request, given request r:

  1. If r's url is not an potentially trustworthy URL, return.

So fetch('http://192.168.0.12/') sends sec-fetch-mode: cors, while browsers send no Sec-Fetch-* header to plain-http, non-loopback destinations. Reproduces on main and on the undici bundled with current Node releases.

Repro (Node built-ins only)
import http from 'node:http'
import os from 'node:os'
import { once } from 'node:events'

const lanIp = Object.values(os.networkInterfaces()).flat()
  .find((i) => i.family === 'IPv4' && !i.internal).address
const server = http.createServer((req, res) => {
  res.end(JSON.stringify({ 'sec-fetch-mode': req.headers['sec-fetch-mode'] ?? null }))
})
server.listen(0, lanIp)
await once(server, 'listening')
console.log(await (await fetch(`http://${lanIp}:${server.address().port}/`)).json())
server.close()

Prints { 'sec-fetch-mode': 'cors' }; with this PR the header is absent. https://, http://localhost and http://127.0.0.1 keep sending it.

Changes

  • lib/web/fetch/util.js: early return using the existing isURLPotentiallyTrustworthy() helper on the request's current URL, so the gate applies per redirect hop (matches browsers and WPT fetch/metadata/redirect/*). The Sec-Fetch-Dest/Site/User TODOs are untouched and will inherit the gate.
  • test/fetch/fetch-metadata.js (new): header absent for plain-http non-loopback origins, present for https:// and loopback, dropped/added across https -> http / http -> https redirects. 4 of the 7 cases fail on main.

Features

N/A

Bug Fixes

  • Sec-Fetch-Mode is no longer sent to URLs that are not potentially trustworthy (plain http:///ws:// to non-loopback hosts), per Fetch Metadata step 1.

Breaking Changes and Deprecations

N/A

Test results (Node 24.20.0)

  • npm run lint clean; test/fetch/fetch-metadata.js 7/7 (4/7 fail on main)
  • test/fetch/*.js 483/483
  • test/mock-agent.js 111/111, test/proxy-agent.js 38/38, test/eventsource/eventsource-connect.js 11/11 — every existing assertion on this header targets localhost

This fix was prepared with AI assistance; I reproduced the bug locally and reviewed every line.

Status

appendFetchMetadata skipped step 1 of "append the Fetch metadata headers
for a request"
(https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-append-the-fetch-metadata-headers-for-a-request):

  1. If r's url is not a potentially trustworthy URL, return.

and left the corresponding "Assert: r's url is a potentially trustworthy
URL" step of the Sec-Fetch-Mode algorithm as a TODO, so Sec-Fetch-Mode was
sent to every destination, including plain-http, non-loopback hosts such
as http://192.168.0.12/. Browsers do not send any Sec-Fetch-* header
there.

Add the early return using the existing isURLPotentiallyTrustworthy()
helper on the request's current URL, so the check is applied per redirect
hop like the referrer-policy code and like Chromium/Firefox. https/wss,
localhost, *.localhost, 127.0.0.0/8 and [::1] destinations are unchanged.

The three WPT cases in fetch/metadata/generated/fetch.sub.html
("sec-fetch-mode - Not sent to non-trustworthy ... destination, no init")
now pass; test/web-platform-tests/expectation.json is updated accordingly.

Refs: nodejs#1305 (comment)
Signed-off-by: jongsik <kjsik11@gmail.com>
Comment thread test/fetch/fetch-metadata.js Outdated
@codecov-commenter

codecov-commenter commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.50%. Comparing base (fc3450d) to head (fe69384).
⚠️ Report is 25 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5738      +/-   ##
==========================================
+ Coverage   93.47%   93.50%   +0.03%     
==========================================
  Files         110      110              
  Lines       38908    39077     +169     
==========================================
+ Hits        36368    36538     +170     
+ Misses       2540     2539       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

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

@kjsik11
kjsik11 requested a review from tsctx September 4, 2026 08:12
@tsctx
tsctx merged commit eb04cc3 into nodejs:main Sep 4, 2026
37 of 38 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 4, 2026
@kjsik11
kjsik11 deleted the fix/fetch-metadata-trustworthy branch September 4, 2026 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants