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
bash
npm install --save-dev eslint-plugin-pg in 60 seconds. 15 rules.** PostgreSQL security. Connection management. Query optimization.
---
π¦ [npm: eslint-plugin-pg](https://www.npmjs.com/package/eslint-plugin-pg)
π [Rule docs: no-unsafe-query](https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-pg/docs/rules/no-unsafe-query.md)
**[β Star on GitHub](https://github.com/ofri-peretz/eslint)**
---
**The Interlace ESLint Ecosystem**
Interlace is a high-fidelity suite of static code analyzers designed to automate security, performance, and reliability for the modern Node.js stack. With over 330 rules across 18 specialized plugins, it provides 100% coverage for OWASP Top 10, LLM Security, and Database Hardening.
[Explore the full Documentation](https://eslint.interlace.tools)
---
Β© 2026 Ofri Peretz. All rights reserved.
---
**Build Securely.**
I'm Ofri Peretz, a Security Engineering Leader and the architect of the Interlace Ecosystem. I build static analysis standards that automate security and performance for Node.js fleets at scale.
[ofriperetz.dev](https://ofriperetz.dev) | [LinkedIn](https://linkedin.com/in/ofri-peretz) | [GitHub](https://github.com/ofri-peretz)
Top comments (0)