fix(dashboard): sanction plugin WS/upload auth via SDK helpers (gated mode) - #38533
Closed
benbarclay wants to merge 1 commit into
Closed
fix(dashboard): sanction plugin WS/upload auth via SDK helpers (gated mode)#38533benbarclay wants to merge 1 commit into
benbarclay wants to merge 1 commit into
Conversation
… 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.
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
1 |
First entries
tests/plugins/test_plugin_dashboard_auth_contract.py:28: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
✅ Fixed issues: none
Unchanged: 5047 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Dashboard plugins (
kanban,hermes-achievements) readwindow.__HERMES_SESSION_TOKEN__directly and hand-assembled WebSocket URLs with?token=. That works in loopback /--insecuremode, but is rejected on OAuth-gated deployments (e.g. hosted Fly agents), where the session token is absent by design and_ws_auth_okonly accepts single-use?ticket=auth.Symptoms on a gated agent:
/api/plugins/kanban/events) → 1008 / 403 on upgradeThe dashboard SPA already handled gated mode correctly; only the SDK-bypassing plugin call sites broke.
Impact case
Anyone running the dashboard behind OAuth (the hosted/Fly path, and any self-hoster who enables
auth_required) gets a broken kanban board and broken achievements panel — the kanban WS never connects so the board never live-updates, and attachment upload/download 401s. It's invisible in the default loopback/--insecuresetup, which is why it slipped through. Not a sharp edge on an exotic config — it's every gated deployment.Fix — make the plugin SDK the single sanctioned auth surface
Rather than patch each plugin with its own inline token logic (which would let this regress again), extend the existing
window.__HERMES_PLUGIN_SDK__seam:web/src/lib/api.ts— addauthedFetch()(rawResponseforFormDatauploads / blob downloads; token-or-cookie auth; does not throw / does not 401-redirect) andbuildWsUrl()(assembles aws(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— exposeauthedFetch,buildWsUrl,buildWsAuthParam,sdkVersionon the SDK; addSDK_CONTRACT_VERSION.web/src/plugins/sdk.d.ts(new) — hand-authored typed contract for the plugin SDK + registryWindowglobals (single source of truth; documented as a versioned API boundary).SDK.authedFetch, the live-events WS throughSDK.buildWsUrl.plugins/kanban/dashboard/plugin_api.py—_ws_upgrade_authorized()delegates the/eventsWS upgrade to the canonicalweb_server._ws_auth_okgate, so it transparently accepts loopback-token / gated-ticket / internal-credential and can never drift from core auth again.dist/index.jsreads__HERMES_SESSION_TOKEN__directly; kanban gated-ticket WS acceptance test.Verification
tests/plugins/test_plugin_dashboard_auth_contract.py,tests/plugins/test_kanban_dashboard_plugin.py,tests/hermes_cli/test_dashboard_auth_ws_auth.py— all green (143 passed).npm run build(tsc -b+ vite) clean; eslint clean on changed files. (Note:tsc -bis stricter thantsc --noEmitand caught 4 type-contract bugs in the spike.d.ts— fixed.)/eventsupgrade carries a real minted ticket (ticket_len=43) →_ws_auth_okconsumes it →ws_auth_ok=True→ 101 Switching Protocols, where the old?token=code got 403.Review
plugins/kanban/dashboard/plugin_api.pydelegates to the core dashboard WS-auth gate (hermes_cli.web_server._ws_auth_ok) — flagging @teknium1 for the auth-adjacent backend change. The web/SPA + plugin-frontend changes are tightly coupled to the backend (the frontend mints the ticket the backend must accept), so they ship together as one coherent PR rather than split.The
sdk.d.tsis a spike — it documents the contract and gives plugin authors editor types. Open questions for productionising it (publish as a types package? runtimesdk_versioncompat gate?) are noted in the file header and don't block this auth fix.