The update example inserts request-controlled field names into SQL, enabling injection or unauthorized column changes
Source references: 2The controller passes the whole request body as updates, while the repository builds SQL identifiers from Object.keys(updates). Only values use placeholders; attacker-controlled field names are interpolated directly. A TypeScript DTO does not filter HTTP JSON at runtime.
If an attacker can call the update endpoint, a crafted field name could modify roles, permissions, or other disallowed columns, alter query structure, or repeatedly break the query. The operation runs with the application's database privileges.
This is a copyable documentation example, not code executed merely by reading the Skill. If adopted, however, the request body becomes `updates` with no shown runtime allowlist. The repository parameterizes values but interpolates `Object.keys(updates)` as SQL identifiers. A crafted key could alter SQL structure, while ordinary extra keys could enable unauthorized column updates. Users can ask for strict runtime schema validation and a fixed mapping of permitted update columns.
async updateUser(req: Request, res: Response, next: NextFunction) { try { const { id } = req.params; const updates: UpdateUserDTO = req.body; const user = await this.userService.updateUser(id, updates); res.json(user); } catch (error) {Show 1 other places
async update(id: string, updates: UpdateUserDTO): Promise<UserEntity | null> { const fields = Object.keys(updates); const values = Object.values(updates); const setClause = fields .map((field, idx) => `${field} = $${idx + 2}`) .join(", "); const query = ` UPDATE users SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING * `; const { rows } = await this.db.query(query, [id, ...values]); return rows[0] || null;