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).
Derive identity, role, and ownership from the session — never from client input
Take userId/role/tenant/ownership exclusively from the authenticated session or token. Ignore (or strictly validate against the session) any such fields sent in the request body, query, or headers.
Bad example
| 1 | // POST /api/portfolios { name, ownerId, role } |
| 2 | export default async function handler(req: NextApiRequest, res: NextApiResponse) { |
| 3 | await requireSession(req); |
| 4 |
|
| 5 | // Trusts client-supplied ownerId and role -> privilege escalation + IDOR. |
| 6 | const portfolio = await db.portfolio.create({ data: req.body }); |
| 7 | return res.status(201).json(portfolio); |
| 8 | } |
| 9 |
|
| 10 | // Or updating your own user: |
| 11 | // await db.user.update({ where: { id: sessionUserId }, data: req.body }); |
| 12 | // req.body could include { role: 'admin' } -> instant privilege escalation. |
Explanation (EN)
Spreading `req.body` into a create/update lets the caller set `ownerId` (claim another user's resource), `role` ('admin'), or `accountId` (cross-tenant). This is mass assignment / over-posting and is a direct privilege-escalation and IDOR vector. The server is treating client-provided identity fields as authoritative.
Objašnjenje (HR)
Širenje `req.body` u create/update dopušta pozivatelju da postavi `ownerId` (prisvoji tuđi resurs), `role` ('admin') ili `accountId` (cross-tenant). To je mass assignment / over-posting i izravan vektor eskalacije ovlasti i IDOR-a. Server tretira identitetska polja koja je dao klijent kao mjerodavna.
Good example
| 1 | // POST /api/portfolios { name } |
| 2 | const CreatePortfolio = z.object({ name: z.string().min(1).max(120) }); |
| 3 |
|
| 4 | export default async function handler(req: NextApiRequest, res: NextApiResponse) { |
| 5 | const { userId } = await requireSession(req); |
| 6 |
|
| 7 | // Whitelist only client-owned fields; identity comes from the session. |
| 8 | const parsed = CreatePortfolio.safeParse(req.body); |
| 9 | if (!parsed.success) return res.status(400).json({ message: 'Invalid body' }); |
| 10 |
|
| 11 | const portfolio = await db.portfolio.create({ |
| 12 | data: { name: parsed.data.name, ownerId: userId }, // server sets owner |
| 13 | }); |
| 14 | return res.status(201).json(portfolio); |
| 15 | } |
Explanation (EN)
A schema whitelists exactly the fields the client may set (`name`), and security-critical fields (`ownerId`) are stamped from the server-side session. Even if the client sends `ownerId` or `role`, those are dropped by the parse. Identity and authority always originate server-side.
Objašnjenje (HR)
Shema whitelista točno polja koja klijent smije postaviti (`name`), a sigurnosno kritična polja (`ownerId`) postavljaju se iz serverske sesije. Čak i ako klijent pošalje `ownerId` ili `role`, parse ih odbacuje. Identitet i ovlast uvijek potječu sa servera.
Exceptions / Tradeoffs (EN)
An admin endpoint may legitimately set another user's role or ownerId — but only after a server-side check that the caller holds that admin privilege, and ideally through a separate, explicitly-named endpoint (e.g. POST /admin/users/:id/role) rather than a general update that accepts arbitrary fields.
Iznimke / Tradeoffi (HR)
Admin endpoint smije legitimno postaviti ulogu ili ownerId drugog korisnika — ali tek nakon serverske provjere da pozivatelj ima tu admin ovlast, i idealno kroz zaseban, eksplicitno imenovan endpoint (npr. POST /admin/users/:id/role) umjesto općeg updatea koji prima proizvoljna polja.