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).
Store passwords with a salted, memory-hard hash (argon2id/bcrypt), never a fast hash
Use argon2id or bcrypt with built-in per-password salting; never MD5/SHA-1/SHA-256, plaintext, or reversible encryption for passwords.
Bad example
| 1 | import { createHash } from 'node:crypto'; |
| 2 |
|
| 3 | // Fast hash + static/no salt -> trivially cracked offline |
| 4 | export function hashPassword(pw: string) { |
| 5 | return createHash('sha256').update(pw).digest('hex'); |
| 6 | } |
| 7 |
|
| 8 | export function verifyPassword(pw: string, stored: string) { |
| 9 | return createHash('sha256').update(pw).digest('hex') === stored; // also non-constant-time |
| 10 | } |
Explanation (EN)
SHA-256 is a fast hash designed for throughput, so a leaked database is cracked at billions of guesses per second; with no per-password salt, identical passwords collide and rainbow tables apply. The === comparison also short-circuits, leaking timing. This is plaintext-equivalent security.
Objašnjenje (HR)
SHA-256 je brzi hash projektiran za propusnost pa se procurena baza razbija milijardama pokusaja u sekundi; bez salta po lozinki iste lozinke daju isti hash i primjenjive su rainbow tablice. Usporedba === k tome kratkospaja i odaje vremenske informacije. To je sigurnost ekvivalentna obicnom tekstu.
Good example
| 1 | import argon2 from 'argon2'; |
| 2 |
|
| 3 | export function hashPassword(pw: string) { |
| 4 | // salt + cost params are generated and embedded in the returned string |
| 5 | return argon2.hash(pw, { type: argon2.argon2id }); |
| 6 | } |
| 7 |
|
| 8 | export function verifyPassword(stored: string, pw: string) { |
| 9 | return argon2.verify(stored, pw); // constant-time, reads params from the hash |
| 10 | } |
| 11 |
|
| 12 | // bcrypt is an acceptable alternative: |
| 13 | // await bcrypt.hash(pw, 12); await bcrypt.compare(pw, stored); |
Explanation (EN)
argon2id is memory-hard and adaptive: it generates a unique salt per password and embeds the cost parameters in the hash, so cracking is expensive and parameters can be raised over time. verify() is constant-time and self-describing. bcrypt (cost >= 12) is the accepted fallback where argon2 isn't available.
Objašnjenje (HR)
argon2id je memorijski zahtjevan i prilagodljiv: generira jedinstveni salt po lozinki i ugraduje parametre cijene u hash, pa je razbijanje skupo, a parametri se mogu vremenom povecavati. verify() je konstantnog vremena i samoopisujuci. bcrypt (cost >= 12) je prihvatljiva zamjena gdje argon2 nije dostupan.
Notes (EN)
Re-hash on next successful login when you raise cost parameters; store the algorithm version alongside the hash to enable migration.
Bilješke (HR)
Ponovo hashaj pri sljedecoj uspjesnoj prijavi kad podignes parametre cijene; pohrani verziju algoritma uz hash radi migracije.
Exceptions / Tradeoffs (EN)
PBKDF2-HMAC-SHA256 with a high iteration count is acceptable only when a FIPS-validated algorithm is mandated. Pre-hash with HMAC if you must support passwords longer than bcrypt's 72-byte limit. High-entropy machine secrets (API keys, random tokens) don't need a slow KDF and may use SHA-256.
Iznimke / Tradeoffi (HR)
PBKDF2-HMAC-SHA256 s visokim brojem iteracija prihvatljiv je samo kad je propisan FIPS-validirani algoritam. Pre-hashaj HMAC-om ako moras podrzati lozinke duze od bcryptovih 72 bajta. Strojne tajne visoke entropije (API kljucevi, nasumicni tokeni) ne trebaju spori KDF i smiju koristiti SHA-256.