nodejs / nodejs/undici

`fetch()` fails or returns an opaque response for a redirected cross-origin URL

Open
#4,740 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug fetch
Dominant language
JavaScript
Stars
7.7k
Forks
879
Avg merge
2d 16h
Merged PRs (30d)
68

Description

Bug Description

fetch() fails or returns an opaque response when it takes a redirected cross-origin URL, while it always returns a basic (transparent) response when the cross-origin URL is not redirected.

Reproducible By

Test results 1-3 are expected. Test results 4-9 are not?

import http from 'http'
import { fetch, setGlobalOrigin } from 'undici'

function createServer(same, cross) {
  http.createServer((req, res) => {
    if (req.url === '/redirected') {
        return res.writeHead(301, { Location: `http://localhost:${cross}` }).end()
    }
    return res.writeHead(200).end()
  })
  .listen(same)
}

createServer(8080, 8081)
createServer(8081, 8080)

const origin = 'http://localhost:8080'
const crossOrigin = 'http://localhost:8081'

setGlobalOrigin(origin)

const tests = [
    // Not redirected
    [`${crossOrigin}/foo`],                                 // 1. basic
    [`${crossOrigin}/foo`, { mode: 'same-origin' }],        // 2. basic
    [`${crossOrigin}/foo`, { mode: 'no-cors' }],            // 3. basic
    // Redirected from cross-origin to same-origin
    [`${crossOrigin}/redirected`],                          // 4. cors
    [`${crossOrigin}/redirected`, { mode: 'same-origin' }], // 5. fetch failed
    [`${crossOrigin}/redirected`, { mode: 'no-cors' }],     // 6. opaque
    // Redirected from same-origin to cross-origin
    [`${origin}/redirected`],                               // 7. cors
    [`${origin}/redirected`, { mode: 'same-origin' }],      // 8. fetch failed
    [`${origin}/redirected`, { mode: 'no-cors' }],          // 9. opaque
]
.map(async ([url, init]) => {
    try {
        const response = await fetch(url, init)
        return response.type
    } catch (e) {
        return e.message
    }
})
const results = await Promise.all(tests)
console.log(results)

Note that for browsers, returning an opaque response to a request of a cross-origin URL redirected to a same-origin URL is expected (https://github.com/whatwg/fetch/issues/737).

Expected Behavior

I would expect fetch() to allow requests to any origin regardless of any redirection:

Unlike browsers, Undici does not implement CORS (Cross-Origin Resource Sharing) checks by default. This means:

  • [...]
  • Requests to any origin are allowed regardless of the source

https://undici.nodejs.org/#/?id=specification-compliance

Logs & Screenshots

Environment

Node v24.12.0, Undici v7.18.2

Additional context

While debugging undici.fetch() taking a redirected URL, this line in mainFetch() intrigued me, because it does not match the origin of the current URL with request.origin, as required in the spec, but against the origin of request.url:

request’s current URL’s origin is same origin with request’s origin, and request’s response tainting is "basic"

I am not very familiar with the Fetch specification but I guess this difference is intentional.

Now assuming consistency between non-redirected and redirected cross-origin request is a valid expectation, I do not know how much undici could be allowed to deviate from the specification.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at lib/web/fetch/index.js, particularly mainFetch() around the linked origin check, and run the provided HTTP-server reproduction with Undici's fetch(). Compare redirected and non-redirected behavior with the Fetch specification and determine the intended handling; done means the behavior is resolved consistently and covered by regression tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.