sql-injection-prevention

v2026.09.25

Detect and fix SQL injection vulnerabilities in any framework. Covers Laravel (DB::raw, whereRaw), Node.js (template literals in queries), Python (f-strings in SQL), and Cloudflare D1. Enforces parameterized bindings everywhere. Use when writing database queries, reviewing code for injection, or fixing SQL injection findings.

GitHub
Install command
npx skhub add afu-it/sql-injection-prevention
Markdown
SKILL.md

SQL Injection Prevention

Detect and fix SQL injection vulnerabilities across any framework. This is Finding #1 from Malaysian PDPA court cases -- the most common and most dangerous vulnerability.

The Rule

NEVER concatenate user input into SQL strings. ALWAYS use parameterized bindings.

Detection Patterns

Node.js / TypeScript

BAD (vulnerable):

// Template literal injection
const result = await db.query(`SELECT * FROM users WHERE id = '${userId}'`);

// String concatenation
const result = await db.query("SELECT * FROM users WHERE name = '" + name + "'");

// Tagged template without proper escaping
const result = await db.raw(`SELECT * FROM orders WHERE status = ${status}`);

GOOD (safe):

// Parameterized binding with ?
const result = await db.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first();

// Named parameters
const result = await db.query('SELECT * FROM users WHERE id = $1', [userId]);

// ORM with built-in escaping
const result = await prisma.user.findUnique({ where: { id: userId } });

Cloudflare D1

BAD:

await db.exec(`INSERT INTO logs (msg) VALUES ('${userInput}')`);

GOOD:

await db.prepare('INSERT INTO logs (msg) VALUES (?)').bind(userInput).run();
await db.batch([
  db.prepare('UPDATE credits SET amount = amount - 1 WHERE user_id = ?').bind(userId),
  db.prepare('INSERT INTO transactions (user_id, amount) VALUES (?, ?)').bind(userId, -1),
]);

Laravel / PHP

BAD:

DB::raw("SELECT * FROM users WHERE email = '$email'");
DB::select("SELECT * FROM users WHERE id = " . $request->id);
$query->whereRaw("status = '$status'");
$query->orderByRaw($request->sort_column . ' ' . $request->sort_direction);

GOOD:

DB::select('SELECT * FROM users WHERE email = ?', [$email]);
$query->whereRaw('status = ?', [$status]);
$query->orderByRaw('?? ??', [$sortColumn, $sortDirection]);
User::where('email', $email)->first();

Python

BAD:

cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
cursor.execute("SELECT * FROM users WHERE name = '" + name + "'")
cursor.execute("SELECT * FROM users WHERE id = %s" % user_id)

GOOD:

cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
cursor.execute("SELECT * FROM users WHERE id = :id", {"id": user_id})
User.objects.filter(id=user_id).first()  # Django ORM

Search Patterns

When auditing a codebase, search for these regex patterns:

# Node.js/TypeScript
`.*\$\{.*\}.*` near SELECT|INSERT|UPDATE|DELETE
".*" \+ .* near query|execute|raw
\.raw\(.*\$\{
\.exec\(`

# Laravel
DB::raw\(.*\$
whereRaw\(.*\$
orderByRaw\(.*\$
->select\(DB::raw\(.*\$

# Python
execute\(f"
execute\(".*" \+
execute\(".*%s" %

Fix Process

  1. Find all database query locations -- search for query/prepare/execute/raw calls
  2. Check each one -- does it use ? placeholders or string interpolation?
  3. Replace interpolation with bindings -- swap ${var} with ? and add .bind(var)
  4. Verify with tests -- run existing tests to ensure queries still work
  5. Add integration tests -- test that malicious input is safely handled

PDPA 2024 Consequence

Seksyen 5(1) dan 5(2) PDPA 2010 (Pindaan 2024):

  • Denda sehingga RM1,000,000
  • Penjara sehingga 3 tahun
  • Atau kedua-duanya sekali

SQL injection is the #1 finding in forensic digital analysis of negligent systems.

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.25

Published

Sep 25, 2026

Category

Uncategorized

License

Not specified

Source path

skills/sql-injection-prevention

Default branch

main

Latest commit

6aeeb8f

Tree SHA

14cb46d