Skip to content

fix(netguard): allow operator-configured origins in local mode - #490

Open
tylerpotts wants to merge 1 commit into
mainfrom
fix/netguard-allowed-origins
Open

fix(netguard): allow operator-configured origins in local mode#490
tylerpotts wants to merge 1 commit into
mainfrom
fix/netguard-allowed-origins

Conversation

@tylerpotts

Copy link
Copy Markdown
Collaborator

Fixes #489

Summary

Local mode rejects any request whose Origin header is not a loopback origin (netguard, since #459). When nebi is served through a reverse proxy on a public hostname, the JupyterHub data-science-pack being the shipping example, browsers send the proxy's origin on CORS-mode subresource requests: Vite emits the SPA bundle as <script type="module" crossorigin> / <link crossorigin>. Every JS/CSS asset request therefore returned 403 and clicking the Nebi tile rendered a blank page, while the page navigation itself (no Origin header) and curl-based smoke tests passed.

This adds server.allowed_origins (NEBI_SERVER_ALLOWED_ORIGINS), a comma-separated allowlist of additional origins honored in two places, both of which are required for the browser to load the SPA:

  • netguard.Middleware accepts listed origins alongside loopback ones (request-side 403 fix)
  • corsMiddleware echoes listed origins in Access-Control-Allow-Origin (response side; crossorigin fetches fail in the browser without it even on a 200)

Matching is case-insensitive and tolerates surrounding whitespace and trailing slashes. Default is empty, so behavior is unchanged unless the operator opts in. The desktop app path (app.go) passes no extra origins.

Companion change: nebari-dev/data-science-pack will inject NEBI_SERVER_ALLOWED_ORIGINS=https://<hub-host> into user pods via the spawner (PR to follow on nebari-dev/data-science-pack#202).

Test plan

  • netguard unit tests: configured origins accepted (incl. case-insensitivity), unlisted/scheme-mismatched origins still 403, loopback and absent-Origin behavior unchanged, allowlist does not bypass the Host check
  • CORS tests: configured origin echoed with Vary: Origin, unlisted origin gets no ACAO, local mode still never wildcards, team mode unchanged
  • Config tests: env var parsing, whitespace/empty handling
  • Router-level test driving the real router with server.allowed_origins set
  • go test ./internal/... all green
  • Reproduced the original failure on a live JupyterHub cluster (curl -H "Origin: https://hub..." -> 403 with the exact 64-byte netguard body observed at the Envoy gateway) confirming the diagnosis this fix targets

Local mode rejects any request whose Origin header is not a loopback
origin. Behind a reverse proxy on a public hostname (JupyterHub's
jupyter-server-proxy in the data-science-pack), browsers send the proxy's
origin on CORS-mode subresource requests - Vite emits the SPA bundle as
<script type=module crossorigin> - so every JS/CSS asset request got a
403 and the UI rendered a blank page (#489).

Add server.allowed_origins (NEBI_SERVER_ALLOWED_ORIGINS), a comma-
separated allowlist honored by both netguard's request check and the
CORS response headers, which crossorigin fetches also require.
@netlify

netlify Bot commented Aug 3, 2026

Copy link
Copy Markdown

Deploy Preview for nebi-docs canceled.

Name Link
🔨 Latest commit 5b71ccc
🔍 Latest deploy log https://app.netlify.com/projects/nebi-docs/deploys/6a710cd26d316500086d6eb6

tylerpotts added a commit to nebari-dev/data-science-pack that referenced this pull request Aug 3, 2026
nebi local mode rejects non-loopback Origin headers, which browsers send
on the SPA's crossorigin asset requests, blanking the Nebi tile
(nebari-dev/nebi#489). Inject NEBI_SERVER_ALLOWED_ORIGINS with the hub's
public origin; requires a nebi build with server.allowed_origins
(nebari-dev/nebi#490), older builds ignore the variable.
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.

netguard Origin check breaks the web UI behind jupyter-server-proxy (blank page, assets 403)

1 participant