feat(cli): hermes web dashboard tunnel support#3295
feat(cli): hermes web dashboard tunnel support#3295louisgv merged 3 commits intoOpenRouterTeam:mainfrom
Conversation
Hermes Agent v0.9.0 ships a local web dashboard (hermes dashboard, default
127.0.0.1:9119) for config / session / skill / gateway management. This wires
Hermes into spawn's existing SSH-tunnel infrastructure so `spawn run hermes`
auto-exposes the dashboard to the user's local browser.
- agent-setup.ts: new startHermesDashboard() helper — session-scoped
background launch via setsid/nohup with a port-ready wait loop. No systemd
(unlike OpenClaw's gateway) because the dashboard only needs to live for
the duration of the spawn session. Falls back gracefully if hermes isn't
in PATH or the dashboard fails to come up.
- Wire preLaunch, preLaunchMsg, and tunnel { remotePort: 9119 } into the
hermes AgentConfig. Mirrors the OpenClaw tunnel pattern at
orchestrate.ts:628 — startSshTunnel + openBrowser happen automatically.
- manifest.json: update hermes notes to mention the dashboard.
- hermes-dashboard.test.ts: 7 new unit tests verifying the deploy script
calls `hermes dashboard --port 9119 --host 127.0.0.1 --no-open`, checks
all three port-probe fallbacks (ss / /dev/tcp / nc), uses setsid+nohup,
waits for the port, and does NOT install a systemd unit.
- Bump cli version 1.0.6 -> 1.0.7.
Closes OpenRouterTeam#3293
louisgv
left a comment
There was a problem hiding this comment.
Security Review
Verdict: APPROVED
Commit: 8a10953
Findings
No security issues found.
Security Analysis
- Shell script generation: All strings are static constants or shell-internal variables (
$HOME,$elapsed). No user input injection vectors. - Port binding: Correctly binds to
127.0.0.1:9119(loopback only), not exposed to external networks. - Process backgrounding: Proper use of
setsid/nohupwith I/O redirection, TTY-independent. - Timeout handling: Bounded wait loop (60s max) prevents infinite hangs.
- Tunnel configuration: Static port (9119), safe URL construction with typed
localPortparameter. - Test coverage: Comprehensive mocked tests with no real subprocess spawning.
Tests
- bun test: PASS (2050 tests, all passing)
- biome check: PASS (0 errors)
- bash compatibility: OK (uses arithmetic expansion
$((elapsed + 1)), no bash 4+ features)
Compliance
- CLI version bumped: 1.0.6 → 1.0.7 ✓
- Loopback-only binding (127.0.0.1) ✓
- No credential leaks ✓
- Proper shell quoting ✓
- Session-scoped (not persistent) ✓
-- security/pr-reviewer
la14-1
left a comment
There was a problem hiding this comment.
Reviewed: all 2050 tests pass, biome lint clean (185 files), CI checks all green. The hermes dashboard tunnel wiring follows the OpenClaw pattern correctly — startHermesDashboard() is non-fatal (TUI still works if dashboard doesn't start), port-probe uses the correct 3-fallback chain for Debian/Ubuntu compat, session-scoped via setsid/nohup (no unnecessary systemd unit), and the tunnel config at remotePort: 9119 wires into the existing generic tunnel plumbing without needing changes to orchestrate.ts or connect.ts. Good implementation.\n\n-- refactor/issue-reviewer
louisgv
left a comment
There was a problem hiding this comment.
Security Review
Verdict: APPROVED ✅
Commit: 0efcd5b
Security Analysis
✅ Port and URL Construction
localPortis always a number (validated viatcpCheckin SSH tunnel setup)- Template literal
http://localhost:${localPort}/is safe (no string interpolation risk) - Port properly validated before use
✅ Shell Script Generation (startHermesDashboard)
- No command injection risks - all variables are hardcoded literals or properly quoted
_hermes_bin=$(command -v hermes)uses safe command discovery- All command invocations use proper quoting:
"$_hermes_bin","$HOME" - Port check uses hardcoded port 9119 (no dynamic values)
✅ macOS bash 3.x Compatibility
- Uses
$((elapsed + 1))instead of((elapsed++)) - No
set -uflag - No
echo -eusage
✅ Process Isolation
- Uses
setsid/nohupto properly detach from TTY - Binds to localhost only (127.0.0.1:9119)
- Proper stdin/stdout/stderr redirection
✅ Test Coverage
- 7 new tests, all passing
- Validates security-critical aspects (port binding, process isolation, PATH setup)
- Pure unit tests with mocked runners (no subprocess spawning)
Test Results
- ✅
bash -n: N/A (no .sh files modified) - ✅
bun test: 2111 pass, 0 fail (full test suite)
Summary
This PR adds Hermes web dashboard tunnel support with proper security controls:
- Session-scoped background process (not persistent systemd service)
- Localhost-only binding with SSH tunnel access
- No command injection vectors
- Comprehensive test coverage
-- security/pr-reviewer
Summary
Closes #3293 — wires the Hermes Agent v0.9.0 local web dashboard (
hermes dashboard, default127.0.0.1:9119) into spawn's existing SSH-tunnel infrastructure. `spawn run hermes` now auto-exposes the dashboard to the user's local browser, same flow as OpenClaw's WhatsApp-QR dashboard on port 18789.Changes
Discovery notes
Source of truth for the dashboard port and subcommand is `hermes_cli/main.py` in NousResearch/hermes-agent:
```python
dashboard_parser.add_argument("--port", type=int, default=9119, ...)
dashboard_parser.add_argument("--host", default="127.0.0.1", ...)
dashboard_parser.add_argument("--no-open", action="store_true", ...)
```
The dashboard self-authenticates via a session token injected into the SPA HTML (`hermes_cli/web_server.py`), so no token needs to be appended to the tunnel URL — `http://localhost:/` just works.
Test plan