Skip to content

fix: unify dashboard CSRF for dynamic public origins - #5856

Merged
diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.43from
rdself:coder/unify-dashboard-csrf-origin
Jul 2, 2026
Merged

diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.43from
rdself:coder/unify-dashboard-csrf-origin

Conversation

@rdself

@rdself rdself commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Problem

After the v3.8.40+ origin hardening, dashboard writes can fail behind changing public hosts unless OMNIROUTE_PUBLIC_BASE_URL / NEXT_PUBLIC_BASE_URL is pinned to the exact browser URL. This breaks normal UI workflows such as combo/model tests, saving combo edits, provider/model edits, settings/UI configuration saves, and other dashboard management actions when the dashboard is reached through an ephemeral tunnel, CDN host, or HTTPS proxy.

The v3.8.41 CSRF repair only covered the model/combo test endpoints. The rest of the dashboard still relied on public-origin matching, so authenticated same-origin UI writes could still return INVALID_ORIGIN even though the user was using the dashboard normally.

Root cause

Dashboard CSRF validation was path-limited to:

  • /api/combos/test
  • /api/models/test
  • /api/models/test-all

The frontend also attached the token only at those individual call sites. Regular management mutations such as /api/settings, /api/combos/*, /api/providers/*, /api/quota/pools, /a2a, and other dashboard writes did not get the portable session-bound CSRF fallback.

Changes

  • Allow a valid dashboard CSRF token to satisfy only the invalid-origin branch for authenticated dashboard_session + MANAGEMENT + unsafe methods.
  • Keep cross-site Fetch Metadata fail-closed: sec-fetch-site: cross-site is not bypassed by CSRF.
  • Install one dashboard-level fetch wrapper early from DashboardLayout, before child passive effects can issue initial writes.
  • Attach x-omniroute-csrf automatically to same-origin dashboard management mutations.
  • Exclude client API and public API surfaces from the wrapper (/api/v1*, /v1*, /v1beta*, /responses, /models, /codex, public auth/OAuth routes, and cross-origin requests).
  • Cover explicit top-level management routes used by the dashboard, including /a2a.
  • Move the traffic-inspector system-proxy unload revert from sendBeacon to fetch(..., keepalive: true) so it uses the same CSRF mechanism.
  • Remove one-off CSRF header plumbing from combo/model test call sites.
  • Update reverse-proxy/tunnel docs to clarify that dashboard writes do not require a static public base URL; OAuth callbacks and generated public links still do.

Validation

  • node --import tsx/esm --test tests/unit/ui/use-system-proxy-exit-guard.test.tsx tests/unit/authz/csrf.test.ts tests/unit/authz/pipeline.test.ts
  • npx vitest run --config vitest.config.ts tests/unit/ui/dashboard-csrf-fetch.test.tsx 'src/app/(dashboard)/dashboard/providers/[id]/__tests__/useModelVisibilityHandlers.test.tsx' tests/unit/ui/use-model-visibility-handlers-test-all.test.tsx
  • npm run typecheck:core
  • npx eslint src/server/authz/csrf.ts src/server/authz/pipeline.ts src/shared/utils/dashboardCsrf.ts src/shared/components/layouts/DashboardLayout.tsx 'src/app/(dashboard)/dashboard/combos/page.tsx' 'src/app/(dashboard)/dashboard/providers/[id]/hooks/useModelVisibilityHandlers.ts' 'src/app/(dashboard)/dashboard/providers/[id]/components/PassthroughModelsSection.tsx' 'src/app/(dashboard)/dashboard/providers/[id]/__tests__/useModelVisibilityHandlers.test.tsx' 'src/app/(dashboard)/dashboard/tools/traffic-inspector/hooks/useSystemProxyExitGuard.ts' tests/unit/authz/csrf.test.ts tests/unit/authz/pipeline.test.ts tests/unit/ui/dashboard-csrf-fetch.test.tsx tests/unit/ui/use-system-proxy-exit-guard.test.tsx
  • npm run check:env-doc-sync
  • npm run check:fabricated-docs
  • git diff --check

@rdself
rdself requested a review from diegosouzapw as a code owner July 1, 2026 20:34
Copilot AI review requested due to automatic review settings July 1, 2026 20:34
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the “dashboard CSRF repair” from a few test endpoints to the full dashboard management surface, so authenticated same-origin dashboard writes keep working when the UI is accessed through dynamic/ephemeral public hosts (tunnels, CDNs, HTTPS proxies) without requiring a pinned OMNIROUTE_PUBLIC_BASE_URL / NEXT_PUBLIC_BASE_URL.

Changes:

  • Relax origin enforcement for MANAGEMENT dashboard mutations by allowing a valid dashboard CSRF token to satisfy only the invalid-origin failure case, while keeping Fetch Metadata (sec-fetch-site) fail-closed.
  • Install a dashboard-wide fetch wrapper (via DashboardLayout) to automatically attach x-omniroute-csrf for same-origin unsafe management mutations, and remove per-call-site CSRF header plumbing.
  • Update/expand unit tests and deployment docs to reflect the new behavior (dashboard writes no longer require a static public base URL; OAuth callbacks / generated links still may).

Validation / Coverage (per repo gate)

  • Commands run in this review: none (not executed in this environment)
  • npm run test:coverage: not verified here (required gate per repo guidelines)
  • Changed/added test files:
    • tests/unit/ui/use-system-proxy-exit-guard.test.tsx
    • tests/unit/ui/dashboard-csrf-fetch.test.tsx
    • tests/unit/authz/pipeline.test.ts
    • tests/unit/authz/csrf.test.ts
    • src/app/(dashboard)/dashboard/providers/[id]/__tests__/useModelVisibilityHandlers.test.tsx

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/server/authz/pipeline.ts Allows dashboard CSRF token to bypass only invalid-origin for unsafe MANAGEMENT dashboard mutations; updates INVALID_ORIGIN messaging.
src/server/authz/csrf.ts Removes path-limiting so dashboard CSRF tokens can validate for general management mutation paths (pipeline still gates usage).
src/shared/utils/dashboardCsrf.ts Adds global dashboard fetch wrapper that auto-attaches CSRF header for same-origin unsafe management requests while excluding public/client API surfaces.
src/shared/components/layouts/DashboardLayout.tsx Installs the dashboard CSRF fetch wrapper early in the dashboard layout lifecycle.
src/app/(dashboard)/dashboard/tools/traffic-inspector/hooks/useSystemProxyExitGuard.ts Switches system-proxy revert-on-exit from sendBeacon to fetch(..., keepalive: true) to align with the CSRF mechanism.
tests/unit/ui/use-system-proxy-exit-guard.test.tsx Updates hook-logic tests to assert keepalive fetch behavior instead of sendBeacon behavior.
tests/unit/ui/dashboard-csrf-fetch.test.tsx Adds Vitest coverage for the new dashboard CSRF fetch wrapper behavior and exclusions.
tests/unit/authz/pipeline.test.ts Expands authz pipeline tests to cover broader dashboard management routes + Fetch Metadata fail-closed behavior.
tests/unit/authz/csrf.test.ts Updates CSRF token validation expectations to cover management mutation paths beyond the original test endpoints.
src/app/(dashboard)/dashboard/combos/page.tsx Removes per-request CSRF header plumbing for combo tests (now provided by wrapper).
src/app/(dashboard)/dashboard/providers/[id]/hooks/useModelVisibilityHandlers.ts Removes per-request CSRF header plumbing for model tests (now provided by wrapper).
src/app/(dashboard)/dashboard/providers/[id]/components/PassthroughModelsSection.tsx Removes per-request CSRF header plumbing for model test-all (now provided by wrapper).
src/app/(dashboard)/dashboard/providers/[id]/__tests__/useModelVisibilityHandlers.test.tsx Updates expectations after removal of per-call-site CSRF token plumbing.
docs/reference/ENVIRONMENT.md Clarifies NEXT_PUBLIC_BASE_URL/public-origin semantics: dashboard writes use CSRF and don’t require a static public base URL.
docs/ops/VM_DEPLOYMENT_GUIDE.md Updates reverse-proxy notes to reflect dashboard CSRF behavior.
docs/ops/TUNNELS_GUIDE.md Adds explicit guidance that ephemeral tunnel hosts can still be used for normal dashboard writes after login.
docs/guides/DOCKER_GUIDE.md Updates reverse-proxy guidance consistent with dashboard CSRF behavior.
.env.example Updates comments to reflect dashboard CSRF protection vs. CORS/public-base-url requirements.

Comment on lines +159 to +162
headers.set(DASHBOARD_CSRF_HEADER, token);
return originalFetch(input, { ...init, headers });
}) as typeof fetch;
}
Comment on lines +143 to +145
if (installCount === 0) {
originalFetch = globalThis.fetch.bind(globalThis) as typeof fetch;

@diegosouzapw
diegosouzapw merged commit 103a0ee into cheaper-inference:release/v3.8.43 Jul 2, 2026
7 checks passed
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks @rdself! We reviewed the CSRF unification line by line — the fail-closed sec-fetch-site layer stays intact and the session-bound HMAC design is solid. This fixes the dashboard-behind-tunnel workflow across the whole management surface. Merged into release/v3.8.43. 🙌

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.

3 participants