GHSA-Q4XM-P75H-H9P3
Vulnerability from github – Published: 2026-11-09 21:17 – Updated: 2026-11-09 21:17Summary
The client credentials grant (grant_type=client_credentials) does not validate the resource parameter before embedding it as the aud claim in the issued JWT. Any authenticated OIDC client can obtain a Pocket ID-signed access token claiming authorization for any arbitrary audience, including other registered OIDC clients. There is no check that the requested audience is a registered resource server, and no check that the requesting client has been granted permission to access it.
Details
In backend/internal/service/oidc_service.go, createTokenFromClientCredentials accepts the resource parameter from the token request and places it directly into the aud claim of the issued JWT without validation:
audClaim := client.ID
if input.Resource != "" {
audClaim = input.Resource // no validation performed
}
accessToken, err := s.jwtService.GenerateOAuthAccessToken(dummyUser, audClaim)
The only guard in this function is verifyClientCredentialsInternal, which confirms the requesting client's credentials are valid. After that check passes, any value supplied as resource is accepted. There is no registry of permitted audiences, no per-client authorization policy controlling which resources a client may request tokens for, and no validation that the resource value corresponds to anything registered in Pocket ID.
The introspection endpoint (/api/oidc/introspect) does not mitigate this. It checks that the introspecting client's ID matches the token's aud claim, but since the attacker controls aud, a resource server calling introspection to validate an inbound token will receive "active": true. Pocket ID has no server-side record of which client requested the token or whether it was authorized to do so, so introspection cannot distinguish a legitimately issued token from one obtained through this vulnerability.
PoC
Given a Pocket ID instance with two registered OIDC clients — client-a (attacker-controlled) and client-b (target resource) — the following request causes Pocket ID to issue a signed JWT with aud: ["client-b"], authenticated only as client-a:
curl -X POST https://<pocket-id-host>/api/oidc/token \
-d "grant_type=client_credentials" \
-d "client_id=client-a" \
-d "client_secret=<client-a-secret>" \
-d "resource=client-b"
The response is a valid, Pocket ID-signed JWT with:
{
"sub": "client-client-a",
"aud": ["client-b"],
"iss": "https://<pocket-id-host>",
"type": "oauth-access-token"
}
This token will pass signature and aud validation at any resource server that trusts Pocket ID's JWKS and accepts client-b as its audience identifier.
Impact
Any OIDC client registered in Pocket ID that has valid credentials (client secret or federated identity) can obtain a Pocket ID-signed access token scoped to any other registered client or arbitrary audience string. In deployments where services use the client credentials grant and validate incoming bearer tokens against Pocket ID's JWKS, this allows a compromised or malicious client to bypass audience-based access controls and present tokens to resource servers it should not be authorized to access.
The severity depends on deployment: if no resource servers validate Pocket ID-issued access tokens, there is no immediate exploitable impact. In deployments that do rely on the client credentials grant for service-to-service authorization, the isolation between services is entirely broken.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/pocket-id/pocket-id/backend"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.0.0-20260622164202-8158452b3730"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-62662"
],
"database_specific": {
"cwe_ids": [
"CWE-20",
"CWE-863"
],
"github_reviewed": true,
"github_reviewed_at": "2026-11-09T21:17:04Z",
"nvd_published_at": "2026-11-04T23:17:56Z",
"severity": "HIGH"
},
"details": "## Summary\n\nThe client credentials grant (`grant_type=client_credentials`) does not validate the resource parameter before embedding it as the aud claim in the issued JWT. Any authenticated OIDC client can obtain a Pocket ID-signed access token claiming authorization for any arbitrary audience, including other registered OIDC clients. There is no check that the requested audience is a registered resource server, and no check that the requesting client has been granted permission to access it.\n\n## Details\n\nIn `backend/internal/service/oidc_service.go`, `createTokenFromClientCredentials` accepts the resource parameter from the token request and places it directly into the `aud` claim of the issued JWT without validation:\n\n```golang\n audClaim := client.ID\n if input.Resource != \"\" {\n audClaim = input.Resource // no validation performed\n }\n accessToken, err := s.jwtService.GenerateOAuthAccessToken(dummyUser, audClaim)\n```\n\nThe only guard in this function is `verifyClientCredentialsInternal`, which confirms the requesting client\u0027s credentials are valid. After that check passes, any value supplied as resource is accepted. There is no registry of permitted audiences, no per-client authorization policy controlling which resources a client may request tokens for, and no validation that the resource value corresponds to anything registered in Pocket ID.\n\nThe introspection endpoint (`/api/oidc/introspect`) does not mitigate this. It checks that the introspecting client\u0027s ID matches the token\u0027s `aud` claim, but since the attacker controls `aud`, a resource server calling introspection to validate an inbound token will receive `\"active\": true`. Pocket ID has no server-side record of which client requested the token or whether it was authorized to do so, so introspection cannot distinguish a legitimately issued token from one obtained through this vulnerability.\n\n## PoC\n\nGiven a Pocket ID instance with two registered OIDC clients \u2014 `client-a` (attacker-controlled) and `client-b` (target resource) \u2014 the following request causes Pocket ID to issue a signed JWT with `aud: [\"client-b\"]`, authenticated only as `client-a`:\n\n```\ncurl -X POST https://\u003cpocket-id-host\u003e/api/oidc/token \\\n -d \"grant_type=client_credentials\" \\\n -d \"client_id=client-a\" \\\n -d \"client_secret=\u003cclient-a-secret\u003e\" \\\n -d \"resource=client-b\"\n```\n\nThe response is a valid, Pocket ID-signed JWT with:\n\n```json\n{\n \"sub\": \"client-client-a\",\n \"aud\": [\"client-b\"],\n \"iss\": \"https://\u003cpocket-id-host\u003e\",\n \"type\": \"oauth-access-token\"\n}\n```\n\nThis token will pass signature and `aud` validation at any resource server that trusts Pocket ID\u0027s JWKS and accepts `client-b` as its audience identifier.\n\n## Impact\n\nAny OIDC client registered in Pocket ID that has valid credentials (client secret or federated identity) can obtain a Pocket ID-signed access token scoped to any other registered client or arbitrary audience string. In deployments where services use the client credentials grant and validate incoming bearer tokens against Pocket ID\u0027s JWKS, this allows a compromised or malicious client to bypass audience-based access controls and present tokens to resource servers it should not be authorized to access.\n\nThe severity depends on deployment: if no resource servers validate Pocket ID-issued access tokens, there is no immediate exploitable impact. In deployments that do rely on the client credentials grant for service-to-service authorization, the isolation between services is entirely broken.",
"id": "GHSA-q4xm-p75h-h9p3",
"modified": "2026-11-09T21:17:04Z",
"published": "2026-11-09T21:17:04Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pocket-id/pocket-id/security/advisories/GHSA-q4xm-p75h-h9p3"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-62662"
},
{
"type": "WEB",
"url": "https://github.com/pocket-id/pocket-id/pull/1520"
},
{
"type": "WEB",
"url": "https://github.com/pocket-id/pocket-id/commit/8158452b37304115c97c61c3a56082ce938ce6f0"
},
{
"type": "PACKAGE",
"url": "https://github.com/pocket-id/pocket-id"
},
{
"type": "WEB",
"url": "https://github.com/pocket-id/pocket-id/releases/tag/v2.10.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Pocket ID: The client credentials grant allows any OIDC client to obtain tokens for arbitrary audiences"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
How durations are calculated
Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.
Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.