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).
Return explicit field projections, never raw DB rows or upstream payloads
Map server responses to an explicit DTO/projection of fields the client actually needs. Never return a full database entity, ORM model, or upstream API payload directly to the client.
Bad example
| 1 | // pages/api/user/[id].ts (Next.js API route) |
| 2 | export default async function handler(req, res) { |
| 3 | const user = await userService.findById(req.query.id); |
| 4 | // leaks passwordHash, email, internalRole, deletedAt, ... |
| 5 | res.status(200).json(user); |
| 6 | } |
| 7 |
|
| 8 | // Nest controller |
| 9 | @Get(':id') |
| 10 | async getUser(@Param('id') id: string) { |
| 11 | return this.users.findOne(id); // returns the full entity |
| 12 | } |
Explanation (EN)
Returning the entity ships every column to the client — password hashes, email addresses, internal role flags, soft-delete timestamps. Clients (and attackers) read it straight from devtools or the network tab. New columns added later silently join the leak. This is OWASP A01 (Broken Access Control) via excessive data exposure.
Objašnjenje (HR)
Vraćanje entiteta šalje svaki stupac klijentu — hash lozinke, e-mail adrese, interne oznake uloga, vremenske oznake mekog brisanja. Klijenti (i napadači) to čitaju izravno iz devtools-a ili network taba. Novi stupci dodani kasnije tiho se pridruže curenju. Ovo je OWASP A01 kroz prekomjerno izlaganje podataka.
Good example
| 1 | // pages/api/user/[id].ts |
| 2 | export default async function handler(req, res) { |
| 3 | const user = await userService.findById(req.query.id); |
| 4 | if (!user) return res.status(404).end(); |
| 5 | res.status(200).json({ |
| 6 | id: user.id, |
| 7 | displayName: user.displayName, |
| 8 | avatarUrl: user.avatarUrl, |
| 9 | }); |
| 10 | } |
| 11 |
|
| 12 | // Nest: enforce projection with a DTO + class-transformer |
| 13 | @Get(':id') |
| 14 | @SerializeOptions({ strategy: 'excludeAll' }) // only @Expose()'d fields leave |
| 15 | async getUser(@Param('id') id: string): Promise<PublicUserDto> { |
| 16 | return plainToInstance(PublicUserDto, await this.users.findOne(id)); |
| 17 | } |
Explanation (EN)
The response is an explicit allow-list of public fields, so adding sensitive columns later cannot widen exposure by accident. In Nest, an `excludeAll` serialization strategy makes the projection fail-closed — a field leaks only if someone deliberately `@Expose()`s it. The shape of what leaves the server is now reviewable in one place.
Objašnjenje (HR)
Odgovor je eksplicitna dozvoljena lista javnih polja, pa dodavanje osjetljivih stupaca kasnije ne može slučajno proširiti izloženost. U Nestu, `excludeAll` strategija serijalizacije čini projekciju 'fail-closed' — polje procuri samo ako ga netko namjerno `@Expose()`-a. Oblik onoga što napušta server sada je provjerljiv na jednom mjestu.
Exceptions / Tradeoffs (EN)
Trusted internal service-to-service calls behind a network boundary may pass richer payloads, but anything reachable by a browser or third party must be projected. When the client genuinely needs many fields, still define the DTO explicitly rather than spreading the entity.
Iznimke / Tradeoffi (HR)
Pouzdani interni pozivi servis-na-servis iza mrežne granice mogu prosljeđivati bogatije podatke, ali sve dohvatljivo iz preglednika ili treće strane mora biti projicirano. Kada klijentu zaista treba mnogo polja, ipak definirajte DTO eksplicitno umjesto da širite entitet.