fix(dashboard): sanction plugin WS/upload auth via SDK helpers (gated mode) - #38549
Merged
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.
Collaborator
|
Duplicate of #38533 — this PR is a cherry-pick salvage of that PR onto current main with authorship preserved. Tracking the work here. |
1 task
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.
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
kanbanandhermes-achievementsdashboard bundles readwindow.__HERMES_SESSION_TOKEN__directly and hand-assembled?token=WebSocket URLs. That token is absent by design in gated mode (where_ws_auth_okonly 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
mainwith authorship preserved.Changes
web/src/lib/api.ts: addauthedFetch()(rawResponsefor FormData uploads / blob downloads; token-or-cookie auth; no throw, no 401-redirect) andbuildWsUrl()(assembles aws(s)://URL with the right auth param per mode — fresh single-use ticket gated, token loopback).web/src/plugins/registry.ts: exposeauthedFetch,buildWsUrl,buildWsAuthParam,sdkVersiononwindow.__HERMES_PLUGIN_SDK__; addSDK_CONTRACT_VERSION.web/src/plugins/sdk.d.ts(new): typed contract for the plugin SDK + registryWindowglobals.SDK.authedFetch, the live-events WS throughSDK.buildWsUrl.plugins/kanban/dashboard/plugin_api.py:_ws_upgrade_authorized()delegates the/eventsupgrade to the canonicalweb_server._ws_auth_okgate — transparently accepts loopback-token / gated-ticket / internal-credential, can't drift from core auth again.dist/index.jsreads__HERMES_SESSION_TOKEN__directly; kanban gated-ticket WS acceptance test.Validation
tsc -b/eventsupgrades 101 with minted ticket where old?token=got 403Closes #38533.
Infographic