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).
Parameterize SQL queries; never string-build them from input
Pass user-controlled values as bound parameters (placeholders), never via string concatenation or template literals.
Bad example
| 1 | // Express + node-postgres (pg) |
| 2 | app.get('/users', async (req, res) => { |
| 3 | const { email } = req.query; |
| 4 | // ❌ user input concatenated straight into SQL text |
| 5 | const sql = `SELECT id, name FROM users WHERE email = '${email}'`; |
| 6 | const { rows } = await pool.query(sql); |
| 7 | res.json(rows); |
| 8 | }); |
| 9 | // email = "' OR '1'='1" -> dumps every row |
| 10 | // email = "x'; DROP TABLE users;--" -> destructive |
Explanation (EN)
The value from req.query is interpolated directly into the SQL string, so an attacker can break out of the quoted literal and inject arbitrary SQL (auth bypass, data exfiltration, table drops). No amount of quoting or escaping by hand is reliable.
Objašnjenje (HR)
Vrijednost iz req.query se izravno umeće u SQL string, pa napadač može izaći iz navodnika i ubaciti proizvoljan SQL (zaobilaženje autentikacije, krađa podataka, brisanje tablica). Ručno navođenje ili escapanje nije pouzdano.
Good example
| 1 | // Express + node-postgres (pg) |
| 2 | app.get('/users', async (req, res) => { |
| 3 | const { email } = req.query; |
| 4 | // ✅ value sent as a bound parameter; driver separates code from data |
| 5 | const { rows } = await pool.query( |
| 6 | 'SELECT id, name FROM users WHERE email = $1', |
| 7 | [email], |
| 8 | ); |
| 9 | res.json(rows); |
| 10 | }); |
| 11 |
|
| 12 | // mysql2 equivalent: |
| 13 | // connection.execute('SELECT id FROM users WHERE email = ?', [email]); |
Explanation (EN)
The query text is a fixed string with a placeholder ($1 / ?), and the value travels in a separate parameter array. The database treats it strictly as data, so injected SQL syntax has no effect regardless of input.
Objašnjenje (HR)
Tekst upita je fiksni string s rezerviranim mjestom ($1 / ?), a vrijednost putuje u zasebnom polju parametara. Baza je tretira isključivo kao podatak, pa ubačena SQL sintaksa nema učinka bez obzira na ulaz.
Notes (EN)
Applies to every driver and query builder: pg, mysql2, better-sqlite3, and ORM raw/escape-hatch methods. The goal is binding values, not escaping them yourself.
Bilješke (HR)
Vrijedi za svaki driver i query builder: pg, mysql2, better-sqlite3 te raw/escape-hatch metode ORM-ova. Cilj je vezivanje vrijednosti, a ne ručno escapanje.
Exceptions / Tradeoffs (EN)
Placeholders cannot bind identifiers (table/column names) or SQL keywords like ASC/DESC. When those must be dynamic, validate against a fixed allow-list of known-good values and map to literals — never pass them through from input. See the separate identifier allow-list rule.
Iznimke / Tradeoffi (HR)
Rezervirana mjesta ne mogu vezati identifikatore (imena tablica/stupaca) ni ključne riječi poput ASC/DESC. Kad oni moraju biti dinamični, validiraj ih prema fiksnoj allow-listi poznatih vrijednosti i preslikaj na literale — nikad ih ne prosljeđuj direktno iz ulaza. Vidi zasebno pravilo o allow-listi identifikatora.