Skip to content

fix(dashboard): sanction plugin WS/upload auth via SDK helpers (gated mode) - #38549

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-7aa1f851
Jun 3, 2026
Merged

fix(dashboard): sanction plugin WS/upload auth via SDK helpers (gated mode)#38549
teknium1 merged 1 commit into
mainfrom
hermes/hermes-7aa1f851

Conversation

@teknium1

@teknium1 teknium1 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Dashboard plugins now authenticate correctly on OAuth-gated deployments — the kanban board live-updates and attachment upload/download work behind OAuth, not just in loopback/--insecure.

Root cause: the kanban and hermes-achievements dashboard bundles read window.__HERMES_SESSION_TOKEN__ directly and hand-assembled ?token= WebSocket URLs. That token is absent by design in gated mode (where _ws_auth_ok only accepts a single-use ?ticket=), so plugin REST calls 401'd and the live-events WS 1008/403'd on every gated agent. Invisible in the default loopback setup, which is why it slipped through.

Salvage of #38533 by @benbarclay, cherry-picked onto current main with authorship preserved.

Changes

  • web/src/lib/api.ts: add authedFetch() (raw Response for FormData uploads / blob downloads; token-or-cookie auth; no throw, no 401-redirect) and buildWsUrl() (assembles a ws(s):// URL with the right auth param per mode — fresh single-use ticket gated, token loopback).
  • web/src/plugins/registry.ts: expose authedFetch, buildWsUrl, buildWsAuthParam, sdkVersion on window.__HERMES_PLUGIN_SDK__; add SDK_CONTRACT_VERSION.
  • web/src/plugins/sdk.d.ts (new): typed contract for the plugin SDK + registry Window globals.
  • kanban + hermes-achievements dist bundles: stop reading the session token; route uploads/downloads through SDK.authedFetch, the live-events WS through SDK.buildWsUrl.
  • plugins/kanban/dashboard/plugin_api.py: _ws_upgrade_authorized() delegates the /events upgrade to the canonical web_server._ws_auth_ok gate — transparently accepts loopback-token / gated-ticket / internal-credential, can't drift from core auth again.
  • tests: guard asserting no plugin dist/index.js reads __HERMES_SESSION_TOKEN__ directly; kanban gated-ticket WS acceptance test.

Validation

Result
Python (auth contract + kanban dashboard + dashboard WS auth) 143 passed
tsc -b clean
eslint (changed TS) clean
Author contributed live-verified on a gated staging Fly agent /events upgrades 101 with minted ticket where old ?token= got 403

Closes #38533.

Infographic

dashboard-plugin-auth-gated-mode-fix

… mode)

Dashboard plugins (kanban, hermes-achievements) read
window.__HERMES_SESSION_TOKEN__ directly and hand-assembled WebSocket
URLs with ?token=. That works in loopback/--insecure mode but is
rejected on OAuth-gated deployments, where the session token is absent
and _ws_auth_ok only accepts single-use ?ticket= auth. The result was
401s on plugin REST calls and 1008/403 on the kanban live-events WS
whenever the dashboard ran behind OAuth (e.g. hosted Fly agents).

Make the plugin SDK the single sanctioned auth surface:

- web/src/lib/api.ts: add authedFetch() (raw Response for FormData
  uploads / blob downloads, token-or-cookie auth, no throw / no 401
  redirect) and buildWsUrl() (assembles a ws(s):// URL with the correct
  auth param for the active mode — fresh single-use ticket in gated
  mode, token in loopback).
- web/src/plugins/registry.ts: expose authedFetch, buildWsUrl,
  buildWsAuthParam, and sdkVersion on window.__HERMES_PLUGIN_SDK__;
  add SDK_CONTRACT_VERSION.
- web/src/plugins/sdk.d.ts: hand-authored typed contract for the
  plugin SDK + registry globals (single source of truth for the
  Window declarations).
- plugins/kanban + hermes-achievements dist bundles: stop reading the
  session token directly; route uploads/downloads through
  SDK.authedFetch and the live-events WS through SDK.buildWsUrl.
- plugins/kanban plugin_api.py: _ws_upgrade_authorized() delegates the
  /events WS upgrade to the canonical web_server._ws_auth_ok gate, so
  it transparently accepts loopback token / gated ticket / internal
  credential and can never drift from core auth again.
- tests: guard test asserting no plugin dist reads
  __HERMES_SESSION_TOKEN__ directly; kanban gated-ticket WS test.

Verified live on a gated staging Fly agent: kanban /events upgrades
101 with a minted ticket (ticket_len=43, ws_auth_ok=True) where the
old code got 403.
@alt-glitch alt-glitch added type/bug Something isn't working comp/plugins Plugin system and bundled plugins area/auth Authentication, OAuth, credential pools P3 Low — cosmetic, nice to have labels Jun 3, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #38533 — this PR is a cherry-pick salvage of that PR onto current main with authorship preserved. Tracking the work here.

@teknium1
teknium1 merged commit a6e4731 into main Jun 3, 2026
23 checks passed
@teknium1
teknium1 deleted the hermes/hermes-7aa1f851 branch June 3, 2026 23:59
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 comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants