T09 · Insecure Skill Coding Practices
Error
- Location
- SKILL.md:316
- Finding
- Public User Registration Allows Self-Assignment of Administrator Role<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:316-336` **Vulnerability Type**: User-controlled privilege assignment **Risk Level**: High ```typescript const CreateUserSchema = z.object({ email: z.string().email().max(255).toLowerCase(), name: z.string().min(1).max(100).trim(), role: z.enum(['user', 'admin']).default('user'), }); // Usage with Hono app.post('/users', zValidator('json', CreateUserSchema), async (c) => { const body = c.req.valid('json'); // Fully typed! const user = await userService.create(body); return c.json({ data: user }, 201); }); ``` ### Technical Analysis The public user-creation schema accepts both `user` and `admin` as valid role values. The route then forwards the entire validated request body to `userService.create` without an authentication or authorization check and without replacing the submitted role with a server-controlled value. Schema validation only confirms that `admin` is an allowed string; it does not establish that the requester is authorized to grant that role. If this illustrative template is copied into a production application, it creates a mass-assignment vulnerability at an authorization boundary. The Skill itself is documentation and does not directly execute this code. The vulnerability arises in applications generated from or modeled on this example. ### Attack Path 1. An attacker locates the unauthenticated `POST /users` registration endpoint. 2. The attacker submits a request such as: ```http POST /users HTTP/1.1 Content-Type: application/json { "email": "attacker@example.com", "name": "Attacker", "role": "admin" } ``` 3. `CreateUserSchema` accepts `admin` because it is explicitly included in the role enumeration. 4. The validated body, including the attacker-selected role, is passed directly to `userService.create`. 5. If the service and repository preserve the supplied role as shown by the surrounding pattern, the account is created with administrator privileges. ...[truncated 606 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Remove `role` from all public registration and profile-update request schemas. - Assign the initial role exclusively on the server: ```typescript const PublicRegistrationSchema = z.object({ email: z.string().email().max(255).toLowerCase(), name: z.string().min(1).max(100).trim(), }); app.post('/users', zValidator('json', PublicRegistrationSchema), async (c) => { const body = c.req.valid('json'); const user = await userService.create({ ...body, role: 'user' }); return c.json({ data: user }, 201); }); ``` - Implement role changes through a separate endpoint requiring authentication and an explicit administrator permission. - Enforce authorization again in the service layer so route omissions do not permit privilege escalation. - Use allowlisted data-transfer objects rather than passing complete request bodies into persistence methods. - Add negative security tests confirming that public registration cannot assign or modify privileged roles. - Record privileged role changes in an immutable audit log. ]]>
