Follow-up hardening after the account-adoption gate (GHSA-vf58 / 2.6.0)
- Dominant language
- JavaScript
- Stars
- 1
- Forks
- 1
- Avg merge
- 2d 16h
- Merged PRs (30d)
- 12
Description
## Follow-up hardening after the account-adoption gate (2.6.0 / GHSA-vf58-5v5f-mvpm)
2.6.0 shipped the account-adoption gate: a new OAuth login adopts an existing `hdb_user` only when the claim is a verified email from an authenticated source (a JWKS-signature-verified OIDC id token with a validated issuer, or GitHub's authenticated email fetch). This issue tracks the hardening deliberately deferred from that release.
### 1. Durably neutralize pre-existing (pre-gate) sessions — incl. the operations API
The gate governs **new** logins. Sessions established before upgrading persist (Harper does not expire sessions by default) and may still hold a raw claim as their identity. The **operations API** resolves a session cookie to an `hdb_user` role in Harper core, on a listener the plugin's request middleware never wraps — so a plugin-level request-time check can't reach it, and a plugin-level startup sweep is only best-effort (fail-open on storage errors; a mixed-version rolling-upgrade window). The interim mitigation is documented (operator clears `system.hdb_session` fleet-wide on upgrade).
Proposed durable fix: a **session provenance epoch** in core — a marker/epoch that core's session resolution checks, so pre-gate sessions fail closed uniformly on both the app-server and operations-API paths without a per-plugin sweep. Everyone re-logs in once (same cost as clearing sessions), but fail-closed. **Decision to settle:** targeted plugin sweep vs. a core session epoch.
### 2. Expose authenticated-source provenance to `onLogin` hooks
A hook that returns `{ user }` is authoritative and bypasses the gate. Today the plugin strips its provenance determination before calling `onLogin`, so a hook can only see `oauthUser.emailVerified` — which an unsigned UserInfo body can assert (`email_verified: true`). Expose the plugin's authenticated-source signal (and ideally validated issuer + subject) on the hook's `oauthUser` so hook-based deployments can gate correctly, and update the hook docs/examples to use it. (2.6.0 already corrected the docs to warn against trusting bare `emailVerified`.) *A first PR for this is open.*
### 3. Bind accounts by stable identity, not verified email (OIDC §5.7)
Adoption keys on the verified email matching the username and never consults `(iss, sub)` / a provider's stable user id. A reassigned email lets a new holder inherit the prior holder's role. Use verified email to *locate* a candidate account, then require a stable-identity match or confirmation for the initial binding. **Accepted for 2.6.0 as email-keyed adoption; this is the follow-up.**
### 4. Provider compatibility: explicit-endpoint configs become issuer-less
An Okta (or generic) custom-auth-server config with explicit endpoints leaves `issuer` empty → JWKS verifies but `issuerValidated` is false → hookless logins that previously adopted are denied. Derive or require the issuer for JWKS-enabled providers, and fail fast at startup with an actionable error rather than silently denying at login.
### 5. Smaller items
- **Reject UserInfo `sub` != id-token `sub`** on the fetchEmail path (OIDC Core §5.3.2/§5.3.4).
- **Storage-error availability:** a transient `hdb_user` read error is treated fail-closed as "account exists" → the login is denied. Secure but an availability hit; quarantining (roleless) on a read error is equally secure and more available.
- **Config-reload deeper hardening:** apply the escape-hatch setting independently of the fallible provider config (or fail it closed on a reload error), so a snapshot that both disables the hatch and errors cannot leave it on. (2.6.0 fixed the drop-of-final-snapshot case.)
- **`_emailProvenance` label:** `getUserInfo` stamps `signed-oidc` even on the no-JWKS fallback (`signatureVerified === false`). Not exploitable today (the gate re-checks the flags), but the label overstates its own contract for any future consumer.
### 6. End-to-end integration coverage
Prove the positive path end-to-end (a signature-verified JWKS OIDC token adopts an existing account and inherits its role) and the `allowUnverifiedClaimInheritance` escape hatch, against a real Harper instance — the current suite proves only denial/quarantine. This also needs a harness that can disable the loopback auth bypass so an operations-API denial (403) of a neutralized session is executable. Tracked in #230.
Contributor guide
Research direction
The issue names no files; start by separating the six hardening tracks and reading core session resolution, the plugin request middleware, onLogin/getUserInfo, and the integration harness described in #230. Confirm which track is unclaimed, since the hook-provenance work already has a first PR. Done means the selected behavior is covered by tests, including the relevant fail-closed or authenticated-source cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- authentication, backend, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100