fix(csp): scope connect-src to API/OIDC origins so all sub-paths are allowed (MAPPS-369) #428

Merged
nrupard merged 1 commit from fix/MAPPS-369-csp-connect-src-origin into main 2026-07-15 17:02:09 +02:00
Owner

Fixes MAPPS-369.

The mokosh-www runtime CSP built connect-src straight from the operator env: connect-src 'self' {$MOKOSH_API_BASE:-} {$MOKOSH_OIDC_ISSUER:-} (oci-build/Caddyfile). Two defects made cross-origin API calls fail:

  1. MOKOSH_API_BASE is the full API base including its /api/v1 path (the SPA's api_base() appends /auth/login, /ready, ... to it). A CSP host-source whose path does not end in / is an exact-path match (CSP3 path-part matching), so only /api/v1 itself was allowed and every real API call was blocked. The browser aborts the fetch before it is sent, which the SPA surfaces as ApiError::Network ("Network error. Check your connection and try again.") with nothing reaching the server. This broke all cross-origin API access: any self-hosted or split SPA/API deployment.

  2. Caddy's env-placeholder default syntax is {$VAR:default}, so {$VAR:-} sets the default to a literal - (not empty, as the old comment claimed). An unset OIDC issuer therefore injected a junk - / http://- source.

Found while smoke-testing the v0.7.0 self-host compose (standalone login, no bunyip): login failed with "Network error" and clean server logs; the browser console reported the connect-src violation at http://localhost:25252/api/v1/ready.

Fix

  • oci-build/entrypoint.sh: new origin_of() reduces a URL to scheme://host[:port] (drops path/query; empty for a relative or empty value, which is same-origin and covered by 'self'). It derives MOKOSH_API_ORIGIN / MOKOSH_OIDC_ORIGIN from MOKOSH_API_BASE / MOKOSH_OIDC_ISSUER and exports them before execing Caddy.
  • oci-build/Caddyfile: connect-src now reads 'self' {$MOKOSH_API_ORIGIN:} {$MOKOSH_OIDC_ORIGIN:} - origin-scoped so all API/OIDC sub-paths match, with correct empty-default syntax so an unset origin drops out cleanly.
  • Operator-facing MOKOSH_API_BASE / MOKOSH_OIDC_ISSUER are unchanged; the origin vars are internal and derived.

Note: the canonical msp.<tld> deploy is cross-origin (SPA msp.<tld> calls API api.msp.<tld>) and relies on MOKOSH_API_BASE being set (its entrypoint does) so its origin appears in connect-src. A deploy that leaves MOKOSH_API_BASE unset and relies purely on the SPA's runtime host-prefix derivation would still have no API origin in connect-src; that path is same-as-before (no regression) and documented inline.

Verification

  • sh -n oci-build/entrypoint.sh passes.
  • origin_of returns the expected origin for absolute URLs with the port preserved (http://localhost:25252/api/v1 -> http://localhost:25252, https://api.msp.example:8443/api/v1 -> https://api.msp.example:8443) and empty for relative (/api/v1) and empty input.
  • caddy validate on the Caddyfile passes with MOKOSH_OIDC_ORIGIN both set and unset.

Manual test

Self-host stack (SPA :8080 + API :25252, different origins), MOKOSH_API_BASE=http://localhost:25252/api/v1: POST /api/v1/auth/login and subsequent /api/v1/* calls complete with no connect-src violation; console shows connect-src 'self' http://localhost:25252 with no path and no - token.

Fixes MAPPS-369. The mokosh-www runtime CSP built `connect-src` straight from the operator env: `connect-src 'self' {$MOKOSH_API_BASE:-} {$MOKOSH_OIDC_ISSUER:-}` (`oci-build/Caddyfile`). Two defects made cross-origin API calls fail: 1. `MOKOSH_API_BASE` is the full API base including its `/api/v1` path (the SPA's `api_base()` appends `/auth/login`, `/ready`, ... to it). A CSP host-source whose path does not end in `/` is an exact-path match (CSP3 path-part matching), so only `/api/v1` itself was allowed and every real API call was blocked. The browser aborts the fetch before it is sent, which the SPA surfaces as `ApiError::Network` ("Network error. Check your connection and try again.") with nothing reaching the server. This broke all cross-origin API access: any self-hosted or split SPA/API deployment. 2. Caddy's env-placeholder default syntax is `{$VAR:default}`, so `{$VAR:-}` sets the default to a literal `-` (not empty, as the old comment claimed). An unset OIDC issuer therefore injected a junk `-` / `http://-` source. Found while smoke-testing the v0.7.0 self-host compose (standalone login, no bunyip): login failed with "Network error" and clean server logs; the browser console reported the `connect-src` violation at `http://localhost:25252/api/v1/ready`. ## Fix - `oci-build/entrypoint.sh`: new `origin_of()` reduces a URL to `scheme://host[:port]` (drops path/query; empty for a relative or empty value, which is same-origin and covered by `'self'`). It derives `MOKOSH_API_ORIGIN` / `MOKOSH_OIDC_ORIGIN` from `MOKOSH_API_BASE` / `MOKOSH_OIDC_ISSUER` and exports them before `exec`ing Caddy. - `oci-build/Caddyfile`: `connect-src` now reads `'self' {$MOKOSH_API_ORIGIN:} {$MOKOSH_OIDC_ORIGIN:}` - origin-scoped so all API/OIDC sub-paths match, with correct empty-default syntax so an unset origin drops out cleanly. - Operator-facing `MOKOSH_API_BASE` / `MOKOSH_OIDC_ISSUER` are unchanged; the origin vars are internal and derived. Note: the canonical `msp.<tld>` deploy is cross-origin (SPA `msp.<tld>` calls API `api.msp.<tld>`) and relies on `MOKOSH_API_BASE` being set (its entrypoint does) so its origin appears in connect-src. A deploy that leaves `MOKOSH_API_BASE` unset and relies purely on the SPA's runtime host-prefix derivation would still have no API origin in connect-src; that path is same-as-before (no regression) and documented inline. ## Verification - `sh -n oci-build/entrypoint.sh` passes. - `origin_of` returns the expected origin for absolute URLs with the port preserved (`http://localhost:25252/api/v1` -> `http://localhost:25252`, `https://api.msp.example:8443/api/v1` -> `https://api.msp.example:8443`) and empty for relative (`/api/v1`) and empty input. - `caddy validate` on the Caddyfile passes with `MOKOSH_OIDC_ORIGIN` both set and unset. ## Manual test Self-host stack (SPA :8080 + API :25252, different origins), `MOKOSH_API_BASE=http://localhost:25252/api/v1`: `POST /api/v1/auth/login` and subsequent `/api/v1/*` calls complete with no `connect-src` violation; console shows `connect-src 'self' http://localhost:25252` with no path and no `-` token.
fix(csp): scope connect-src to API/OIDC origins so all sub-paths are allowed
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m20s
Create release / Create release from merged PR (pull_request) Has been skipped
b56242416c
The mokosh-www runtime CSP built connect-src straight from the operator env: `connect-src 'self' {$MOKOSH_API_BASE:-} {$MOKOSH_OIDC_ISSUER:-}`. Two defects:

1. `MOKOSH_API_BASE` is the full API base including its `/api/v1` path (the SPA appends `/auth/login`, `/ready`, ... to it). A CSP host-source whose path does not end in `/` is an exact-path match, so only `/api/v1` itself was allowed and every real call was blocked. The browser aborted the fetch before it left, surfacing as the SPA's `ApiError::Network` ("Network error") with nothing reaching the server. This broke all cross-origin API access, i.e. any self-hosted or split SPA/API deployment.

2. Caddy's env-placeholder default syntax is `{$VAR:default}`, so `{$VAR:-}` sets the default to a literal `-` (not empty, as the old comment claimed). An unset OIDC issuer therefore injected a junk `-` / `http://-` source.

Fix: entrypoint.sh derives origin-scoped CSP sources (`origin_of` reduces a URL to `scheme://host[:port]`, dropping the path; empty for a relative or empty value) into `MOKOSH_API_ORIGIN` / `MOKOSH_OIDC_ORIGIN` and exports them; the Caddyfile connect-src now reads `{$MOKOSH_API_ORIGIN:} {$MOKOSH_OIDC_ORIGIN:}` (origin-scoped so all sub-paths match, correct empty-default syntax so an unset origin drops out). Operator-facing `MOKOSH_API_BASE` / `MOKOSH_OIDC_ISSUER` are unchanged.

Verified: `sh -n` on entrypoint.sh; `origin_of` returns the expected origin for absolute URLs (port preserved) and empty for relative/empty input; `caddy validate` passes with the OIDC origin both set and unset.

#MAPPS-369

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch fix/MAPPS-369-csp-connect-src-origin 2026-07-15 17:02:09 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!428
No description provided.