fix(desktop): keep isolated SSH backend on session-token auth - #93828
fix(desktop): keep isolated SSH backend on session-token auth#93828majestique1337 wants to merge 3 commits into
Conversation
Security-sensitive change handled with the right posture: every private-backend marker is mandatory (
Minor: the duplicated boilerplate between the two new tests (env + clear_providers + stub + restore) is begging for a small fixture; harmless now. |
…nd auth gate - Add concise logger.info when isolated Desktop SSH private-backend path activates - Document parameter intent for ssh_session_token and ssh_owner_nonce across call surface - Document threat-model assumption near proxy_headers=False - Refactor test setup boilerplate in test_dashboard_auth_gate.py into _setup_desktop_ssh_test_env helper and assert log emission
What does this PR do?
Keeps the remote Desktop SSH backend on its intended per-launch session-token
authentication when the same machine also has an external
dashboard.public_url/HERMES_DASHBOARD_PUBLIC_URLconfigured.The Desktop remote lifecycle starts a private backend as:
start_server()also resolves the regular browser dashboard's public host.An external public host makes
should_require_dashboard_auth()enable thecookie/ticket auth gate even for this ephemeral loopback process. The Desktop
SSH client still connects to
/api/ws?token=<per-launch token>, and gated modeunconditionally rejects that legacy token. The result is a split symptom:
/api/healthis reachable, while the WebSocket closes during boot.This change recognizes only the fully marked private Desktop SSH shape and
does not inherit the browser dashboard's public Host/Origin declaration for
that process. Its loopback session-token auth therefore remains active.
Regular dashboard and
servelaunches keep the existing public auth gate.Related Issue
Related to #91495. This addresses the remaining SSH token-rejection path
described in the issue discussion; it is separate from the local Desktop SPA
problem investigated in #91552.
Type of Change
Changes Made
hermes_cli/web_server.py: identify the private Desktop SSH backend usingall of its existing security/lifecycle markers: loopback host, ephemeral
port, headless mode, session token, and owner nonce.
hermes_cli/web_server.py: keep the configured browser-facing public hostsout of that private process's Host/Origin and auth-gate state.
tests/hermes_cli/test_dashboard_auth_gate.py: prove that the session tokenis accepted with an external public URL configured, and that dropping any
one private marker leaves the public auth gate enabled.
How to Test
dashboard.public_url(or its existing environmentequivalent) on a remote Hermes host.
main, observe HTTP health succeed while/api/ws?token=...is rejectedafter the dashboard auth gate engages.
and the WebSocket connect, while the regular public dashboard remains gated.
Automated regression run (through the repository's hermetic test wrapper):
Also checked with
ruff 0.15.10on both changed files: clean.Manual end-to-end verification: packaged macOS Desktop connecting over SSH to
a Linux backend with an external private-overlay dashboard URL configured.
Before the fix, HTTP health returned 200 but the WebSocket closed; after the
fix, the WebSocket opened and Desktop reached
Gateway ready.Security Boundary
The exception requires every marker below:
127.0.0.1,localhost, or::1)0)If any marker is absent, the existing public-dashboard auth decision is used.
The parameterized regression test covers every missing-marker case.
Checklist
Code
scripts/run_tests.shDocumentation & Housekeeping
cli-config.yaml.example: N/A; no config keys changedCONTRIBUTING.md/AGENTS.md: N/A; no workflow changes