WHIRL v0.1.4 was released on October 5. WHIRL is a native Windows C++/HIP LLM inference engine for the Radeon AI PRO R9700.

The release describes itself as a bug-fix release with no new features and no speed changes, with output bit-identical to v0.1.3. The one changed default is CORS.

CORS that depends on the web page's origin

CORS (Cross-Origin Resource Sharing) is the rule a browser uses to decide whether a page from another origin may read a response. Before v0.1.4 every response carried Access-Control-Allow-Origin: *, so any page open in the browser could read the answers.

Now CORS headers go only to HTTP and HTTPS pages served from localhost, 127.0.0.1 or [::1], on any port. The page's Origin is echoed back with Vary: Origin, never *.

Affected are other origins such as https://app.example.com, pages served from another machine on the network, and file:// pages whose Origin is null. Their requests still reach the server, but the browser no longer hands the response to the page.

Clients that are not browser pages, such as the OpenAI and Anthropic SDKs, curl, IDE coding agents and scripts, do not use CORS and are unaffected.

The option for allowing other origins

To give a page from another origin access again, pass something like --cors-origin https://app.example.com, repeating the option once per origin.

--cors-origin "*" restores the allow-all behavior from before v0.1.4. A web UI served from a loopback address keeps working without any extra allowance.

CORS is not authentication

CORS headers only control whether the browser reads a response; they do not check who the caller is. The HTTP implementation keeps the choice of response headers separate from request handling.

The tag-pinned tests check that the loopback check rejects origins such as null, localhost.evil.com and 127.0.0.1.nip.io. This article read the source but did not run the tests.

Bind address and API keys

The server guide lists the default bind address and port as 127.0.0.1:8080 and states that any API key is accepted. So an API key field in a client does not mean credentials are checked.

Widening network exposure is a separate matter from allowing origins and calls for its own access-control review.

Handling a stalled streaming client

Previously, if a streaming client stopped reading, the engine could block on that connection and stall other users' slots too. Now the engine never waits for a client, and a connection that accepts no data for 30 seconds is dropped and its slot freed.

In the HTTP options send_timeout_ms also defaults to 30000, and the limit applies to stalled sending, not to inference time. It does not mean long answers must finish within 30 seconds.

The test checks that another request finishes with HTTP 200 in under a second while one stream goes unread, and that the slot goes idle after the stalled request is dropped. The timeout is shortened to 2 seconds for the test.

New HTTP limits

The release lists limits of 64 open connections, 60 seconds for the request line and headers, 30 seconds idle between reads of a request, and 64 KiB or 100 lines of headers. The request body and unsent response data buffered for a client are each capped at 64 MiB.

The GGUF parser's bounds checks can no longer overflow, and every tensor's type and shape is checked before GPU memory is allocated. These are documented changes, not results of load or security testing by this article.

Caveats: a narrow supported environment

Requirements are unchanged: Windows 11, Adrenalin 26.8.1 or newer, and one R9700 (gfx1201). The README supports only the qwen35 and qwen35moe architectures.

The source is under Apache License 2.0, which does not decide the license of separately downloaded models. The release carries whirl-0.1.4-windows-x64.zip and a SHA-256 file, and this article did not install or run the ZIP.

WHIRL v0.1.4 only narrows browser response reading to loopback origins and adds no authentication, so an upgrade calls for checking the web UI's origin and the server's network exposure separately.