Part of #3848 · severity: high · related: #3825
Where
modules/users/models/users.schema.js (fields 26-27, 39-40, 42, 48-50), modules/auth/routes/auth.routes.js:73, modules/auth/controllers/auth.controller.js:119.
Problem
The signup route validates with model.isValid(UsersSchema.User). The model middleware replaces req.body with the full Zod-parsed User, so every optional schema field is client-writable on signup: emailVerified, providerData, additionalProvidersData, resetPasswordToken/Expires, failedLoginAttempts/lockUntil/lastLoginAt. The controller only overrides roles.
Exploit
- Email-verification bypass:
POST /api/auth/signup { …, "emailVerified": true } creates a verified account, defeating verification gates (org creation, join requests, domain search).
- OAuth account pre-hijacking: pre-register
victim@domain with emailVerified:true and seeded providerData; when the real victim later signs in with the provider, the lookup hits the attacker-seeded record.
Fix
Don't use the full Mongoose-mirroring User schema as the signup write surface. Add a dedicated signup Zod schema with only client-settable fields (firstName, lastName, email, password, optional bio/position/terms), .strict(). Defense in depth: in the controller force emailVerified:false and delete the server-owned fields before UserService.create. The server must own identity/trust fields. (Cross-refs #3825, the analogous email-change path.)
Created via /dev:issue
Part of #3848 · severity: high · related: #3825
Where
modules/users/models/users.schema.js(fields 26-27, 39-40, 42, 48-50),modules/auth/routes/auth.routes.js:73,modules/auth/controllers/auth.controller.js:119.Problem
The signup route validates with
model.isValid(UsersSchema.User). The model middleware replacesreq.bodywith the full Zod-parsedUser, so every optional schema field is client-writable on signup:emailVerified,providerData,additionalProvidersData,resetPasswordToken/Expires,failedLoginAttempts/lockUntil/lastLoginAt. The controller only overridesroles.Exploit
POST /api/auth/signup { …, "emailVerified": true }creates a verified account, defeating verification gates (org creation, join requests, domain search).victim@domainwithemailVerified:trueand seededproviderData; when the real victim later signs in with the provider, the lookup hits the attacker-seeded record.Fix
Don't use the full Mongoose-mirroring
Userschema as the signup write surface. Add a dedicated signup Zod schema with only client-settable fields (firstName,lastName,email,password, optionalbio/position/terms),.strict(). Defense in depth: in the controller forceemailVerified:falseanddeletethe server-owned fields beforeUserService.create. The server must own identity/trust fields. (Cross-refs #3825, the analogous email-change path.)Created via /dev:issue