Skip to content

feat(dashboard): add hardened API-only remote proxy - #66498

Open
joelbrilliant wants to merge 2 commits into
NousResearch:mainfrom
joelbrilliant:feat/dashboard-remote-proxy
Open

feat(dashboard): add hardened API-only remote proxy#66498
joelbrilliant wants to merge 2 commits into
NousResearch:mainfrom
joelbrilliant:feat/dashboard-remote-proxy

Conversation

@joelbrilliant

@joelbrilliant joelbrilliant commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Problem

The desktop remote gateway expects a trusted proxy, but tunnelling the whole dashboard server exposes more of the host than remote Desktop access needs.

  1. The SPA HTML embeds the dashboard session token.
  2. Machine lifecycle routes can update, restart, back up, import, or migrate the local Hermes installation.
  3. Denied probes have no durable attribution without a dedicated audit log.

Related remote access demand appears in #9269, #8951, #34390, #37119, #39553, and #53839.

Security model

This is an API-only topology for Desktop token authentication. The loopback backend remains the authentication authority for every protected HTTP and WebSocket route. Configure a fixed HERMES_DASHBOARD_SESSION_TOKEN on the backend and enter the same value in Desktop token mode.

The proxy does not forward /login, /auth/callback, the SPA, or static assets. Browser OAuth and the browser dashboard are intentionally unsupported through this surface. Use the normal authenticated non-loopback dashboard deployment when those are required.

What this adds

  • A hermes dashboard proxy command that forwards only /api/* HTTP and WebSocket traffic to the loopback backend.
  • Default denials for machine lifecycle, backup, import, migration, and gateway control routes.
  • Explicit route policy overrides for operators who need a different boundary.
  • HTTP and WebSocket denial auditing in logs/remote-proxy-denied.log.
  • Repeated response header preservation, including multiple Set-Cookie fields.
  • Hop-by-hop header stripping in both directions.
  • Documentation for fixed-token Desktop setup through Cloudflare, Tailscale, or SSH tunnelling.
  • No new dependencies.

Review follow-up

The hermes-sweeper findings are addressed in the follow-up commit:

  • The supported token topology is explicit and the incorrect OAuth claims are removed.
  • Browser authentication bootstrap routes remain closed by design.
  • Repeated upstream response headers are preserved.
  • Denied WebSocket upgrades are audited.
  • A production-shaped regression runs the proxy against the real loopback backend and proves missing tokens receive 401 while the correct token succeeds.
  • Existing dashboard parser test harnesses now receive the proxy handler, fixing the seven Python CI failures on the original commit.

Validation

  • scripts/run_tests.sh across the proxy, HTTP auth, WebSocket auth, admin endpoint, dashboard parser, and serve parser suites
  • 205 tests passed
  • Ruff passed for every changed Python file
  • git diff --check passed
  • Documentation shell examples passed syntax validation

@alt-glitch alt-glitch added type/feature New feature or request comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P2 Medium — degraded but workaround exists needs-decision Awaiting maintainer decision before any implementation labels Jul 17, 2026
@joelbrilliant
joelbrilliant force-pushed the feat/dashboard-remote-proxy branch from 4fcd32a to ebec5bc Compare July 18, 2026 01:56

@teknium1 teknium1 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.

Thanks for addressing a real remote-dashboard boundary problem; the API-only and lifecycle-deny direction is useful. There are blocking integration issues.

Problems

  • hermes_cli/remote_proxy.py:118 returns 404 for every non-/api path. Desktop OAuth intentionally opens /login (apps/desktop/electron/main.ts:5119-5121, 5255) and completes through /auth/callback, so the documented sign-in flow cannot work through this proxy.
  • The documented loopback hermes serve topology does not engage the current OAuth gate: loopback is explicitly a no-op in hermes_cli/dashboard_auth/middleware.py:13-15, while start_server() derives auth_required from the bind host (hermes_cli/web_server.py:17856-17879).
  • hermes_cli/remote_proxy.py:206 collapses response headers into a dict. Dashboard auth emits multiple Set-Cookie headers (hermes_cli/dashboard_auth/cookies.py:191-212), so cookie-based auth cannot be forwarded reliably.

Suggested changes

  • Design an explicit authenticated loopback-upstream mode, then allow only the narrow auth-bootstrap routes needed by Desktop—without serving the SPA/static surface.
  • Preserve repeated response headers and add an end-to-end OAuth/login + WS-ticket proxy test.
  • Audit denied WebSocket upgrades as well as HTTP denies.

This is an automated hermes-sweeper review.

Comment thread hermes_cli/remote_proxy.py
Comment thread hermes_cli/remote_proxy.py Outdated
Comment thread hermes_cli/remote_proxy.py Outdated
@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 18, 2026
joelbrilliant and others added 2 commits July 19, 2026 13:32
…ote access

The desktop's remote-gateway mode expects "an already-running Hermes
backend ... behind a trusted proxy", but the trusted proxy has always
been DIY. This adds it as a first-class command, ported from a proxy
that has been running in production in front of a Cloudflare tunnel
since early July.

hermes dashboard proxy forwards only /api/* (HTTP + WebSocket) to the
loopback backend, so the SPA HTML — which inlines the dashboard session
token — never crosses the tunnel. Machine-lifecycle routes are denied by
default with an audit log:

- /api/hermes/update (a remotely triggered self-update restarts
  backends with nobody at the machine; the read-only update/check stays
  reachable)
- /api/gateway/start|stop|restart|drain
- /api/ops/backup + backup/download (single-request exfiltration of the
  whole HERMES_HOME), ops/import + import-upload, ops/config-migrate

--allow-route re-enables a denied route per explicit operator decision;
--deny-route adds more. Denials are 403 + one audit line in
logs/remote-proxy-denied.log. Auth is deliberately untouched: every
forwarded request still hits the backend's session-token/OAuth checks.
Built on the existing core stack (FastAPI/uvicorn, httpx, websockets).

Docs: user-guide/remote-access.md covers the loopback + tunnel setup
(Cloudflare, Tailscale, SSH -R), the deny table, and policy overrides.

Tests: 29 covering route classification, deny/allow overrides,
hop-by-hop header stripping, forwarding with query/header passthrough,
403 + audit on denied routes, SPA/static 404s, and WS policy close.
Verified live against a running backend: forwarded routes return the
upstream's own 401 without a session, denied routes 403 without ever
reaching the backend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Address hermes-sweeper review on NousResearch#66498:
- Document Desktop token mode as the supported API-only topology and keep browser OAuth routes closed.
- Preserve repeated upstream response headers and audit denied WebSocket upgrades.
- Add a production-shaped regression against the real loopback backend token gate.
- Wire the proxy handler into existing parser test harnesses to fix the seven CI failures.
@joelbrilliant
joelbrilliant force-pushed the feat/dashboard-remote-proxy branch from ebec5bc to 5a41613 Compare July 19, 2026 03:33
@joelbrilliant joelbrilliant changed the title feat(dashboard): add hermes dashboard proxy — hardened API-only remote access feat(dashboard): add hardened API-only remote proxy Jul 19, 2026
@joelbrilliant

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I rechecked the runtime auth path and found an important distinction: loopback disables the OAuth and password gate, but hermes_cli.web_server.auth_middleware still requires the dashboard session token on protected HTTP routes, and the WebSocket path requires the same token. Desktop token mode already sends X-Hermes-Session-Token for HTTP and ?token= for WebSockets.

Addressed in the pushed commits:

  • Made fixed-token Desktop mode the explicit supported topology. /login and /auth/callback remain 404 by design, and the docs no longer claim browser OAuth or the browser dashboard work through this API-only surface.
  • Preserved repeated upstream response headers through raw_headers, including multiple Set-Cookie fields.
  • Added audit entries for denied WebSocket upgrades.
  • Added a production-shaped regression that runs the proxy against the real loopback backend. A missing token receives 401 and the correct token succeeds on a protected route.
  • Wired the proxy handler into the existing dashboard parser test harnesses, fixing all seven Python CI failures from the original commit.

Verified after rebasing onto current main: 205 tests passed across the proxy, HTTP auth, WebSocket auth, admin endpoint, dashboard parser, and serve parser suites. Ruff and git diff --check also pass.

Out of scope for this PR: supporting browser OAuth or the browser dashboard through the API-only proxy. That needs a separate auth-bootstrap design rather than partially exposing login routes here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants