Feb 27, 2026·7 min read·4 visits
Authenticated users can opt themselves out of mandatory SSO by sending a crafted JSON payload to the user settings API. This enables local password login, bypassing corporate IdP controls.
A mass assignment vulnerability in n8n's self-service settings API allows authenticated users to bypass mandatory Single Sign-On (SSO) enforcement. By manipulating the `/me/settings` endpoint, users can enable local password authentication for their accounts, effectively circumventing identity provider controls and multi-factor authentication policies.
n8n has rapidly become the Swiss Army knife of the modern enterprise. It connects everything—your CRM, your Slack, your production databases, and that weird legacy API nobody understands. Because it sits at the intersection of all your critical data, securing access to n8n is paramount. For most organizations, that means one thing: Single Sign-On (SSO).
SSO is the bouncer at the club. It decides who gets in, checks their ID (authentication), and makes sure they aren't carrying weapons (authorization). When an admin enforces SSO, they are essentially saying, "Nobody gets in unless Okta/Google says so." It allows for centralized revocation, audit trails, and, crucially, Multi-Factor Authentication (MFA).
But what if the bouncer had a tip jar? What if you could just walk up to the velvet rope, slip the bouncer a twenty (or a JSON boolean), and have him wave you through the side door forever? That is exactly what GHSA-VJF3-2GPJ-233V allows. It is a logic flaw that lets any authenticated user whisper to the backend, "Hey, don't worry about that SSO stuff for me," and the backend simply nods and agrees.
This vulnerability is a textbook example of Mass Assignment, also known as Overposting (CWE-915). In modern web frameworks, developers often use Data Transfer Objects (DTOs) to define the shape of incoming data. A lazy or efficient (depending on your cynicism) developer might define a single DTO for a user entity and reuse it across multiple endpoints—creation, admin updates, and self-service updates.
The problem arises when that DTO includes fields that should never be touched by a standard user. In n8n's case, the MeController, which handles the /me endpoints for the currently logged-in user, was reusing a DTO intended for administrative actions. This DTO contained the keys to the castle, specifically a property called allowSSOManualLogin.
Here is the logic gap: The endpoint /me/settings is designed to let you change benign preferences, like whether you want to see the "Welcome to AI" onboarding modal. However, because the backend blindly accepted the entire DTO and merged it into the user's record in the database, it didn't just update the UI preferences. It updated everything you sent it, provided the field existed in the schema. It's akin to filling out a change-of-address form at the bank, writing "Account Balance: $1,000,000" in the margin, and having the teller update your ledger because they didn't check which fields were read-only.
The smoking gun lies in packages/cli/src/controllers/me.controller.ts. Prior to version 2.8.0, the code handling user updates looked something like this (simplified for clarity):
// VULNERABLE CODE
@Patch('/settings')
async updateCurrentUserSettings(
req: AuthenticatedRequest,
_: Response,
// The flaw: Using the Admin-level DTO for a user-level endpoint
@Body payload: SettingsUpdateRequestDto,
): Promise<User['settings']> {
const { id } = req.user;
// The payload is passed directly to the service layer
return await this.usersService.updateSettings(id, payload);
}The SettingsUpdateRequestDto was a wide-open door. It included fields that controlled security policies. The fix, implemented in commit a70b2ea379086da3de103bb84811e88cadf29976, was to introduce a strict whitelist. The developers created a new DTO specifically for self-service updates.
// FIXED CODE
// packages/@n8n/api-types/src/dto/user/user-self-settings-update-request.dto.ts
export class UserSelfSettingsUpdateRequestDto extends Z.class({
// Only benign fields are allowed here
easyAIWorkflowOnboarded: z.boolean().optional(),
dismissedCallouts: z.record(z.string(), z.boolean()).optional(),
}) {}
// packages/cli/src/controllers/me.controller.ts
@Patch('/settings')
async updateCurrentUserSettings(
req: AuthenticatedRequest,
_: Response,
// The fix: Using the restricted DTO
@Body payload: UserSelfSettingsUpdateRequestDto,
): Promise<User['settings']> {
// ...
}By switching the type definition in the controller method signature, the framework's validation layer (Zod) now automatically strips out any field not explicitly defined in UserSelfSettingsUpdateRequestDto. If a hacker tries to send allowSSOManualLogin, the validator effectively ignores it before it ever reaches the database logic.
Exploiting this is trivially easy for anyone who already has access (or has compromised a low-level account). You don't need buffer overflows or heap spraying; you just need curl or Burp Suite.
Scenario: You are an employee at a company enforcing strict SSO via Okta. You want to maintain access even if your Okta account is suspended, or perhaps you just hate 2FA. You are currently logged in.
Step 1: Intercept the request when you change a setting in the UI, or construct a fresh one.
Step 2: Send the payload with the forbidden flag.
PATCH /me/settings HTTP/1.1
Host: n8n.corp.internal
Authorization: Bearer <YOUR_JWT_TOKEN>
Content-Type: application/json
{
"allowSSOManualLogin": true,
"easyAIWorkflowOnboarded": true
}Step 3: The server responds with 200 OK. The database now has your user record marked as allowed to use manual login.
Step 4: You can now set a local password (if you haven't already) or use the "Forgot Password" flow to establish one. From this point forward, you can log in directly at the /login page, completely bypassing the "Log in with SSO" button and any MFA checks associated with the IdP.
While the CVSS score is a moderate 4.3 (due to the requirement of prior authentication), the practical impact for an enterprise is significant. This is a Persistence technique.
Imagine an insider threat scenario: An employee is about to be fired. Knowing their access will be revoked in the Identity Provider (IdP) at 5:00 PM, they execute this exploit at 4:55 PM. At 5:01 PM, the sysadmin kills their Okta account. The employee goes home, opens n8n, types in their local email and password, and they are back in. They still have access to all the API keys, credentials, and workflows stored in n8n.
Furthermore, this breaks the chain of trust. Security compliance standards (SOC2, ISO27001) often rely on the assertion that "Access is strictly controlled via SSO with MFA." This vulnerability proves that assertion false for the affected versions. A user could theoretically have a weak password like password123 on a system protecting production database credentials, and the security team would be none the wiser because they believe SSO is enforcing strong auth.
Remediation is straightforward but requires action on two fronts: patching the code and scrubbing the data.
1. Patching: Upgrade to n8n version 2.8.0 or later immediately. This version introduces the DTO segregation that physically prevents the API from accepting the malicious field.
2. Hunting: Patching stops new exploits, but it does not revert changes made by past exploits. If a malicious user has already flipped this bit, upgrading the software might not reset it (depending on how the migration scripts are written, but usually, user data is preserved). You must audit your database.
Run the following SQL query against your n8n postgres database to find users who have effectively opted out of SSO:
SELECT id, email, "firstName", "lastName"
FROM "user"
WHERE "settings"->>'allowSSOManualLogin' = 'true'
AND "role" != 'global:owner'; -- Assuming owners might legitimately have thisIf you find any non-admin users in this list, reset their settings immediately and investigate their activity logs.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
n8n n8n.io | < 2.8.0 | 2.8.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-915 (Mass Assignment) |
| CVSS v3.1 | 4.3 (Medium) |
| Attack Vector | Network (Authenticated) |
| Impact | Security Policy Bypass (SSO/MFA) |
| Patch Commit | a70b2ea379086da3de103bb84811e88cadf29976 |
| Fixed Version | 2.8.0 |
The software allows the user to modify object attributes that should be restricted, leading to unauthorized changes in security settings.