Files
Shadowbroker/frontend
Shadowbroker bcc2d036b3 [security] Close tg12 auth-bypass chain #249, #254, #255 (#263)
External audit by @tg12 found three coupled vulnerabilities in the
Next.js admin-auth surface that together let any webpage the operator
visits trigger arbitrary privileged backend calls:

  #249/#254 — Cross-origin webpages can have process.env.ADMIN_KEY
              injected into their forwarded backend requests just by
              issuing fetch('http://localhost:3000/api/wormhole/...')
              from a browser tab the operator has open. Full
              identity-takeover CSRF.

  #255      — When ADMIN_KEY is unset on the server (the default in
              .env.example), the admin session route fell through to
              GET /api/settings/privacy-profile to "verify" the user-
              supplied key. That endpoint is public; it always returns
              200 for any X-Admin-Key value. So arbitrary attacker
              keys minted full admin session cookies on default
              installs.

Both fixes preserve every legitimate UX path. Origin-header gating is
transparent to browser tabs on the dashboard's own host, transparent
to Tauri/native shells (no Origin), and transparent to server-to-
server callers (no Origin). Only cross-origin browser fetches with a
foreign Origin lose the injection.

  frontend/src/app/api/[...path]/route.ts
    Adds isSameOriginOrNonBrowser() — checks the Origin header against
    the request's own Host. Allow if no Origin (native/server-to-
    server), allow if Origin host == Host host (same-origin), reject
    otherwise. The admin-key injection now requires EITHER a valid
    session cookie (auth) OR same-origin-or-non-browser (CSRF guard).

  frontend/src/app/api/admin/session/route.ts
    verifyAdminKey() simplified to local-only string comparison. When
    ADMIN_KEY is configured, the supplied key must match exactly.
    When ADMIN_KEY is unset, minting is refused entirely with a clear
    message pointing the operator at the backend's auto-trust-loopback
    behavior (SHADOWBROKER_TRUST_DOCKER_BRIDGE_LOCAL_OPERATOR=1, the
    Docker default — local users keep working without a session).

    The previous round-trip to /api/settings/privacy-profile was both
    the source of the bug AND useless on its own merits (the endpoint
    is public). Removing it makes the validation honest about what
    it's checking.

Tests:
  frontend/src/__tests__/proxy/proxyAuthBypassChain.test.ts (new, 12)
    Cross-origin fetch to sensitive route → no admin-key injection
    Cross-origin POST to sensitive route → no admin-key injection
    Same-origin fetch → admin-key injection works
    No-Origin (server-to-server / native) → admin-key injection works
    Valid session cookie on cross-origin → cookie auth wins
    Malformed Origin → conservative reject
    Non-sensitive routes unaffected
    Mint with ADMIN_KEY unset → refused (no fetch happens)
    Empty key → 400
    Mint with matching ADMIN_KEY → success
    Mint with mismatched key → 403
    Mint never round-trips to the backend (local-only validation)

  frontend/src/__tests__/desktop/adminSessionBoundary.test.ts (updated)
    Three tests updated to reflect the new local-only validation
    contract. The previous tests asserted fetchMock.toHaveBeenCalled
    which validated the now-removed (and broken) backend round-trip.

Full frontend suite: 707 passed, 72 files. No regressions.

Credit: @tg12 for the report. The cross-origin CSRF angle was
non-obvious — they specifically called out that the proxy's
admin-key injection was an open door for any page running in the
operator's browser, which is exactly the right framing.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 20:59:40 -06:00
..
2026-03-04 22:44:08 -07:00
2026-05-01 22:56:50 -06:00
2026-03-04 22:44:08 -07:00
2026-05-01 22:56:50 -06:00
2026-05-01 22:56:50 -06:00

ShadowBroker Frontend

Next.js 16 dashboard with MapLibre GL, Cesium, and Framer Motion.

Development

npm install
npm run dev        # http://localhost:3000

API URL Configuration

The frontend needs to reach the backend (default port 8000). Resolution order:

  1. NEXT_PUBLIC_API_URL env var — if set, used as-is (build-time, baked by Next.js)
  2. Server-side (SSR) — falls back to http://localhost:8000
  3. Client-side (browser) — auto-detects using window.location.hostname:8000

Common scenarios

Scenario Action needed
Local dev (localhost:3000 + localhost:8000) None — auto-detected
LAN access (192.168.x.x:3000) None — auto-detected from browser hostname
Public deploy (same host, port 8000) None — auto-detected
Backend on different port (e.g. 9096) Set NEXT_PUBLIC_API_URL=http://host:9096 before build
Backend on different host Set NEXT_PUBLIC_API_URL=http://backend-host:8000 before build
Behind reverse proxy (e.g. /api path) Set NEXT_PUBLIC_API_URL=https://yourdomain.com before build

Setting the variable

# Shell (Linux/macOS)
NEXT_PUBLIC_API_URL=http://myserver:8000 npm run build

# PowerShell (Windows)
$env:NEXT_PUBLIC_API_URL="http://myserver:8000"; npm run build

# Docker Compose (set in .env file next to docker-compose.yml)
NEXT_PUBLIC_API_URL=http://myserver:8000

Note: This is a build-time variable. Changing it requires rebuilding the frontend.

Theming

Dark mode is the default. A light/dark toggle is available in the left panel toolbar. Theme preference is persisted in localStorage as sb-theme and applied via data-theme attribute on <html>. CSS variables in globals.css define all structural colors for both themes.