Skip to content

fix(dashboard): isolate internal websocket capabilities - #67305

Open
StellarisW wants to merge 1 commit into
NousResearch:mainfrom
StellarisW:fix/dashboard-internal-capability-isolation
Open

fix(dashboard): isolate internal websocket capabilities#67305
StellarisW wants to merge 1 commit into
NousResearch:mainfrom
StellarisW:fix/dashboard-internal-capability-isolation

Conversation

@StellarisW

@StellarisW StellarisW commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes a Dashboard internal WebSocket capability-confusion issue found during a security audit.

Previously, server-spawned TUI processes received one reusable internal bearer that could authenticate multiple Dashboard WebSocket endpoints. A profile-scoped sidecar publisher could therefore reuse its /api/pub credential against the broader /api/ws gateway.

This PR replaces that shared bearer with audience-bound capabilities and limits their propagation:

  • the JSON-RPC gateway receives a gateway capability accepted only by /api/ws;
  • event publishers receive a sidecar capability bound to the exact profile and channel and accepted only by /api/pub;
  • /api/pty, /api/events, and /api/console continue to reject internal capabilities;
  • capability-bearing TUI URLs are removed from ambient Node/Python environments before lower-trust subprocesses start;
  • generic Hermes subprocess environments always strip both TUI capability URL variables.

The capabilities remain multi-use within their exact scope so existing reconnect behavior is preserved.

Related Issue

N/A — security audit finding.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • hermes_cli/dashboard_auth/ws_tickets.py
    • derive opaque capabilities from a process-local root using explicit audience and binding inputs;
    • validate each capability against the exact expected audience/profile/channel scope.
  • hermes_cli/web_server.py
    • issue separate gateway and sidecar capabilities;
    • bind sidecar credentials to profile + channel;
    • opt only /api/ws and /api/pub into their respective internal audiences;
    • construct PTY environments through the centralized subprocess policy.
  • ui-tui/src/gatewayClient.ts, tui_gateway/entry.py, tui_gateway/server.py, tools/environments/local.py
    • consume capability URLs before broader runtime imports/spawns;
    • strip them from generic child environments and shell.exec.
  • Tests add real WebSocket upgrade rejection/reconnect coverage plus real child-process environment probes.

Infographic

Before-and-after architecture showing one shared Dashboard WebSocket bearer replaced by separate gateway and profile-channel-bound sidecar capabilities, with other routes denied and child environments scrubbed

Capability audience and profile/channel scope are validated at the accepting route, while lower-trust subprocesses no longer inherit the capability URL.

How to Test

  1. Run the focused Python security/profile/PTY matrix:
    HERMES_TEST_FILE_RETRIES=0 scripts/run_tests.sh \
      tests/hermes_cli/test_dashboard_internal_capabilities.py \
      tests/test_tui_gateway_entry_capability_env.py \
      tests/hermes_cli/test_dashboard_auth_ws_tickets.py \
      tests/hermes_cli/test_dashboard_auth_ws_auth.py \
      tests/hermes_cli/test_web_server_profile_unification.py \
      tests/tools/test_hermes_subprocess_env.py \
      tests/tools/test_local_env_blocklist.py \
      tests/test_tui_gateway_server.py \
      tests/test_tui_gateway_ws.py \
      tests/dashboard/test_ws_client_host.py \
      tests/hermes_cli/test_web_server_pty_reconnect.py \
      tests/test_pty_keepalive_ws.py -q
    Expected: 601 passed, 0 failed.
  2. Run the complete Dashboard web-server test file with a same-filesystem pytest temp root:
    mkdir -p "$HOME/.cache/hermes-tests"
    HERMES_TEST_FILE_RETRIES=0 scripts/run_tests.sh \
      tests/hermes_cli/test_web_server.py \
      --basetemp="$HOME/.cache/hermes-tests/dashboard-capability-web-server" -q
    Expected: 410 passed, 0 failed.
  3. Run TUI build/type/tests and lint:
    cd ui-tui
    npm run check
    npm run lint
    Expected: 111 files passed; 1190 tests passed, 1 skipped; lint exits 0.
  4. Run Python lint and diff hygiene:
    python -m ruff check <changed Python files>
    git diff --check origin/main...HEAD

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass (targeted canonical-wrapper suites above: 1011 Python tests passed)
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Linux, Python 3.11, Node/Vitest

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A; behavior is covered by code docstrings and regression tests
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — platform-neutral env and stdlib primitives; runtime verification was on Linux
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Screenshots / Logs

No UI change. Verification summaries:

Python focused matrix: 12 files, 601 passed, 0 failed
Dashboard web-server file: 410 passed, 0 failed
TUI: 111 files passed; 1190 passed, 1 skipped
Ruff / ESLint / git diff --check: passed

Main branch refresh (2026-07-21)

Merged upstream main at f4df260f26c93f15694698869f3ea8e965eea301 and resolved the conflict in tui_gateway/entry.py. The resolution keeps dashboard capability variables consumed before gateway imports while also retaining the current main-branch stdin EOF recovery hook.

Merge commit: 7afb8ba5ec2d116f4850c22ed647d6cb991c749a

Validation:

  • scripts/run_tests.sh -j 8 tests/dashboard/test_ws_client_host.py tests/hermes_cli/test_dashboard_auth_ws_auth.py tests/hermes_cli/test_dashboard_auth_ws_tickets.py tests/hermes_cli/test_dashboard_internal_capabilities.py tests/hermes_cli/test_web_server.py tests/hermes_cli/test_web_server_profile_unification.py tests/test_tui_gateway_entry_capability_env.py tests/test_tui_gateway_server.py tests/tools/test_hermes_subprocess_env.py tests/tools/test_local_env_blocklist.py
  • Result: 1,056 passed, 0 failed.

Latest conflict refresh (2026-07-23)

This section supersedes the earlier merge-based refresh. The single functional commit was replayed cleanly onto upstream main@9b1028f2974f7b456285b23b28eac5336f71e13c, removing the obsolete merge commit; the refreshed head is f86a13b2b46e720e4d8bb26e79d50b951da158ad.

The tui_gateway/entry.py resolution consumes capability-bearing environment variables before runtime imports while retaining main's stdin EOF recovery hook. The server test file keeps both main's battery coverage and the PR's child-environment capability stripping regression. Independent review confirmed the same 17-file, +605/-194 functional delta was preserved.

Validation after conflict resolution:

  • Focused Python matrix: 1,100 passed, 0 failed.
  • Gateway client Vitest: 14 passed, 0 failed.
  • TypeScript typecheck, ESLint, Ruff, and git diff --check: passed.
  • History: 1 signed functional commit, original author preserved, no merge commits.

@alt-glitch alt-glitch added type/security Security vulnerability or hardening comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/tui Terminal UI (ui-tui/ + tui_gateway/) backend/local Local shell execution area/auth Authentication, OAuth, credential pools P3 Low — cosmetic, nice to have needs-repro Bug needs reproduction steps sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Jul 19, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to merged #37972: this follows up by scoping server-internal WebSocket capabilities to their route/profile/channel and stripping them before child processes. The dashboard remains loopback by default; no unauthorized external-attacker PoC was supplied.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused security fix. I found no blocking defect in the implementation.

  • Current main accepts the same process-lifetime internal credential in both /api/ws and /api/pub (hermes_cli/web_server.py:15393-15400, :16518, :16549), while the credential itself has no route or binding input (hermes_cli/dashboard_auth/ws_tickets.py:110-149).
  • The PR scopes derived values by audience and sidecar profile/channel, and adds real WebSocket rejection, binding-tampering, and reconnect coverage (tests/hermes_cli/test_dashboard_internal_capabilities.py:43-98).
  • The current-main delta since the PR merge base does not touch this PR's production files, so the change appears mechanically salvageable.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 19, 2026
@StellarisW
StellarisW marked this pull request as ready for review July 19, 2026 04:45
(cherry picked from commit 41c0fccd462ad07fc77a32ef6b83494d20038333)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools backend/local Local shell execution comp/cli CLI entry point, hermes_cli/, setup wizard comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/tui Terminal UI (ui-tui/ + tui_gateway/) needs-repro Bug needs reproduction steps P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants