Skip to content

serve: add CORS preflight (OPTIONS), reverse-proxy header support, and X-Accel-Buffering for SSE - #321

Closed
BlitzenCats wants to merge 1 commit into
Niko1221:mainfrom
BlitzenCats:feat/reverse-proxy-cors
Closed

BlitzenCats wants to merge 1 commit into
Niko1221:mainfrom
BlitzenCats:feat/reverse-proxy-cors

Conversation

@BlitzenCats

Copy link
Copy Markdown
Contributor

Summary

This PR adds support for web-based API clients (CORS) and reverse proxies / tunnels (such as Cloudflare Tunnel, Tailscale Funnel, and Nginx) when remotely serving Strata or connecting third-party web frontends.

Key Changes

  1. CORS & Preflight (\OPTIONS\ 204 No Content):

    • Implements \do_OPTIONS\ returning \204 No Content\ with \Access-Control-Allow-Origin: *, \Access-Control-Allow-Methods: GET, POST, OPTIONS, and \Access-Control-Allow-Headers: *.
    • Without this, browser-based clients (such as Open WebUI, LibreChat, Chatbox, or browser extensions) making CORS preflight checks received \501 Unsupported method ('OPTIONS').
    • Adds standard CORS headers to JSON responses.
  2. Reverse Proxy Origin Validation:

    • Updates _own_page\ in \serve/server.py\ to inspect \X-Forwarded-Host\ and recognize proxy headers (\CF-Ray, \CF-Connecting-IP, \X-Forwarded-For).
    • Reverse proxies typically rewrite \Host: 127.0.0.1:8080\ while retaining \Origin: https://. Previously, this mismatch caused _own_page\ to trigger \403 Forbidden\ on settings and MCP tools when accessing Strata's web UI remotely through a tunnel.
  3. SSE Stream Buffering (\X-Accel-Buffering: no):

    • Adds \X-Accel-Buffering: no\ to _sse\ response headers to instruct edge/reverse proxies (Cloudflare, Nginx) not to buffer tokens, ensuring real-time streaming over Server-Sent Events.
  4. Unit Tests:

    • Added tests in \serve/test_server.py\ covering CORS preflights, response headers, and reverse-proxy forwarded origin handling (all 80+ server unit tests pass).

Authored by @BlitzenCats & Gemini

…d X-Accel-Buffering for SSE

- Implement do_OPTIONS (204 No Content) and add Access-Control-Allow-* headers to allow web-based clients (Open WebUI, LibreChat, browser extensions) to query endpoints.
- Update _own_page to recognize reverse proxies and tunnels (Cloudflare Tunnel, Tailscale Funnel, Nginx) via X-Forwarded-Host, CF-Ray, and forwarded headers, preventing 403 CSRF false-positives when serving remotely.
- Add X-Accel-Buffering: no to SSE streaming responses to prevent reverse proxies from buffering real-time token generation.
- Add unit tests in serve/test_server.py covering CORS preflight, CORS headers, and reverse proxy origin checks.

Co-authored-by: Gemini <gemini@google.com>
@Niko1221

Niko1221 commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Thanks! This is in the 0.1.32 batch. We take it without the origin bypass on X-Forwarded-For / CF-* headers: with OPTIONS open to every origin, any web page could send such a header and then change settings or run MCP tools. CORS on /v1/* stays opt-in with a configured list of trusted origins; X-Accel-Buffering and the proxy headers stay; every change goes through our identity gate (byte-identical output on four models) and a speed A/B before it ships.

Niko1221 added a commit that referenced this pull request Oct 1, 2026
…e bypass for proxy headers

- The Origin check of /settings and the MCP tools let any page through when the request carried X-Forwarded-For,
  CF-Ray or CF-Connecting-IP (headers every reverse proxy or tunnel adds), and compared hosts without their ports
  (a page on another local port passed): any web page could then change the shared settings or run MCP tools with the
  user's rights. Removed. The Origin must be this server's host:port, or an origin in the config's new
  "trusted_origins" (the web app behind a proxy or tunnel); a proxy header alone opens nothing.
- CORS is opt-in: "cors_origins" in the config (an origin, a list, or "*"). Only /v1/* answers with
  Access-Control-Allow-Origin (the origin itself, or "*"), never /settings, /unload or the app's files; the
  preflight echoes the headers asked for ("*" does not cover Authorization). OPTIONS is answered 204 either way.
- X-Accel-Buffering: no on SSE stays. origins_of() checks both lists at start (scheme://host[:port], no paths;
  "*" only for cors_origins). docs/DETAILS.md; tests for the bypasses, the trusted origin, CORS off/on, the header.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0154bwiad8gY7wRehCXy27pN
@Niko1221

Niko1221 commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Released in 0.1.32 without the X-Forwarded-For / CF-* origin bypass: CORS on /v1/* is opt-in with a trusted-origin list. Merged under your name (GitHub doesn't show it as merged because it went in by hand). Thank you!

@Niko1221 Niko1221 closed this Oct 1, 2026
Niko1221 added a commit that referenced this pull request Oct 6, 2026
…e bypass for proxy headers

- The Origin check of /settings and the MCP tools let any page through when the request carried X-Forwarded-For,
  CF-Ray or CF-Connecting-IP (headers every reverse proxy or tunnel adds), and compared hosts without their ports
  (a page on another local port passed): any web page could then change the shared settings or run MCP tools with the
  user's rights. Removed. The Origin must be this server's host:port, or an origin in the config's new
  "trusted_origins" (the web app behind a proxy or tunnel); a proxy header alone opens nothing.
- CORS is opt-in: "cors_origins" in the config (an origin, a list, or "*"). Only /v1/* answers with
  Access-Control-Allow-Origin (the origin itself, or "*"), never /settings, /unload or the app's files; the
  preflight echoes the headers asked for ("*" does not cover Authorization). OPTIONS is answered 204 either way.
- X-Accel-Buffering: no on SSE stays. origins_of() checks both lists at start (scheme://host[:port], no paths;
  "*" only for cors_origins). docs/DETAILS.md; tests for the bypasses, the trusted origin, CORS off/on, the header.
anon761 pushed a commit to anon761/Strata-upstream that referenced this pull request Oct 6, 2026
… own-page bypass for proxy headers

- The Origin check of /settings and the MCP tools let any page through when the request carried X-Forwarded-For,
  CF-Ray or CF-Connecting-IP (headers every reverse proxy or tunnel adds), and compared hosts without their ports
  (a page on another local port passed): any web page could then change the shared settings or run MCP tools with the
  user's rights. Removed. The Origin must be this server's host:port, or an origin in the config's new
  "trusted_origins" (the web app behind a proxy or tunnel); a proxy header alone opens nothing.
- CORS is opt-in: "cors_origins" in the config (an origin, a list, or "*"). Only /v1/* answers with
  Access-Control-Allow-Origin (the origin itself, or "*"), never /settings, /unload or the app's files; the
  preflight echoes the headers asked for ("*" does not cover Authorization). OPTIONS is answered 204 either way.
- X-Accel-Buffering: no on SSE stays. origins_of() checks both lists at start (scheme://host[:port], no paths;
  "*" only for cors_origins). docs/DETAILS.md; tests for the bypasses, the trusted origin, CORS off/on, the header.
anon761 pushed a commit to anon761/Strata-upstream that referenced this pull request Oct 6, 2026
… own-page bypass for proxy headers

- The Origin check of /settings and the MCP tools let any page through when the request carried X-Forwarded-For,
  CF-Ray or CF-Connecting-IP (headers every reverse proxy or tunnel adds), and compared hosts without their ports
  (a page on another local port passed): any web page could then change the shared settings or run MCP tools with the
  user's rights. Removed. The Origin must be this server's host:port, or an origin in the config's new
  "trusted_origins" (the web app behind a proxy or tunnel); a proxy header alone opens nothing.
- CORS is opt-in: "cors_origins" in the config (an origin, a list, or "*"). Only /v1/* answers with
  Access-Control-Allow-Origin (the origin itself, or "*"), never /settings, /unload or the app's files; the
  preflight echoes the headers asked for ("*" does not cover Authorization). OPTIONS is answered 204 either way.
- X-Accel-Buffering: no on SSE stays. origins_of() checks both lists at start (scheme://host[:port], no paths;
  "*" only for cors_origins). docs/DETAILS.md; tests for the bypasses, the trusted origin, CORS off/on, the header.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0154bwiad8gY7wRehCXy27pN
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.

2 participants