HarperFast / HarperFast/oauth

Follow-up hardening after the account-adoption gate (GHSA-vf58 / 2.6.0)

Open
#231 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.