What is the issue with the Fetch Standard?
Problem
Fetch's design reflects the security and implementation constraints of web browsers. This model produces constraints throughout the spec:
- Forbidden request-headers prevent scripts from spoofing
Origin, Cookie, Host, Referer, and other headers the browser must control.
- Forbidden response-header names prevent scripts from reading
Set-Cookie to protect the HttpOnly cookie boundary.
- CORS mediates cross-origin access, including preflight, response filtering (opaque/CORS filtered responses), and response tainting.
- Request modes (
no-cors, cors, same-origin, navigate) gate behavior based on the browser's origin model.
- Headers guards (
request, request-no-cors, response, immutable) enforce header restrictions at the API level.
- Service workers, CSP, COEP/CORP, mixed content, referrer policy, and deferred fetch/fetchLater are browser-specific mechanisms threaded through the core algorithms.
Non-browser JavaScript runtimes have adopted the Fetch API (fetch(), Request, Response, Headers) as a standard HTTP client and server interface. These runtimes operate in a different trust model:
- The sandboxing constraints differ from browsers. Some runtimes (e.g. Workers) do sandbox untrusted code, but under a different security model with different requirements. Other runtimes do not sandbox at all.
- There is no single global user. Browsers act on behalf of one user whose credentials (cookies, client certs) are attached to outgoing requests automatically. Server-side runtimes typically handle requests from many users and manage credentials per-request.
- There is no browser cookie jar. Cookie management is the application's responsibility.
- There is no same-origin policy. The application controls all outbound requests.
- There are no service workers intercepting fetches.
- There is no document, navigable, or browsing context.
These other runtimes have independently diverged from the fetch spec by relaxing forbidden headers, ignoring CORS, and stripping navigation-related properties. The divergence is largely undocumented and inconsistent between runtimes. Code that moves between runtimes encounters subtle incompatibilities, and none of these runtimes can claim Fetch spec conformance despite implementing substantial parts of the spec correctly.
Proposal: Posture
A posture is a property of the environment in which fetch() is invoked.
It is effectively a conformance profile.
The fetch posture can be one of:
'sandboxed': The current (default) posture. The environment enforces the browser's multi-origin sandboxing model. All existing spec behavior applies unchanged.
'unrestricted': The environment does not enforce multi-origin sandboxing. Browser-specific restrictions are relaxed as described below.
(we can bikeshed the names..)
The posture is simply a constant internal property of the runtime. It is not a new API or configurable option.
The default posture is 'sandboxed'. This means:
-
Existing spec text does not need to be updated to say "if sandboxed, do X." The existing text is the sandboxed behavior by default.
-
Only the 'unrestricted' deviations need explicit annotation, using language like:
If the fetch posture is 'unrestricted', skip this step.
-
Browser implementations remain fully compliant (or as compliant as they already are) without any changes.
Bottom line: The 'sandboxed' posture is the spec as it exists today.
The spec only needs new conditional text at the specific points where 'unrestricted' behavior diverges. If you don't support the 'unrestricted' posture, you ignore those branches.
What changes in 'unrestricted' posture
No forbidden Request-headers
In 'unrestricted' posture, no request-headers are forbidden. The forbidden request-header algorithm returns false for all headers.
Spec change: In the definition of "forbidden request-header" (2.2.2), add a short-circuit:
A header (name, value) is a forbidden request-header if the following conditions
are true:
- If the fetch posture is
'unrestricted', return false.
- [existing conditions]
API impact:
Headers.validate() step 3 becomes a no-op (guard 'request' never blocks).
- The
'request-no-cors' guard becomes equivalent to 'request' (but see 4.3).
Cookie, Cookie2, Origin, Host, Referer, Set-Cookie and all others can be freely set on outgoing requests.
- The method-override smuggling check (
X-HTTP-Method, etc.) also becomes a no-op.
No forbidden Response-headers
In 'unrestricted' posture, no response-heades are forbidden. Set-Cookie and Set-Cookie2 are visible on response headers.
Spec changes:
- In "forbidden response-header name" (2.2.2): gate the definition on sandboxed posture.
- In "basic filtered response" (2.2.6): do not strip forbidden response-header names in
unrestricted posture (or, more precisely, the set is empty, so there is nothing to strip).
Headers.validate() step 4 becomes a no-op (guard 'response' never blocks).
CORS is bypassed
In 'unrestricted' posture, the CORS protocol is not enforced. All requests are treated as if they are same-origin for the purposes of response tainting and filtering.
Spec changes: In main fetch (4.1), the response tainting decision tree changes:
- Response tainting is always
'basic' in unrestricted posture.
- The response is always a basic filtered response (never CORS-filtered or opaque).
- CORS preflight is never triggered.
- The CORS check algorithm is never invoked.
Access-Control-* response headers are not processed for filtering purposes (though they remain visible as ordinary headers on the response).
API impact:
Response.type is always 'default' or 'error' (never 'cors', 'opaque', or 'opaqueredirect').
- Redirected responses are fully visible (not opaque-redirect filtered).
- The
redirect: "manual" option returns a full, non-opaque redirect response with its actual status (3xx), headers (including Location), and body visible. In sandboxed posture, this produces an opaque-redirect filtered response per "Atomic HTTP redirect handling," a security measure to prevent scripts from observing redirect targets. This concern does not apply in unrestricted posture. The Response.type for a manually-intercepted redirect is 'default'.
Request modes are simplified
In 'unrestricted' posture:
RequestInit.mode is silently ignored. The internal request mode is effectively always 'cors' in the sense that no CORS-safelisted restrictions are applied, but the CORS enforcement is bypassed.
RequestInit.credentials is silently ignored. Credentials mode has no meaning without a cookie jar and CORS enforcement.
Browser-specific Request properties (mode, credentials, destination, referrer, referrerPolicy, keepalive, isReloadNavigation, isHistoryNavigation) follow a presence-optional, value-undefined rule:
- Implementations MAY include these properties on the
Request prototype, or MAY omit them.
- If a property IS present, its getter MUST return
undefined.
- If a property is NOT present, accessing it returns
undefined by normal JS semantics.
This avoids choosing between "absent" (breaks code that accesses without checking) and "present-but-fixed" (returns misleading values like 'cors' for mode). Either way, request.mode === "cors" evaluates to false, and request.mode === undefined or request.mode == null evaluates to true. Code that needs to feature-detect can use 'mode' in request, and code that doesn't check gets undefined rather than a misleading value.
The spec would express this as:
The mode getter steps are: if the fetch posture is 'unrestricted', return undefined.
Otherwise, [existing steps].
Headers guard simplification:
- The
'request-no-cors' guard is never used (since no-cors mode is not meaningful).
- Headers guards reduce to:
'none', 'request', 'response', 'immutable'.
- The
'request' guard imposes no restrictions (since forbidden request-headers are empty).
No forbidden methods
In 'unrestricted' posture, there are no forbidden methods. The Request constructor does not throw for CONNECT, TRACE, or TRACK.
Referrer and referrer policy are not applicable
In 'unrestricted' posture:
RequestInit.referrer and RequestInit.referrerPolicy are silently ignored.
- The "determine request's referrer" step in main fetch is skipped.
- No
Referer header is automatically appended (the application may set it manually via the
now-unrestricted headers).
Service Worker Interception is not applicable
In 'unrestricted' posture, the service worker interception block in HTTP fetch is skipped. The request's service-workers mode is effectively always 'none'.
Browser Security Policies are not applicable
In 'unrestricted' posture, the following main fetch steps are skipped:
- "report Content Security Policy violations for request"
- "should request be blocked by Content Security Policy"
- "should fetching request be blocked as mixed content"
- "Upgrade a mixed content request to a potentially trustworthy URL"
- HSTS upgrade
- Cross-Origin Resource Policy (CORP) check
- "nosniff" MIME type blocking
- Cross-Origin-Embedder-Policy (COEP) checks
Port Blocking is implementation-defined
In 'unrestricted' posture, port blocking ("should be blocked due to a bad port") is implementation-defined. Runtimes MAY choose to block bad ports or not.
RequestInit.cache default is implementation-defined
In 'unrestricted' posture, the default value for RequestInit.cache is implementation-defined rather than 'default'.
The browser's 'default' cache mode implies interaction with the browser's HTTP cache, which non-browser runtimes typically do not have. Runtimes like Deno default to 'no-store' or 'no-cache', while Cloudflare Workers has its own caching semantics. Rather than mandating a single default, the spec acknowledges that the appropriate default depends on the runtime's caching architecture. All valid RequestInit.cache values MUST be accepted without throwing; the runtime's actual caching behavior for each value is implementation-defined, but the corresponding Cache-Control request headers SHOULD be sent.
Subresource Integrity is implementation-defined
In 'unrestricted' posture, support for RequestInit.integrity is implementation-defined.
- Implementations MUST accept
undefined and '' (empty string) without throwing.
- If a non-empty integrity string is provided and the implementation does not support that integrity algorithm, it MUST throw (not silently ignore).
- Implementations MAY support any subset of integrity algorithms (including none beyond the empty string).
SRI is useful on the server (verifying downloaded artifacts, for instance) but is not universally needed. Making it implementation-defined allows runtimes to adopt it incrementally without being non-conformant.
Keepalive body size limits are implementation-defined
In 'unrestricted' posture, the keepalive body size limits enforced per fetch group in HTTP-network-or-cache fetch (4.6) are implementation-defined.
fetchLater / Deferred Fetch are not available
fetchLater(), FetchLaterResult, DeferredRequestInit, and the entire deferred fetch infrastructure (4.12) are not applicable in 'unrestricted' posture.
Navigation-related Request properties are presence-optional, value-undefined
The following request properties are not meaningful in 'unrestricted' posture and follow the presence-optional, value-undefined rule described in 4.4:
destination (no document destinations)
referrer, referrerPolicy (see 4.6)
isReloadNavigation, isHistoryNavigation
keepalive (tied to tab lifetime)
mode, credentials (see 4.4)
Implementations SHOULD include these on the Request prototype but MAY omit them. If present, their getters MUST return undefined.
file: URL Support is implementation-defined
In 'unrestricted' posture, fetch() of file: URLs is implementation-defined. Runtimes MAY
support it. The spec does not define the behavior but acknowledges it as a valid extension point.
Relative URL resolution is mplementation-Defined
In 'unrestricted' posture, relative URL resolution in fetch() is implementation-defined. Runtimes have varying base URL semantics (CWD, module URL, explicit base).
What does not change
Everything else that's not mentioned.
Spec integration pattern
Since 'sandboxed' is the default posture, existing spec text does not need to be wrapped in "if sandboxed" conditionals. Changes are only needed at the points where unrestricted behavior diverges, expressed as:
If the fetch posture is 'unrestricted', skip this step.
or:
If the fetch posture is 'unrestricted', [alternative behavior].
This keeps edits localized. The spec does not need a "if sandboxed, do X" conditional around every browser-specific step; those steps apply by default.
Conformance statement
The spec's conformance section should include:
A conformant implementation operates in one of two fetch posture:
- Sandboxed (default): The implementation enforces the multi-origin sandboxing model.
All normative requirements apply as written. This is the conformance mode for web browsers.
Any algorithm step or requirement that does not reference the fetch posture assumes sandboxed.
- Unrestricted: The implementation does not enforce the multi-origin sandboxing model.
Requirements that are explicitly conditioned on the unrestricted posture are modified or
skipped as specified. All other requirements apply as written.
An implementation MUST NOT mix postures within a single environment (e.g., a runtime cannot
enforce CORS for some requests and not others based on runtime conditions, unless it is
implementing the sandboxed posture and using the spec's existing conditional mechanisms like
request mode).
WPT implications
Web Platform Tests would need a mechanism to test both modes. For unrestricted-posture tests:
- Tests that verify forbidden headers are rejected should have unrestricted-posture counterparts that verify they are accepted.
- Tests for CORS enforcement should have unrestricted-posture counterparts that verify CORS is not enforced.
- Tests for opaque/filtered responses should have counterparts verifying basic responses.
Where is this coming from
I'm the one opening this issue but the proposal here is coming from the ECMA TC-55 / WinterTC, representing multiple Web compatible runtimes includes Node.js, Deno, Cloudflare Workers, etc.
What is the issue with the Fetch Standard?
Problem
Fetch's design reflects the security and implementation constraints of web browsers. This model produces constraints throughout the spec:
Origin,Cookie,Host,Referer, and other headers the browser must control.Set-Cookieto protect theHttpOnlycookie boundary.no-cors,cors,same-origin,navigate) gate behavior based on the browser's origin model.request,request-no-cors,response,immutable) enforce header restrictions at the API level.Non-browser JavaScript runtimes have adopted the Fetch API (
fetch(),Request,Response,Headers) as a standard HTTP client and server interface. These runtimes operate in a different trust model:These other runtimes have independently diverged from the fetch spec by relaxing forbidden headers, ignoring CORS, and stripping navigation-related properties. The divergence is largely undocumented and inconsistent between runtimes. Code that moves between runtimes encounters subtle incompatibilities, and none of these runtimes can claim Fetch spec conformance despite implementing substantial parts of the spec correctly.
Proposal: Posture
A posture is a property of the environment in which
fetch()is invoked.It is effectively a conformance profile.
The fetch posture can be one of:
'sandboxed': The current (default) posture. The environment enforces the browser's multi-origin sandboxing model. All existing spec behavior applies unchanged.'unrestricted': The environment does not enforce multi-origin sandboxing. Browser-specific restrictions are relaxed as described below.(we can bikeshed the names..)
The posture is simply a constant internal property of the runtime. It is not a new API or configurable option.
The default posture is
'sandboxed'. This means:Existing spec text does not need to be updated to say "if sandboxed, do X." The existing text is the sandboxed behavior by default.
Only the
'unrestricted'deviations need explicit annotation, using language like:Browser implementations remain fully compliant (or as compliant as they already are) without any changes.
Bottom line: The
'sandboxed'posture is the spec as it exists today.The spec only needs new conditional text at the specific points where
'unrestricted'behavior diverges. If you don't support the'unrestricted'posture, you ignore those branches.What changes in
'unrestricted'postureNo forbidden Request-headers
In
'unrestricted'posture, no request-headers are forbidden. Theforbidden request-headeralgorithm returns false for all headers.Spec change: In the definition of "forbidden request-header" (2.2.2), add a short-circuit:
API impact:
Headers.validate()step 3 becomes a no-op (guard'request'never blocks).'request-no-cors'guard becomes equivalent to'request'(but see 4.3).Cookie,Cookie2,Origin,Host,Referer,Set-Cookieand all others can be freely set on outgoing requests.X-HTTP-Method, etc.) also becomes a no-op.No forbidden Response-headers
In
'unrestricted'posture, no response-heades are forbidden.Set-CookieandSet-Cookie2are visible on response headers.Spec changes:
unrestricted posture (or, more precisely, the set is empty, so there is nothing to strip).
Headers.validate()step 4 becomes a no-op (guard'response'never blocks).CORS is bypassed
In
'unrestricted'posture, the CORS protocol is not enforced. All requests are treated as if they are same-origin for the purposes of response tainting and filtering.Spec changes: In main fetch (4.1), the response tainting decision tree changes:
'basic'in unrestricted posture.Access-Control-*response headers are not processed for filtering purposes (though they remain visible as ordinary headers on the response).API impact:
Response.typeis always'default'or'error'(never'cors','opaque', or'opaqueredirect').redirect: "manual"option returns a full, non-opaque redirect response with its actual status (3xx), headers (includingLocation), and body visible. In sandboxed posture, this produces an opaque-redirect filtered response per "Atomic HTTP redirect handling," a security measure to prevent scripts from observing redirect targets. This concern does not apply in unrestricted posture. TheResponse.typefor a manually-intercepted redirect is'default'.Request modes are simplified
In
'unrestricted'posture:RequestInit.modeis silently ignored. The internal request mode is effectively always'cors'in the sense that no CORS-safelisted restrictions are applied, but the CORS enforcement is bypassed.RequestInit.credentialsis silently ignored. Credentials mode has no meaning without a cookie jar and CORS enforcement.Browser-specific
Requestproperties (mode,credentials,destination,referrer,referrerPolicy,keepalive,isReloadNavigation,isHistoryNavigation) follow a presence-optional, value-undefined rule:Requestprototype, or MAY omit them.undefined.undefinedby normal JS semantics.This avoids choosing between "absent" (breaks code that accesses without checking) and "present-but-fixed" (returns misleading values like
'cors'formode). Either way,request.mode === "cors"evaluates tofalse, andrequest.mode === undefinedorrequest.mode == nullevaluates totrue. Code that needs to feature-detect can use'mode' in request, and code that doesn't check getsundefinedrather than a misleading value.The spec would express this as:
Headers guard simplification:
'request-no-cors'guard is never used (sinceno-corsmode is not meaningful).'none','request','response','immutable'.'request'guard imposes no restrictions (since forbidden request-headers are empty).No forbidden methods
In
'unrestricted'posture, there are no forbidden methods. TheRequestconstructor does not throw forCONNECT,TRACE, orTRACK.Referrer and referrer policy are not applicable
In
'unrestricted'posture:RequestInit.referrerandRequestInit.referrerPolicyare silently ignored.Refererheader is automatically appended (the application may set it manually via thenow-unrestricted headers).
Service Worker Interception is not applicable
In
'unrestricted'posture, the service worker interception block in HTTP fetch is skipped. The request'sservice-workers modeis effectively always'none'.Browser Security Policies are not applicable
In
'unrestricted'posture, the following main fetch steps are skipped:Port Blocking is implementation-defined
In
'unrestricted'posture, port blocking ("should be blocked due to a bad port") is implementation-defined. Runtimes MAY choose to block bad ports or not.RequestInit.cachedefault is implementation-definedIn
'unrestricted'posture, the default value forRequestInit.cacheis implementation-defined rather than'default'.The browser's
'default'cache mode implies interaction with the browser's HTTP cache, which non-browser runtimes typically do not have. Runtimes like Deno default to'no-store'or'no-cache', while Cloudflare Workers has its own caching semantics. Rather than mandating a single default, the spec acknowledges that the appropriate default depends on the runtime's caching architecture. All validRequestInit.cachevalues MUST be accepted without throwing; the runtime's actual caching behavior for each value is implementation-defined, but the correspondingCache-Controlrequest headers SHOULD be sent.Subresource Integrity is implementation-defined
In
'unrestricted'posture, support forRequestInit.integrityis implementation-defined.undefinedand''(empty string) without throwing.SRI is useful on the server (verifying downloaded artifacts, for instance) but is not universally needed. Making it implementation-defined allows runtimes to adopt it incrementally without being non-conformant.
Keepalive body size limits are implementation-defined
In
'unrestricted'posture, the keepalive body size limits enforced per fetch group in HTTP-network-or-cache fetch (4.6) are implementation-defined.fetchLater / Deferred Fetch are not available
fetchLater(),FetchLaterResult,DeferredRequestInit, and the entire deferred fetch infrastructure (4.12) are not applicable in'unrestricted'posture.Navigation-related Request properties are presence-optional, value-undefined
The following request properties are not meaningful in
'unrestricted'posture and follow the presence-optional, value-undefined rule described in 4.4:destination(no document destinations)referrer,referrerPolicy(see 4.6)isReloadNavigation,isHistoryNavigationkeepalive(tied to tab lifetime)mode,credentials(see 4.4)Implementations SHOULD include these on the
Requestprototype but MAY omit them. If present, their getters MUST returnundefined.file:URL Support is implementation-definedIn
'unrestricted'posture,fetch()offile:URLs is implementation-defined. Runtimes MAYsupport it. The spec does not define the behavior but acknowledges it as a valid extension point.
Relative URL resolution is mplementation-Defined
In
'unrestricted'posture, relative URL resolution infetch()is implementation-defined. Runtimes have varying base URL semantics (CWD, module URL, explicit base).What does not change
Everything else that's not mentioned.
Spec integration pattern
Since
'sandboxed'is the default posture, existing spec text does not need to be wrapped in "if sandboxed" conditionals. Changes are only needed at the points where unrestricted behavior diverges, expressed as:or:
This keeps edits localized. The spec does not need a "if sandboxed, do X" conditional around every browser-specific step; those steps apply by default.
Conformance statement
The spec's conformance section should include:
WPT implications
Web Platform Tests would need a mechanism to test both modes. For unrestricted-posture tests:
Where is this coming from
I'm the one opening this issue but the proposal here is coming from the ECMA TC-55 / WinterTC, representing multiple Web compatible runtimes includes Node.js, Deno, Cloudflare Workers, etc.