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).
Guard against ReDoS: no catastrophic backtracking, no user-built regex
Avoid regex patterns with nested/overlapping quantifiers on attacker-controlled strings, never compile regex from user input, and bound input length before matching to prevent event-loop-blocking ReDoS.
Bad example
| 1 | // Nested quantifier -> catastrophic backtracking on long non-matching input |
| 2 | const EMAIL_RE = /^([a-zA-Z0-9]+)+@example\.com$/; |
| 3 |
|
| 4 | function isValid(input: string) { |
| 5 | return EMAIL_RE.test(input); // 'aaaaaaaaaaaaaaaaaaaaaaaa!' freezes the event loop |
| 6 | } |
| 7 |
|
| 8 | // Even worse: regex compiled from user input |
| 9 | const userRe = new RegExp(req.query.pattern as string); |
Explanation (EN)
`([a-zA-Z0-9]+)+` has overlapping quantifiers; on a long string that ultimately fails to match, the regex engine explores exponentially many paths, blocking Node's single thread for seconds and stalling all concurrent requests. Building a `RegExp` from user input lets an attacker supply the malicious pattern directly.
Objašnjenje (HR)
`([a-zA-Z0-9]+)+` ima preklapajuće kvantifikatore; na dugom stringu koji na kraju ne odgovara, regex engine istražuje eksponencijalno mnogo putova, blokirajući jedinu Node nit nekoliko sekundi i zaustavljajući sve istovremene zahtjeve. Gradnja `RegExp` iz korisničkog unosa dopušta napadaču da izravno dostavi zlonamjerni uzorak.
Good example
| 1 | // Length-bound the input, use a linear, non-backtracking pattern |
| 2 | const EMAIL_RE = /^[a-zA-Z0-9]+@example\.com$/; // no nested quantifiers |
| 3 |
|
| 4 | function isValid(input: string) { |
| 5 | if (input.length > 254) return false; // bound work before matching |
| 6 | return EMAIL_RE.test(input); |
| 7 | } |
| 8 |
|
| 9 | // If you truly need user-supplied search, treat it as a literal substring, |
| 10 | // not a regex: |
| 11 | const hits = rows.filter((r) => r.name.includes(userQuery)); |
| 12 | // or use a linear-time engine like 're2' for untrusted patterns |
Explanation (EN)
A flat pattern with no nested quantifiers runs in linear time, and capping input length bounds worst-case work. User-provided search is handled as a literal `includes` (or via a guaranteed-linear engine such as re2), so attackers can never inject a backtracking-prone pattern.
Objašnjenje (HR)
Plitki uzorak bez ugniježđenih kvantifikatora radi u linearnom vremenu, a ograničavanje duljine unosa ograničava najgori slučaj. Korisničko pretraživanje obrađuje se kao doslovni `includes` (ili putem zajamčeno linearnog enginea poput re2), pa napadači nikad ne mogu ubaciti uzorak sklon backtrackingu.
Exceptions / Tradeoffs (EN)
Trusted, developer-authored patterns run against short, length-bounded inputs are generally fine. When dynamic patterns are unavoidable, run them through a linear-time engine (re2) and/or enforce a per-match timeout in a worker thread rather than the main event loop.
Iznimke / Tradeoffi (HR)
Pouzdani uzorci koje pišu programeri nad kratkim, duljinski ograničenim unosima uglavnom su u redu. Kad su dinamički uzorci neizbježni, pokrenite ih kroz linearni engine (re2) i/ili nametnite timeout po podudaranju u worker niti umjesto na glavnoj event loop niti.