npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kdinisv/sql-scanner

v0.2.4

Published

Smart SQL injection scanner with crawler and optional Playwright capture.

Readme

@kdinisv/sql-scanner

Лёгкий SDK для поиска SQL-инъекций в Node.js. Умеет точечно сканировать URL и выполнять «умное» сканирование с краулингом. Работает в ESM и CommonJS, типы включены. Поддерживает отчёты JSON/Markdown/CSV/JUnit и приоритизирует payload’ы (в т.ч. time/union PoC) по отпечатку СУБД.

— Node.js >= 18.17 — Типы: TypeScript – Опционально: Playwright для JS-страниц (захват сетевых запросов SPA)

Установка

npm i @kdinisv/sql-scanner
# (опционально) для SPA-страниц
npm i -D playwright

Быстрый старт (ESM)

import { SqlScanner } from "@kdinisv/sql-scanner";

const scanner = new SqlScanner();

const result = await scanner.scan({
  target: "https://example.com/search?q=test",
  method: "GET",
  enable: { query: true, error: true, boolean: true, time: false },
});

console.log(result.vulnerable, result.details);

Быстрый старт (CommonJS)

const { SqlScanner } = require("@kdinisv/sql-scanner");

(async () => {
  const scanner = new SqlScanner();
  const result = await scanner.scan({ target: "https://example.com?id=1" });
  console.log(result);
})();

«Умное» сканирование (краулер)

const smart = await scanner.smartScan({
  baseUrl: "https://example.com",
  maxDepth: 2,
  maxPages: 50,
  usePlaywright: true,
});
console.log(smart.crawledPages, smart.candidates.length, smart.sqli.length);

Краткое API

  • new SqlScanner(options?)

    • requestTimeoutMs?: number (по умолчанию 10000)
    • timeThresholdMs?: number для time-based (по умолчанию 2500)
    • headers?: Record<string,string>, cookies?: Record<string,string>
  • scan(input)

    • target: string, method?: "GET"|"POST"
    • jsonBody?: Record<string,unknown>
    • enable?: { query|path|form|json|header|cookie|error|boolean|time|union?: boolean }
    • payloads?: { error?: string[]; boolean?: { true:string; false:string; label?:string }[]; time?: { p:string; label?:string }[] }
    • onProgress?: (p: ScanProgress) => void
    • Возвращает: { vulnerable: boolean; details: Detail[] }
  • smartScan(options)

    • baseUrl: string, maxDepth?: number, maxPages?: number
    • sameOriginOnly?: boolean, usePlaywright?: boolean
    • crawlConcurrency?: number — параллельность HTML-краулинга (по умолчанию 4)
    • playwrightConcurrency?: number — число одновременных страниц Playwright (по умолчанию 2)
    • playwrightWaitMs?: number — ожидание XHR/fetch после загрузки, мс (по умолчанию 1000)
    • scanParallel?: number — параллельное сканирование кандидатов (по умолчанию 2)
    • techniques?: { error?: boolean; boolean?: boolean; time?: boolean }
    • onProgress?: (p: SmartScanProgress) => void
    • Возвращает: { crawledPages: number; candidates: DiscoveredTarget[]; sqli: ResultShape[] }

Возвращаемые данные

scan(input) → ResultShape

  • vulnerable: boolean — есть ли подтвержденные уязвимости среди проверок
  • details: Detail[] — записи по каждому проверенному «поинту/технике»
    • point: { kind: "query"|"path"|"form"|"json"|"header"|"cookie"; name: string; meta?: object }
    • technique: "error" | "boolean_truefalse" | "time"
    • payload: string — инъекция, использованная в проверке
    • vulnerable: boolean — результат конкретной проверки
    • responseMeta?: { status: number; elapsedMs?: number; len?: number; location?: string }
    • evidence?: string — краткая улика (фрагмент ошибки/метрика)
    • confirmations?: string[] — ярлыки подтверждений (например, "error_signature", а при error-based может добавляться отпечаток СУБД: "mysql"|"postgres"|"mssql"|"oracle"|"sqlite")
    • reproduce?: { curl: string[]; note?: string } — готовые примеры для воспроизведения (curl)
    • remediation?: string[] — краткие рекомендации по исправлению

Пример:

{
  "vulnerable": true,
  "details": [
    {
      "point": { "kind": "query", "name": "q" },
      "technique": "error",
      "payload": "' OR '1'='1",
      "vulnerable": true,
      "responseMeta": { "status": 200, "len": 12345, "elapsedMs": 120 },
      "evidence": "You have an error in your SQL syntax",
      "confirmations": ["error_signature"],
      "reproduce": {
        "curl": ["curl \"https://example.com/search?q=' OR '1'='1\""]
      },
      "remediation": [
        "Используйте параметризованные запросы/Prepared Statements",
        "Не конкатенируйте пользовательский ввод в SQL"
      ]
    }
  ]
}

Совет: фильтруйте details.filter(d => d.vulnerable) для списка подтвержденных находок.

smartScan(options) → SmartScanResult

  • crawledPages: number — сколько страниц обработано краулером
  • candidates: DiscoveredTarget[] — найденные цели для сканирования
    • { kind: "url-with-query"; url: string }
    • { kind: "form"; action: string; method: "GET"|"POST"; enctype?: string; fields: { name: string; value: string }[] }
    • { kind: "json-endpoint"; url: string; method: "GET"|"POST"|"PUT"|"PATCH"|"DELETE"; body?: any; headers?: Record<string,string> }
  • sqli: ResultShape[] — результаты сканирования для каждой из целей

Пример:

{
  "crawledPages": 12,
  "candidates": [
    { "kind": "url-with-query", "url": "https://site/search?q=" },
    {
      "kind": "form",
      "action": "https://site/login",
      "method": "POST",
      "fields": [{ "name": "email", "value": "" }]
    }
  ],
  "sqli": [
    {
      "vulnerable": false,
      "details": [
        {
          "point": { "kind": "query", "name": "q" },
          "technique": "boolean_truefalse",
          "payload": "...",
          "vulnerable": false
        }
      ]
    }
  ]
}

Тонкая настройка

  • Кастомные payload’ы для scan
await scanner.scan({
  target: "http://127.0.0.1:3000/rest/products/search?q=",
  enable: { query: true, error: true, boolean: true, time: false },
  payloads: {
    error: ["'", "' OR 1=1--", "' UNION SELECT 1--"],
    boolean: [{ true: "' OR 1=1--", false: "' OR 1=2--", label: "or_comment" }],
  },
});

– Предварительная авторизация (форма/JSON)

await scanner.scan({
  target: "https://site.local/products?id=1",
  method: "GET",
  auth: {
    url: "https://site.local/api/login",
    method: "POST",
    type: "form-urlencoded", // или "json"
    usernameField: "username",
    passwordField: "password",
    username: "admin",
    password: "secret",
    additionalFields: { remember: "1" },
    verifyUrl: "https://site.local/",
    success: { notContainsText: "Sign in" },
  },
  enable: { query: true, error: true, boolean: true },
});

Аналогично в smartScan:

await scanner.smartScan({
  baseUrl: "https://site.local",
  auth: {
    url: "https://site.local/api/login",
    method: "POST",
    type: "json",
    usernameField: "email",
    passwordField: "password",
    username: "[email protected]",
    password: "secret",
    verifyUrl: "https://site.local/",
    success: { notContainsText: "Sign in" },
  },
});
  • Управление техниками в smartScan
await scanner.smartScan({
  baseUrl: "https://example.com",
  maxDepth: 2,
  maxPages: 50,
  techniques: { error: true, boolean: true, time: false },
});

Прогресс и ETA

  • Прогресс для точечного сканирования
await scanner.scan({
  target: "https://example.com/search?q=1",
  enable: { query: true, error: true, boolean: true, time: false },
  onProgress: (p) => {
    if (p.phase === "discover") {
      console.log(`points=${p.points}`);
    } else if (p.phase === "scan") {
      console.log(
        `checks ${p.processedChecks}/${p.plannedChecks}, eta=${p.etaMs}ms`
      );
    }
  },
});
  • Прогресс для умного сканирования (краулинг + скан)
await scanner.smartScan({
  baseUrl: "https://example.com",
  onProgress: (p) => {
    if (p.phase === "crawl") {
      console.log(`crawled ${p.crawledPages}/${p.maxPages}`);
    } else if (p.phase === "scan") {
      console.log(
        `scanned ${p.scanProcessed}/${p.scanTotal}, eta=${p.etaMs}ms`
      );
    }
  },
});

CLI (опционально)

Запуск без установки:

npx --package @kdinisv/sql-scanner sql-scan https://example.com

Глобально:

npm i -g @kdinisv/sql-scanner
sql-scan https://example.com

# отключить захват JS/SPA (без Playwright)
sql-scan https://example.com --no-js

# сохранить отчёт (Markdown/JSON/CSV/JUnit)
sql-scan https://example.com --report md --out report.md
sql-scan https://example.com --report json --out report.json
sql-scan https://example.com --report csv --out report.csv
sql-scan https://example.com --report junit --out report.xml

CLI показывает индикатор прогресса и оценку ETA в процессе.

.env-конфиг

CLI поддерживает конфигурацию через .env-файл (dotenv). Создайте .env в корне проекта (см. .env.example). Основные переменные:

  • SQL_SCANNER_REQUEST_TIMEOUT_MS, SQL_SCANNER_TIME_THRESHOLD_MS
  • SQL_SCANNER_PARALLEL, SQL_SCANNER_MAX_REQUESTS
  • SQL_SCANNER_USE_JS, SQL_SCANNER_MAX_DEPTH, SQL_SCANNER_MAX_PAGES, SQL_SCANNER_SAME_ORIGIN_ONLY
  • SQL_SCANNER_PLAYWRIGHT_HEADLESS (true|false). В CLI есть флаг --headed (делает headless=false)
  • SQL_SCANNER_TECHNIQUES=error,boolean,time
  • SQL_SCANNER_HEADERS / SQL_SCANNER_HEADERS_JSON (JSON строка)
  • SQL_SCANNER_COOKIES / SQL_SCANNER_COOKIES_JSON (JSON строка)
  • SQL_SCANNER_AUTH_JSON (JSON строка с параметрами авторизации)

Переменные окружения прокси HTTP_PROXY/HTTPS_PROXY/NO_PROXY также учитываются.

Тестовые эмуляторы СУБД (для разработки)

В репозитории есть лёгкие локальные HTTP-эмуляторы баз данных, которые имитируют поведение MySQL/PostgreSQL/MSSQL/Oracle/SQLite для техник:

  • error-based — возвращают характерные сигнатуры ошибок СУБД;
  • boolean-based — различающиеся ответы для «true/false» инъекций;
  • time-based — искусственная задержка при SLEEP/pg_sleep/WAITFOR/DBMS_LOCK.SLEEP;
  • union-based PoC — различия в ответах для ORDER BY ok/bad и UNION SELECT.

Они используются в автотестах и не требуют внешних контейнеров/стендов:

  • Код эмуляторов: tests/servers/dbEmulators.ts
  • Тест: tests/db-emulator.test.ts

Запуск тестов:

npm test -s

Сеть и прокси

  • Поддерживаются переменные окружения прокси:
    • HTTP_PROXY / HTTPS_PROXY — адрес прокси, например: http://127.0.0.1:3128 или с авторизацией http://user:[email protected]:8080.
    • NO_PROXY — список исключений через запятую (если задан системно, будет учтён на стороне среды).
  • Для транзиентных ошибок на GET (502/503/504, сетевые таймауты) вшиты короткие ретраи с экспоненциальным backoff.

— Приоритет пейлоадов: если error-based детект дал отпечаток СУБД (MySQL/Postgres/MSSQL/Oracle/SQLite), time-based подбор сначала пробует соответствующие пейлоады (например, pg_sleep/WAITFOR/DBMS_LOCK.SLEEP), что ускоряет и повышает точность.

— Union-based PoC: реализованы безопасные пробы ORDER BY (сравнение ответов на валидный/заведомо «лишний» индекс) и базовые UNION SELECT пейлоады. При наличии отпечатка СУБД выбираются наиболее подходящие варианты.

Дополнительные примеры

1) Сканирование JSON API (POST)

const result = await scanner.scan({
  target: "https://api.site.local/search",
  method: "POST",
  jsonBody: { q: "test", page: 1 },
  enable: { json: true, error: true, boolean: true, time: false },
});

2) Сканирование формы

// target указывает на страницу с формой; сканер сам получит HTML и извлечёт поля
const res = await scanner.scan({
  target: "https://site.local/login",
  enable: { form: true, error: true, boolean: true, time: false, query: false },
});

3) Заголовки и куки (и инъекции в них)

const res = await scanner.scan({
  target: "https://site.local/profile?id=1",
  headers: { "X-Trace": "abc" },
  cookies: { session: "token" },
  enable: { header: true, cookie: true, error: true, boolean: true },
});

4) Time-based проверки

const res = await scanner.scan({
  target: "https://site.local/search?q=1",
  enable: { query: true, time: true },
  // Поднимите порог, если бэкенд медленный
  timeThresholdMs: 3000,
});

— Встроено статистическое подтверждение (p-value) для time-based: несколько парных замеров baseline/injected, расчёт p (односторонний тест). В evidence попадают p, z, средние времена. Для шумных систем можно повысить timeThresholdMs.

5) smartScan с/без JS

// Без JS (быстрее, только HTML)
await scanner.smartScan({
  baseUrl: "https://site.local",
  usePlaywright: false,
  crawlConcurrency: 6, // быстрее HTML-краулинг
});

// С JS (если установлен Playwright): захватывает запросы SPA
await scanner.smartScan({
  baseUrl: "https://site.local",
  usePlaywright: true,
  playwrightMaxPages: 4, // 0 или отрицательное — без ограничения
  playwrightConcurrency: 3, // несколько страниц параллельно
  playwrightWaitMs: 1200, // чуть дольше ждём XHR
  scanParallel: 3, // параллельно сканируем кандидатов
});

6) Постобработка результатов

const onlyVuln = result.details.filter((d) => d.vulnerable);
const byTechnique = onlyVuln.reduce(
  (acc, d) => {
    acc[d.technique] = (acc[d.technique] || 0) + 1;
    return acc;
  },
  /** @type {Record<string, number>} */ {}
);

— Готовые репорты: JSON/Markdown/CSV/JUnit

import {
  toJsonReport,
  toMarkdownReport,
  toCsvReport,
  toJUnitReport,
} from "@kdinisv/sql-scanner";

const json = toJsonReport(result);
const md = toMarkdownReport(result);
const csv = toCsvReport(result);
const junitXml = toJUnitReport(result);
// Сохраните в файл или отправьте в CI-артефакты

В отчётах теперь присутствуют примеры воспроизведения и советы по исправлению:

  • Markdown: внутри каждого найденного пункта добавляется раздел reproduce с curl ..., а также remediation — список рекомендаций.
  • CSV: дополнительные колонки reproduce_curl и remediation.
  • JUnit: в <failure> попадает тело с блоками curl: и fix:.

7) Управление нагрузкой

const scanner = new SqlScanner({ parallel: 4, maxRequests: 500 });
const res = await scanner.scan({ target: "https://site.local/?q=1" });

// Для smartScan скорость можно настраивать:
await scanner.smartScan({
  baseUrl: "https://site.local",
  crawlConcurrency: 4,
  playwrightConcurrency: 2,
  scanParallel: 2,
});

8) UNION/ORDER BY (PoC)

// Экспериментальная техника: сначала ORDER BY (ok vs bad), затем UNION
const res = await scanner.scan({
  target: "http://127.0.0.1:3000/search?q=1",
  enable: {
    query: true,
    union: true,
    error: false,
    boolean: false,
    time: false,
  },
});

const unionFindings = res.details.filter((d) => d.technique === "union");
// evidence включает orderby-sim=... и/или sim(base,union)=...

Примечание: это PoC с консервативными, безопасными пейлоадами. В бою комбинируйте с error/boolean/time для повышения уверенности.

Важно

Используйте сканер только на ресурсах, для которых у вас есть разрешение.