Skip to content

fix: Prevent admin role self-assignment via registration endpoint#1950

Open
fennhelloworld wants to merge 2 commits into
SecureBananaLabs:mainfrom
fennhelloworld:fix/admin-role-self-assignment-1823
Open

fix: Prevent admin role self-assignment via registration endpoint#1950
fennhelloworld wants to merge 2 commits into
SecureBananaLabs:mainfrom
fennhelloworld:fix/admin-role-self-assignment-1823

Conversation

@fennhelloworld
Copy link
Copy Markdown

Fix for #1823: Admin role self-assignment via registration endpoint

Vulnerability Description

The registration endpoint (POST /api/auth/register) accepted a role field in the request body, allowing any user to self-assign privileged roles including "admin". The vulnerable code was in apps/api/src/validators/auth.js:

// BEFORE (vulnerable)
export const registerSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  role: z.enum(["client", "freelancer", "admin"]).default("client")
});

An attacker could simply send:

curl -X POST /api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"attacker@evil.com","password":"password123","role":"admin"}'

And the service would create an account with role: "admin" and issue a JWT token with admin claims, granting full administrative access.

Fix Summary

Defense in depth — three layers of protection:

  1. Schema-level (validators/auth.js): Removed role from registerSchema and added .strict() to reject any unexpected fields. Sending "role" now results in a validation error (HTTP 400).

  2. Service-level (services/authService.js): registerUser() no longer reads payload.role. All new registrations are hardcoded to "client" via a DEFAULT_ROLE constant. Even if schema validation were bypassed, the service layer would not use a client-supplied role.

  3. Error handling (controllers/authController.js, middleware/errorHandler.js): Added proper ZodError catch blocks in the controller and error middleware so validation errors return clean 400 responses instead of unhandled rejections or 500s.

Files Changed

File Change
apps/api/src/validators/auth.js Removed role field, added .strict()
apps/api/src/services/authService.js Hardcoded DEFAULT_ROLE = "client", no longer reads payload.role
apps/api/src/controllers/authController.js Added ZodError try/catch for clean error responses
apps/api/src/middleware/errorHandler.js Added ZodError handling in error middleware
apps/api/src/tests/register.role-prevention.test.js New test file proving the fix works

Test Results

✓ POST /api/auth/register with role: "admin" → 400 (rejected)
✓ POST /api/auth/register with role: "freelancer" → 400 (rejected)
✓ POST /api/auth/register without role field → 201, role defaults to "client"

Closes #1823

…cureBananaLabs#1823)

- Remove 'role' field from registerSchema to prevent privilege escalation
- Add .strict() to registerSchema to reject any unexpected fields
- Default all new registrations to 'client' role in authService
- Add ZodError handling in authController and error middleware
- Add tests proving role self-assignment is blocked
github-actions Bot added a commit that referenced this pull request May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Admin role self-assignment via registration endpoint

1 participant