WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
NEW
320657
Preflight OPTIONS fetch hangs indefinitely on streaming body response
https://bugs.webkit.org/show_bug.cgi?id=320657
Summary
Preflight OPTIONS fetch hangs indefinitely on streaming body response
Dave Jones
Reported
2026-07-30 07:36:05 PDT
Created
attachment 480890
[details]
Python script to run locally to reproduce bad OPTIONS behavior # Description If an OPTIONS (CORS preflight) fetch() is sent and the server response is a byte stream (such as streaming audio/video) instead of a proper 204/200 response, WebKit/Safari hangs indefinitely, streaming the bytes to nowhere until the page aborts or a network drop occurs. Chromium and Firefox correctly base the OPTIONS request status success on the response headers being valid, ignore the body stream, close the connection and move on. This behavior was first observed on a Live365 Icecast stream which incorrectly serves streaming audio in response to an OPTIONS preflight request. While this is not correct behavior for a server response, it also constitutes an unintentional denial of service path against Webkit/Safari and a general robustness problem since this manifests as a silent page hang. Per the Fetch Standard's CORS-preflight fetch algorithm (
https://fetch.spec.whatwg.org/#cors-preflight-fetch
), a preflight response is evaluated from its status ("restricted to an ok status") and its Access-Control-Allow-* headers. No step of the algorithm consumes the response body, so the body's lifetime should therefore not control the outcome of the preflight request. # Steps to reproduce: ## Online Demo I have a test page and server set up that allows quickly reproducing the problem:
https://public.podcastindex.org/webkit-preflight-bug-public.html
This page just streams bytes, not audio. The audio is not the problem. Any unterminated byte stream triggers it. ## Local Machine 1. `python3 webkit-preflight-repro.py` (attached; stdlib only). It serves a test page on
http://127.0.0.1:8788/
and a cross-origin "stream server" on 127.0.0.1:8787 with two endpoints, byte-identical except for the OPTIONS response: - `/good` answers OPTIONS with a bodyless 204 + CORS headers - `/bad` answers OPTIONS with 200 + the same CORS headers and then an infinite body. 2. Open
http://127.0.0.1:8788/
in Safari and click Run. The page fetches both endpoints with a custom `X-Demo: 1` header (forcing a preflight) and a 15-second abort timer. ## Tested on: - Safari on iOS v26, on device - WebKit via Playwright build v2248 (recent trunk), Linux ## Real-world impact: Live365's streaming CDN edges (das-edge*.cdnstream.com, "Server: DAS") answer CORS preflights exactly like `/bad` with correct Access-Control-* headers followed by the live audio stream as the OPTIONS body. As a result, any web page that reads ICY now-playing metadata from any Live365 station (a fetch carrying an `Icy-MetaData: 1` header, hence preflighted) hangs in Safari and every iOS browser, while working fine in Chrome and Firefox. We are reporting the server-side misbehavior to the vendor separately, but WebKit is the only engine the misbehavior takes down. Verify against the real server (while unfixed) with: ``` curl -m 10 -X OPTIONS '
https://das-edge15-live365-dal02.cdnstream.com/a80899
' \ -H 'Origin:
https://example.com
' \ -H 'Access-Control-Request-Method: GET' \ -H 'Access-Control-Request-Headers: icy-metadata' \ -o preflight_body.bin ; ls -la preflight_body.bin # downloads live MP3 from an OPTIONS request until the 10 s kill ```
Attachments
Python script to run locally to reproduce bad OPTIONS behavior
(6.11 KB, text/x-python)
2026-07-30 07:36 PDT
,
Dave Jones
no flags
Details
View All
Add attachment
proposed patch, testcase, etc.
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug