I've reviewed hundreds of Node.js + PostgreSQL codebases. The same vulnerability appears in 80% of them.
The Pattern That Looks Safe
// β This looks fine, right?
async function getUser(userId) {
const query = `SELECT * FROM users WHERE id = '${userId}'`;
const result = await pool.query(query);
return result.rows[0];
}
It's clean. It's readable. It's also a critical security vulnerability.
The Attack
// Attacker input:
const userId = "'; DROP TABLE users; --";
// Generated query:
// SELECT * FROM users WHERE id = ''; DROP TABLE users; --'
Your users table is gone. Your data is gone. Your job might be gone.
Why Developers Keep Making This Mistake
| Reason | Reality |
|---|---|
| "I validate the input" | Validation can be bypassed |
| "It's an internal API" | Internal APIs get exposed |
| "Template literals are safe" | They're just string concatenation |
| "ORM handles this" | Not if you use raw queries |
The Correct Pattern
// β
Parameterized query - the ONLY safe pattern
async function getUser(userId) {
const query = 'SELECT * FROM users WHERE id = $1';
const result = await pool.query(query, [userId]);
return result.rows[0];
}
The $1 placeholder tells PostgreSQL to treat the value as data, not code. No amount of SQL injection can escape this.
Let ESLint Enforce This
npm install --save-dev eslint-plugin-pg
// eslint.config.js
import pg from 'eslint-plugin-pg';
export default [pg.configs.recommended];
Now run your linter:
src/users.ts
4:17 error π CWE-89 OWASP:A03 CVSS:9.8 | Unsafe query detected
Fix: Use parameterized query: client.query('SELECT * FROM users WHERE id = $1', [userId])
More Examples
β Dynamic table names
const table = userInput;
pool.query(`SELECT * FROM ${table}`); // SQL injection
β Allowlist tables
const ALLOWED_TABLES = ['users', 'orders', 'products'];
if (!ALLOWED_TABLES.includes(table)) throw new Error('Invalid table');
pool.query(`SELECT * FROM ${table}`); // Now safe
β Building WHERE clauses
let query = 'SELECT * FROM users WHERE 1=1';
if (name) query += ` AND name = '${name}'`; // Injection!
β Build params array
const params = [];
let query = 'SELECT * FROM users WHERE 1=1';
if (name) {
params.push(name);
query += ` AND name = $${params.length}`;
}
await pool.query(query, params);
Quick Install
npm install --save-dev eslint-plugin-pg
import pg from 'eslint-plugin-pg';
export default [pg.configs.recommended];
13 rules. PostgreSQL security. Connection management. Query optimization.
π¦ npm: eslint-plugin-pg
π Rule docs: no-unsafe-query
π Follow me for more security articles & updates:
GitHub | LinkedIn
Top comments (0)