fix(dashboard-auth): bind basic-auth sessions to a persisted epoch (RAH-01) - #77207
fix(dashboard-auth): bind basic-auth sessions to a persisted epoch (RAH-01)#77207JoaoMarcos44 wants to merge 1 commit into
Conversation
…AH-01) BasicAuthProvider's tokens are stateless HMAC blobs with no server-side session record, so revoke_session() was a documented no-op and rotating the password (with an explicit, restart-stable secret) never invalidated tokens already issued — verify_session() only checked the signature and expiry, never anything derived from the current credential. Give every token an "epoch" claim checked at verification time. The epoch is persisted (atomic JSON write) and keyed to a fingerprint of the credential source material (not the salted hash, which changes every restart even when the password doesn't): register() detects a fingerprint change and bumps the epoch, so both an explicit revoke_session() and a credential rotation across a restart invalidate every previously issued token. Same fingerprint across a restart leaves the epoch untouched, preserving the documented explicit-secret multi-worker/restart-survival contract. Direct construction (tests, or any future caller) without a credential_fingerprint keeps the previous in-memory-only behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GottZ
left a comment
There was a problem hiding this comment.
This was generated by AI during triage.
Summary
Two PRs address the session-revocation and password-rotation defect: #77174 includes the persisted session-epoch fix within a broader five-finding bundle, while #77207 isolates the same RAH-01 implementation and regression coverage for focused review.
Related pull requests
- #77174 [closed]
best fix— (+519/-109) — superseded by #77207 for RAH-01: the bundled diff fixes the root cause by adding persisted credential-bound epochs to access and refresh tokens, invalidating old tokens on revocation or credential rotation. Although #77174 is the recorded best fix for #77172 and #77186, it remains relevant as the source implementation; a contributor closed it in favor of issue-specific PRs, and its RAH-01 changes are carried into #77207. - #77207
fixes— (+201/-5) — focused salvage path: the diff extracts #77174's RAH-01 fix into the dashboard-auth provider, including epoch validation, atomic epoch persistence, credential-fingerprint rotation detection, and tests for revocation, password rotation, and unchanged-password restarts.
Duplicates
#77174 and #77207 contain the same substantive RAH-01 session-epoch change; #77207 is the focused successor, while #77174 also contains unrelated fixes for the other split findings.
Suggested consolidation
Keep #77207 open with a salvage path focused strictly on its persisted session-epoch implementation and the three RAH-01 regression cases. Keep #77174 closed as superseded for this issue: despite its recorded best-fix verdict for #77172 and #77186, the diff shows that the relevant RAH-01 implementation was preserved in #77207, consistent with the contributor's explicit decision to split the bundled PR into issue-specific changes.
Complex graph
flowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I77186(["issue #77186 (open)"])
P77207["PR #77207 (open)"]
P77207 -->|fixes| I77186
class I77186 open
class P77207 open
class P77207 target
click I77186 "https://github.com/NousResearch/hermes-agent/issues/77186"
click P77207 "https://github.com/NousResearch/hermes-agent/pull/77207"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).
Cross-PR triage: Reviewed 2 pull requests and 2 issues in this complex. Each diff was read against this issue; Assessment working set: 53 kB of PR diffs, 19 kB of issue/PR text, <1 kB of discussion (2 comments), 4 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.
Summary
Closes #77186.
BasicAuthProvider(plugins/dashboard_auth/basic/__init__.py) mints stateless HMAC-signed access/refresh tokens with no server-side session record.verify_session()only checked the signature and expiry — nothing tied a token to "the credential that was active when it was minted". Two consequences, both reproduced deterministically before this fix:revoke_session()was a documented no-op. A copied/leaked access token stayed valid for its full TTL (default 12h access / 30d refresh) after logout.secretdid not invalidate previously issued tokens.Fix
Every token now carries an
epochclaim, checked at verification time against the provider's current epoch. The epoch is persisted (atomic JSON write,dashboard_auth_basic_session_epoch.jsonunderHERMES_HOME) and keyed to a fingerprint of the credential source material — the plaintext/precomputed hash as configured, not the salted-per-restartscryptoutput, so a plain restart with an unchanged password does not spuriously log everyone out.revoke_session()bumps the epoch (this provider has exactly one identity, so "logout" is necessarily "logout everywhere" for it).register()detects a fingerprint change across a restart and bumps the epoch too, closing the rotation gap.Direct construction (tests, or any future caller) without a
credential_fingerprintkeeps the previous in-memory-only behavior — fully backward compatible.%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#00f0ff', 'mainBkg': '#0a0a16', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#ff007f', 'lineColor': '#00f0ff'}}}%% graph TD A[🔒 revoke_session] -->|bump epoch| B[⚡ Persisted Epoch Store] C[🔁 password rotation] -->|fingerprint changed| B B -->|epoch claim on new tokens| D[🚀 Mint Session] E[🎫 Incoming Token] -->|epoch claim| F{Token Verify} B -->|current epoch| F F -->|mismatch| G[🚫 Rejected] F -->|match| H[✅ Session Valid]Infographic :
Test plan
test_revoke_session_invalidates_prior_access_token,test_password_rotation_invalidates_prior_session_across_restart,test_same_password_across_restart_keeps_session_validTypeErroron missing kwarg pre-refactor equivalent)tests/plugins/dashboard_auth/test_basic_provider.py— 22 passedpython -m py_compile plugins/dashboard_auth/basic/__init__.py