Skip to content

fix(security): authenticate the /terminal edge route (unauth RCE)#113

Open
mt-alarcon wants to merge 1 commit into
evolution-foundation:developfrom
mt-alarcon:pr/terminal-auth-hardening
Open

fix(security): authenticate the /terminal edge route (unauth RCE)#113
mt-alarcon wants to merge 1 commit into
evolution-foundation:developfrom
mt-alarcon:pr/terminal-auth-hardening

Conversation

@mt-alarcon

Copy link
Copy Markdown

Problem

The Traefik router evonexus_terminal (priority 10) publishes the terminal-server (port 32352) directly on /terminal/* with only a stripprefix middleware — no authentication. This bypasses the Flask @login_required guard entirely (that path never reaches Flask). The terminal-server has no auth of its own, binds 0.0.0.0, runs cors() wide-open, and spawns claude --dangerously-skip-permissionsunauthenticated RCE at the edge for any self-hosted instance exposing this stack.

Fix — defence in depth (3 layers)

  1. Traefik forwardauth — new evonexus_terminal_auth middleware probes Flask GET /api/auth/check before forwarding any /terminal request; Flask validates the session cookie and returns 200 (allow) / 401 (deny). New endpoint added to PUBLIC_PATHS so the auth middleware doesn't pre-reject it before the session resolves.
  2. WebSocket tokenserver.js reads TERMINAL_WS_TOKEN and enforces it via a verifyClient hook (timingSafeEqual, HTTP 401 on upgrade rejection). The Flask proxy appends ?token= upstream, so the legitimate proxied path keeps working and the browser never sees the token. When unset, it logs a warning and stays open (local dev convenience).
  3. Loopback bind — terminal-server binds 127.0.0.1 by default (override via TERMINAL_BIND_HOST); cors() restricted to EVONEXUS_DASHBOARD_ORIGIN in production.

Tests

dashboard/terminal-server/test/server.test.js — 4 new WS-auth tests (accept w/ correct token, reject w/o token → 401, reject wrong token → 401, dev-mode open when unset) + 2 pre-existing. node --test6/6 pass.

Deploy note

Set TERMINAL_WS_TOKEN (e.g. openssl rand -hex 32) in the deploy environment. The forwardauth.address uses 127.0.0.1:8080, correct for single-node Swarm; multi-node deployments should use the dashboard service name instead.

🤖 Generated with Claude Code

…0.0.1 bind

CRITICAL finding: Traefik router `evonexus_terminal` (priority 10) exposed
the terminal-server (port 32352) directly on /terminal/* with only
stripprefix middleware — bypassing Flask login entirely, allowing
unauthenticated RCE via `claude --dangerously-skip-permissions`.

Three defence-in-depth layers:

1. Traefik forwardauth — adds `evonexus_terminal_auth` middleware that probes
   Flask `/api/auth/check` before forwarding any /terminal request. Flask
   validates the session cookie and returns 200 (allow) or 401 (deny).
   New endpoint in auth_routes.py; added to PUBLIC_PATHS so auth_middleware
   doesn't pre-reject it before the session is resolved. [UPSTREAM-PR]

2. WS token (shared secret) — server.js reads TERMINAL_WS_TOKEN env var and
   enforces it via WebSocket.Server verifyClient hook (HTTP 401 on upgrade
   rejection). terminal_proxy.py appends ?token=<value> when connecting
   upstream so the legitimate Flask-proxied path keeps working. Dev mode
   (token unset) logs a warning but stays open for local development.
   [UPSTREAM-PR]

3. Bind on 127.0.0.1 — terminal-server no longer binds on 0.0.0.0 by
   default. TERMINAL_BIND_HOST env var overrides (dev convenience).
   [UPSTREAM-PR]

Both stack files updated: evonexus.stack.yml (upstream template) and
[C]evonexus-contabo.stack.yml (MTA prod deployment — the actually deployed
stack). Both add forwardauth middleware and TERMINAL_WS_TOKEN env var.
[CONFIG] for the contabo stack; [UPSTREAM-PR] for the template.

Tests: 6 Node tests (node --test) — 4 new WS auth tests + 2 pre-existing.
All PASS. Python syntax validated. YAML validated.

WAF Cloudflare rule blocking /terminal* must remain until Marcello deploys
this branch and confirms the fix is live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @mt-alarcon, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

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.

1 participant