Зміст
Коротко
SQL-ін'єкції в Node.js не зникли: TypeScript і рев'ю коду часто пропускають три звичні шаблони в node-postgres. На Dev.to показали, як правило eslint-plugin-pg ловить їх статично — просто в редакторі, без важкого статичного аналізу безпеки.
Що сталося
Сценарій знайомий: лінтер чистий, рев'юер схвалив, реліз пішов у прод. Через три місяці пентестер знаходить рядок на кшталт pool.query("SELECT * FROM orders WHERE user_id = " + req.query.userId). Параметризовані запити всі знають, OWASP давно позначає ін'єкції як критичний ризик — але вразливості все одно проскакують, бо кожен шаблон виглядає безобидно в одному рядку.
Автор виділяє три форми. Пряма конкатенація в першому аргументі .query(). Шаблонні рядки з ${userId} — синтаксис чистіший, ризик той самий. Присвоєння в змінну: const sql = "…" + category і окремо client.query(sql) — на виклику нічого не кидається в очі, а отруєння рядка сталося двома рядками вище.
Правило pg/no-unsafe-query заточене під pg, а не під «будь-який рядок біля SELECT». Воно спрацьовує лише на .query(), розуміє контракт $1 + масив значень і позначає змінну «забрудненою» в момент присвоєння. Обгортка sanitize() не рятує — правило не може довести, що вона еквівалентна параметризації.
Чому це важливо
Універсальні лінтери або шумлять на безобидну збірку рядків, або пропускають шаблонні літерали. ESLint у межах однієї функції не відстежує «забруднення» через увесь проєкт, зате працює на кожному збереженні й у хуку перед комітом — там, де розробник реально править SQL.
Команди на Prisma, Knex або Drizzle теж не застраховані: $queryRaw, knex.raw і аналоги повертають ті самі три шаблони. Для сервісів на чистому pg — внутрішні API, пайплайни, мікросервіси — поверхня атаки саме тут.
На практиці
- Підключіть
eslint-plugin-pgі ввімкнітьpg/no-unsafe-queryяк помилку збірки. - Скрізь, де введення користувача потрапляє в SQL, використовуйте
$1,$2і другий аргумент-масив — не конкатенацію й не${}без плейсхолдерів$N. - Не «рефакторте» параметризований запит у «читабельний» рядок — типовий шлях до регресії.
- Для імен схем/таблиць —
pg-formatабо окремий шар, а не+ SCHEMA_NAME(відомий хибнопозитивний випадок правила). - Semgrep/CodeQL залиште для міжпроцедурного аналізу; ESLint — для миттєвого зворотного зв'язку в редакторі.
Підсумок
SQL-ін'єкції в Node.js — не проблема «невігласних джунів», а проблема шаблонів, які обманюють рев'ю. Спеціалізоване ESLint-правило для pg закриває три реальні форми; матеріал на Dev.to варто прочитати, якщо у вашому коді досі живе query("…" + id).