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).
Encrypt with authenticated AEAD and a unique nonce, never ECB or unauthenticated modes
Use AES-256-GCM/ChaCha20-Poly1305 with a fresh random nonce per message; never aes-*-ecb or MAC-less CBC.
Bad example
| 1 | import { createCipheriv, createDecipheriv } from 'node:crypto'; |
| 2 |
|
| 3 | const KEY = Buffer.from(process.env.ENC_KEY!, 'hex'); |
| 4 | const IV = Buffer.alloc(16, 0); // FIXED IV — reused for every message |
| 5 |
|
| 6 | export function encrypt(plaintext: string): string { |
| 7 | // ECB ignores the IV entirely and leaks plaintext patterns block-by-block. |
| 8 | const cipher = createCipheriv('aes-256-ecb', KEY, null); |
| 9 | return Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]).toString('hex'); |
| 10 | } |
| 11 |
|
| 12 | // A CBC variant with this fixed IV and no MAC is equally broken: |
| 13 | // createCipheriv('aes-256-cbc', KEY, IV) -> padding-oracle / tamperable. |
| 14 | void IV; |
| 15 | void createDecipheriv; |
Explanation (EN)
AES-ECB encrypts each block independently, so identical plaintext blocks produce identical ciphertext — structure and duplicates leak (the classic 'ECB penguin'). CBC fixes patterning but, without a MAC, ciphertext is malleable and vulnerable to padding-oracle attacks. A fixed/reused IV or nonce destroys the security of any mode. None of these detect tampering, so the system has no integrity guarantee.
Objašnjenje (HR)
AES-ECB enkriptira svaki blok neovisno, pa identicni blokovi otvorenog teksta daju identican sifrat — struktura i duplikati cure ('ECB pingvin'). CBC uklanja uzorke, ali bez MAC-a sifrat je promjenjiv i ranjiv na padding-oracle napade. Fiksni/ponovljeni IV ili nonce unistava sigurnost bilo kojeg moda. Nijedan od ovih ne detektira manipulaciju, pa sustav nema jamstvo integriteta.
Good example
| 1 | import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto'; |
| 2 |
|
| 3 | const KEY = Buffer.from(process.env.ENC_KEY_HEX ?? '', 'hex'); // 32 bytes = AES-256 |
| 4 |
|
| 5 | export function encrypt(plaintext: string): string { |
| 6 | const iv = randomBytes(12); // 96-bit nonce, fresh per message |
| 7 | const cipher = createCipheriv('aes-256-gcm', KEY, iv); |
| 8 | const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); |
| 9 | const tag = cipher.getAuthTag(); |
| 10 | // Store iv + tag + ciphertext together. |
| 11 | return [iv.toString('hex'), tag.toString('hex'), ct.toString('hex')].join(':'); |
| 12 | } |
| 13 |
|
| 14 | export function decrypt(payload: string): string { |
| 15 | const [ivHex, tagHex, ctHex] = payload.split(':'); |
| 16 | const decipher = createDecipheriv('aes-256-gcm', KEY, Buffer.from(ivHex, 'hex')); |
| 17 | decipher.setAuthTag(Buffer.from(tagHex, 'hex')); |
| 18 | // final() throws if the tag fails — tampering is rejected, not silently accepted. |
| 19 | return Buffer.concat([decipher.update(Buffer.from(ctHex, 'hex')), decipher.final()]).toString('utf8'); |
| 20 | } |
Explanation (EN)
AES-256-GCM is an AEAD mode: it provides both confidentiality and integrity, and decryption fails loudly if the ciphertext is tampered with. A fresh 96-bit random nonce per message (stored alongside the ciphertext) prevents catastrophic nonce reuse. ChaCha20-Poly1305 is an equally good choice, especially without hardware AES. Persist nonce + auth tag + ciphertext together so decryption is self-contained.
Objašnjenje (HR)
AES-256-GCM je AEAD mod: pruza i povjerljivost i integritet, a desifriranje glasno padne ako je sifrat izmijenjen. Svjezi 96-bitni nasumicni nonce po poruci (spremljen uz sifrat) sprjecava katastrofalnu ponovnu upotrebu noncea. ChaCha20-Poly1305 jednako je dobar izbor, osobito bez hardverskog AES-a. Spremi nonce + auth tag + sifrat zajedno da desifriranje bude samodostatno.
Exceptions / Tradeoffs (EN)
Prefer a vetted high-level library (libsodium/sealedbox, AWS/GCP KMS, Tink) over hand-rolled primitives whenever possible — these examples are the floor, not the goal. For envelope encryption, generate a per-record data key and wrap it with a KMS key.
Iznimke / Tradeoffi (HR)
Kad god je moguce, koristi provjerenu visoko-razinsku biblioteku (libsodium/sealedbox, AWS/GCP KMS, Tink) umjesto rucno slozenih primitiva — ovi primjeri su minimum, ne cilj. Za envelope enkripciju generiraj data kljuc po zapisu i omotaj ga KMS kljucem.