Security

SQL Injection Auditor

Trace every place user input reaches a database query in a diff, and confirm parameterization -- string-built SQL is the finding, not a maybe.

When to use this

  • Any diff that adds or changes a database query, especially with dynamic filter/sort/search parameters.
  • Auditing an older codebase for raw string-concatenated SQL before a security push.
  • Not needed for diffs with no database access, or ones using an ORM/query builder exclusively with no raw-SQL escape hatch used.

The skill file

Copy this verbatim. It's written in the SKILL.md format (YAML frontmatter + markdown instructions) that Claude Code, and increasingly other agent tools, read directly.

SKILL.md
---
name: sql-injection-auditor
description: Trace every path from user-controlled input to a SQL query in a diff, and confirm each is parameterized/prepared rather than string-concatenated. Use for diffs that add or modify database queries, especially with dynamic filtering, sorting, or search.
---

# SQL Injection Auditor

## What to trace

For every SQL query in the diff (raw SQL, or a query builder's raw/unsafe
escape hatch -- ORMs' normal parameterized methods are already safe and
don't need this), trace backward: where does each value in the query
come from?

- If every value is a compile-time constant or comes from a parameterized
  placeholder (`?`, `$1`, named params bound separately from the query
  string), it's safe.
- If any value is built into the query string via concatenation,
  f-string/template interpolation, or `.format()` -- and that value
  originates from user input (request params, headers, uploaded file
  content, even indirectly through a database value that itself came from
  user input) -- that's a finding, full stop, regardless of how unlikely
  exploitation "feels."

## Special cases that are easy to miss

- Dynamic column/table names and `ORDER BY` clauses (can't be parameterized
  the normal way -- must be validated against an allowlist of known
  column/table names, never interpolated directly).
- Search/filter query builders that assemble a `WHERE` clause from a list
  of user-supplied conditions.
- Stored procedure calls with string-built arguments.

## Output

For every query: safe (parameterized, cite the line) or a finding (exact
concatenation point, the user-input origin, and the fix -- parameterize,
or allowlist-validate for identifiers that can't be parameterized).

Installing it elsewhere

The frontmatter/body split above is Claude Code's convention. Here's how to carry the same instructions into other tools:

Claude Code
.claude/skills/sql-injection-auditor/SKILL.md

Save the file below verbatim (frontmatter included) at that path, project-local or in ~/.claude/skills/ for a user-level skill. Claude Code loads the name/description pair to decide when to pull it in, or you invoke it directly as /sql-injection-auditor.

Cursor
.cursor/rules/sql-injection-auditor.mdc

Convert the YAML frontmatter to Cursor's rule format (description, globs, alwaysApply: false) and keep the markdown body as the rule content. Cursor surfaces it by description match, same idea as Claude Code's auto-load.

Codex CLI / Copilot
AGENTS.md

Codex CLI (and increasingly other agentic CLIs) read AGENTS.md at the repo root as always-on instructions. Paste the markdown body under a heading like ## {title}; for GitHub Copilot's coding agent, the equivalent file is .github/copilot-instructions.md.

Windsurf
.windsurfrules

Append the markdown body to .windsurfrules at the repo root. Windsurf treats the whole file as always-on context, so keep only the instructions you want applied on every request.

Where this goes wrong
  • Flagging an ORM's normal parameterized query method as a false positive because it "looks like" string building at a glance.
  • Missing dynamic ORDER BY / column-name injection because it doesn't fit the classic WHERE-clause pattern.
  • Assuming a value is safe because it "comes from the database" without checking whether that database value originated from user input upstream.