A user update endpoint that accepts a role or isAdmin field in the request body without admin verification is a mass assignment vulnerability (CWE-915) enabling privilege escalation (CWE-285). Any authenticated user who POSTs { "role": "admin" } to their own profile endpoint will elevate their privileges if the backend spreads the request body directly into a database update. OWASP A01 (Broken Access Control) flags this pattern explicitly. NIST 800-53 AC-6 (Least Privilege) requires that users cannot grant themselves permissions beyond what they were assigned.
Low because the attacker must be authenticated and must know which fields to tamper with, but self-promotion to admin via a single request is a complete authorization bypass.
Define a profile update Zod schema that does not include role, permissions, or any privilege field. Handle role changes through a separate admin-only endpoint.
// lib/schemas/user.ts
export const UserProfileUpdateSchema = z.object({
displayName: z.string().optional(),
bio: z.string().optional(),
// 'role' is intentionally absent
});
// app/api/admin/users/[id]/role/route.ts — separate admin-only endpoint
export async function PATCH(req: NextRequest, { params }) {
const session = await auth();
if (session?.user.role !== 'admin') return NextResponse.json({}, { status: 403 });
const { role } = await req.json();
await db.user.update({ where: { id: params.id }, data: { role } });
}
UserProfileUpdateSchema.parse() silently drops any fields not in the schema, so attackers cannot inject privilege fields even if they try.
ID: saas-authorization.access-control.role-assignment-restricted
Severity: low
What to look for: Count all relevant instances and enumerate each. Find API routes or Server Actions that update user records — PATCH /api/users/[id], updateUser, updateProfile, inviteMember. Check whether these endpoints accept a role or permissions field in the request body. If they do, check whether the handler verifies that the requester has admin-level authority to set that field before applying the change.
Pass criteria: Endpoints that update user data either (a) do not accept role/permissions fields at all in their input schema, or (b) explicitly verify the requester holds admin-level authority before applying a role change. At least 1 implementation must be verified.
Fail criteria: A user update endpoint accepts a role, permissions, isAdmin, plan, or similar field in the request body and applies it without checking that the requester has administrative authority to make that change.
Skip (N/A) when: No user update endpoints or role management functionality detected.
Detail on fail: "User update endpoint accepts role or permission fields without verifying admin authority. Mass assignment may allow self-promotion." (Note the specific files.)
Remediation: Use Zod (or your validation library) to define an update schema that explicitly excludes sensitive fields. Handle role changes through a separate, admin-only endpoint.
// lib/schemas/user.ts
// Profile updates — role field is absent from this schema
export const UserProfileUpdateSchema = z.object({
displayName: z.string().optional(),
bio: z.string().optional(),
// 'role' is intentionally NOT here
});
// app/api/admin/users/[id]/role/route.ts — separate admin-only endpoint
export async function PATCH(req: NextRequest, { params }) {
const session = await auth();
if (session?.user.role !== 'admin') return NextResponse.json({}, { status: 403 });
const { role } = await req.json();
await db.user.update({ where: { id: params.id }, data: { role } });
}
Cross-reference: For related patterns and deeper analysis, see the corresponding checks in other AuditBuffet audits covering this domain.