Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Prevent sensitive field exposure and mass assignment (BOPLA)
Return only allowed fields and accept only an allowlist of writable fields; never bind request bodies directly to models.
Bad example
| 1 | app.get('/users/:id', async (req, res) => { |
| 2 | const user = await db.user.findUnique({ where: { id: req.params.id } }); |
| 3 | return res.json(user); // returns everything including sensitive fields |
| 4 | }); |
| 5 |
|
| 6 | app.put('/users/:id', async (req, res) => { |
| 7 | // BAD: directly spreads client input into DB update |
| 8 | const updated = await db.user.update({ where: { id: req.params.id }, data: req.body }); |
| 9 | return res.json(updated); |
| 10 | }); |
Explanation (EN)
Returning full objects can leak sensitive fields. Updating with req.body enables mass assignment (e.g., role/isAdmin/blocked/price) if the ORM accepts unknown fields.
Objašnjenje (HR)
Vraćanje cijelog objekta može otkriti osjetljiva polja. Update s req.body omogućuje mass assignment (npr. role/isAdmin/blocked/price) ako ORM prihvaća ta polja.
Good example
| 1 | const UserPublicSchema = z.object({ |
| 2 | id: z.string(), |
| 3 | displayName: z.string(), |
| 4 | avatarUrl: z.string().url().nullable() |
| 5 | }); |
| 6 |
|
| 7 | app.get('/users/:id', async (req, res) => { |
| 8 | const user = await db.user.findUnique({ where: { id: req.params.id } }); |
| 9 | if (!user) return res.status(404).json({ message: 'Not found' }); |
| 10 |
|
| 11 | // pick / map only allowed fields |
| 12 | const publicUser = { id: user.id, displayName: user.displayName, avatarUrl: user.avatarUrl }; |
| 13 | const parsed = UserPublicSchema.safeParse(publicUser); |
| 14 | if (!parsed.success) return res.status(500).json({ message: 'Invalid server response' }); |
| 15 |
|
| 16 | return res.json(parsed.data); |
| 17 | }); |
| 18 |
|
| 19 | const UserUpdateSchema = z.object({ |
| 20 | displayName: z.string().min(1).optional(), |
| 21 | avatarUrl: z.string().url().nullable().optional() |
| 22 | }); |
| 23 |
|
| 24 | app.put('/users/:id', async (req, res) => { |
| 25 | const parsed = UserUpdateSchema.safeParse(req.body); |
| 26 | if (!parsed.success) return res.status(400).json({ errors: parsed.error.issues }); |
| 27 |
|
| 28 | const updated = await db.user.update({ where: { id: req.params.id }, data: parsed.data }); |
| 29 | return res.json({ id: updated.id, displayName: updated.displayName, avatarUrl: updated.avatarUrl }); |
| 30 | }); |
Explanation (EN)
Cherry-pick response fields and validate output shape. For writes, validate input and only allow explicitly writable fields.
Objašnjenje (HR)
Iz odgovora cherry-pickaj samo dopuštena polja i validiraj shape outputa. Za upise, validiraj input i dopusti samo eksplicitno writable polja.
Notes (EN)
Avoid generic toJSON/toString serialization. Keep payloads minimal. For GraphQL, enforce field-level auth and schema-based response validation. Treat 'hidden' fields as server-owned, never client-controlled.
Bilješke (HR)
Izbjegavaj generički toJSON/toString. Drži payload minimalnim. U GraphQL-u provedi field-level auth i schema-based response validation. 'Skrivena' polja smatraj server-owned, nikad client-controlled.