detecting-sql-injection-patterns

v2026.09.24

Scan a source tree for SQL-injection vulnerable patterns: string concatenation into queries, f-string interpolation in SQL, string-format substitution into raw queries, deprecated cursor methods (cursor.execute with % formatting), Knex / Sequelize raw() with template interpolation, sequelize.query with replacements. Use when: pre-commit code review, post-feature SQL-touching release, inheriting a legacy codebase that predates ORMs, or post-bug-report investigation. Threshold: any source line where SQL keywords (SELECT / INSERT / UPDATE / DELETE / FROM / WHERE) appear in a string that's being built via concatenation, f-string, %-format, or .format() with variable input. Trigger with: "scan for sqli", "sql injection patterns", "check raw queries", "audit cursor.execute".

GitHub
Install command
npx skhub add jeremylongshore/detecting-sql-injection-patterns
Markdown
SKILL.md

Detecting SQL Injection Patterns

Overview

SQL injection (CWE-89, OWASP A03:2021) remains one of the highest- impact and most-easily-introduced vulnerability classes. The fix is near-universal: use parameterized queries. The cause when introduced: an engineer concatenates user input into a SQL string because the ORM's parameterization mechanism wasn't obvious, or because they "just need to add a quick condition."

The scanner reads source files and grades each apparent SQL-string construction against the threshold table.

When the skill produces findings

FindingSeverityThresholdAffected control
f-string with SQL keywords + user inputCRITICALf"SELECT * FROM users WHERE id = {user_id}"CWE-89
String concat into SQL keyword stringCRITICAL"SELECT ... " + var + " ..."CWE-89
%-format SQL stringHIGH"SELECT * FROM %s" % table_nameCWE-89
.format() into SQL stringHIGH"SELECT {} FROM users".format(col)CWE-89
cursor.execute(f"...")CRITICALf-string passed directly to cursor.executeCWE-89
sequelize.query with template literalHIGHsequelize.query(\SELECT * FROM ${table}`)`CWE-89
Knex / sequelize raw() with interpolationHIGHknex.raw('SELECT * FROM ' + table)CWE-89
Django .extra() with raw SQLMEDIUMModel.objects.extra(where=['col = ' + val])CWE-89
cursor.executemany with string-built queryCRITICALSame risk as executeCWE-89
JDBC Statement.execute with concatHIGHJava pattern: not PreparedStatementCWE-89
Rails where() with string interpolationHIGHUser.where("name = '#{name}'")CWE-89
Go db.Query with fmt.SprintfHIGHdb.Query(fmt.Sprintf("...", arg))CWE-89

Prerequisites

  • Python 3.9+
  • Target source tree on local filesystem

Instructions

Step 1 — Run the scanner

python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py /path/to/repo

Options:

Usage: scan_sqli.py PATH [OPTIONS]

Options:
  --output FILE      Write findings to FILE
  --format FMT       json | jsonl | markdown (default: markdown)
  --min-severity SEV (default: info)
  --include-tests    Include test directories (default: excluded)
  --languages LIST   Comma-separated: python,javascript,typescript,java,
                     ruby,go,php,csharp (default: all)

Step 2 — Interpret findings

CRITICAL = direct user-input → query string construction. Fix the specific query AND audit nearby code for the same pattern.

HIGH = pattern suggests interpolation but might be a fixed identifier (table/column name). Verify by reading the code.

MEDIUM = framework-specific pattern that's safe ONLY with strict input validation (Django .extra(), Rails string where()).

Step 3 — Remediation

For each finding, the fix is the same shape per language: use the language/library's parameterized-query API. See references/PLAYBOOK.md for per-language snippets.

Step 4 — Cross-skill chaining

Consider running scanning-for-hardcoded-secrets (#10) on the same target — same audit, different class of finding.

Examples

Example 1 — Pre-merge code review

python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py \
    --min-severity high $(git diff --name-only main...HEAD | tr '\n' ' ')

Scans only files changed in the current branch — fast feedback for PR review.

Example 2 — Legacy codebase audit

python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py \
    /path/to/legacy-app --format markdown > sqli-audit.md

Expect dozens to hundreds of findings on a pre-ORM Java/PHP codebase. Prioritize by reachability: the queries reached from public endpoints first.

Output

JSON / JSONL / Markdown. Exit codes: 0 clean, 1 high/critical, 2 error.

Error Handling

  • False positives on fixed-identifier interpolation (e.g., f"SELECT * FROM {tablename}" where tablename is hardcoded) → verify manually. The scanner can't reason about variable provenance without a full AST + control-flow pass.
  • String-built dynamic-table queries are sometimes legitimate (multi-tenant routing). Flag and review; the fix is usually allow-list validation + identifier quoting.

Resources

  • references/THEORY.md — Per-language interpolation patterns, ORM-specific safe vs unsafe APIs, why prepared statements work
  • references/PLAYBOOK.md — Per-language parameterization snippets (Python sqlite3 + psycopg + SQLAlchemy, Node mysql2 + pg + knex
    • sequelize, Ruby ActiveRecord, Go database/sql, Java JDBC PreparedStatement, PHP PDO)
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/.curated/detecting-sql-injection-patterns

Default branch

main

Latest commit

e5a6c3b

Tree SHA

c2dc8e8