PHP SQL Fixer
Detect SQL injection risks and generate parameterized query fix patches for PHP/Yaf projects.
Overview
This skill does two things:
- Scan — find all SQL injection candidates in a PHP project (string concatenation, superglobal interpolation, unsafe sprintf)
- Fix — for each finding, generate the parameterized equivalent and explain the change
Always prefer minimal, targeted fixes. Do not refactor surrounding code. Do not change DB abstraction patterns that already exist in the project.
Workflow
Step 1 — Run the scanner
bash "$SKILL_DIR/scripts/scan_sql.sh" <project-root> [output-file]
Read the output carefully. The scanner flags candidates, not confirmed vulnerabilities. Some hits may be false positives (e.g. SQL built from constants, not user input).
Step 2 — Triage findings
For each flagged file:
- Open the file and read the full context around the hit (at least ±10 lines)
- Confirm whether user-controlled input (
$_GET,$_POST,$_REQUEST, function params from controllers) reaches the SQL string - Mark each finding as: confirmed / suspected / false positive
Step 3 — Generate fix suggestions
php "$SKILL_DIR/scripts/suggest_fix.php" <file-path>
The script outputs annotated before/after for each risky SQL statement in the file.
Step 4 — Apply fixes
Apply fixes manually or with targeted Edit tool calls. Rules:
- Use parameterized queries matching the project's existing DB pattern (PDO, custom model, etc.)
- Do not change method signatures or surrounding business logic
- Add a
// FIXED: sql injectioncomment on the line where the fix was applied - Run
php -l <file>after every edit to verify syntax
Step 5 — Verify
# syntax check
docker compose -f /mnt/d/Users/Public/php20250819/docker-php7.3/docker-compose.yml \
exec fpm-server php -l /var/www/html/2026www/<project>/<file>
# re-scan to confirm no remaining hits
bash "$SKILL_DIR/scripts/scan_sql.sh" <project-root>
Fix Patterns
See references/fix-patterns.md for the complete catalog. Quick reference:
Pattern 1 — String concatenation
// BEFORE (unsafe)
$sql = "SELECT * FROM users WHERE id = " . $id;
$res = $db->query($sql);
// AFTER (PDO)
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$res = $stmt->fetchAll();
Pattern 2 — Variable interpolation
// BEFORE (unsafe)
$sql = "SELECT * FROM orders WHERE status = '$status' AND uid = $uid";
// AFTER (PDO named placeholders)
$stmt = $db->prepare("SELECT * FROM orders WHERE status = :status AND uid = :uid");
$stmt->execute([':status' => $status, ':uid' => $uid]);
Pattern 3 — sprintf injection
// BEFORE (unsafe)
$sql = sprintf("SELECT * FROM t WHERE name = '%s'", $name);
// AFTER
$stmt = $db->prepare("SELECT * FROM t WHERE name = ?");
$stmt->execute([$name]);
Pattern 4 — Yaf Model with custom query builder
// BEFORE (unsafe — raw string passed to model)
$this->_model->where("user_id = $uid AND type = '$type'")->find();
// AFTER (use array condition — depends on your Model API)
$this->_model->where(['user_id' => $uid, 'type' => $type])->find();
// OR if model supports raw+bindings:
$this->_model->where("user_id = ? AND type = ?", [$uid, $type])->find();
Pattern 5 — IN clause with array
// BEFORE (unsafe)
$ids = implode(',', $id_arr);
$sql = "SELECT * FROM t WHERE id IN ($ids)";
// AFTER (PHP 7.3 compatible)
$placeholders = implode(',', array_fill(0, count($id_arr), '?'));
$stmt = $db->prepare("SELECT * FROM t WHERE id IN ($placeholders)");
$stmt->execute($id_arr);
What NOT to Change
- Do not switch DB abstraction libraries (e.g. from custom Model to bare PDO) unless the whole project already uses PDO
- Do not parameterize column names or table names — these cannot be parameterized; use an allowlist instead
- Do not touch SQL built entirely from constants with no user input
- Do not change surrounding cache logic, error handling, or return values
False Positive Checklist
Before reporting a finding as confirmed SQL injection:
- [ ] Does user-controlled input actually reach this SQL string?
- [ ] Is the value an integer that was already
intval()-cast earlier? - [ ] Is the value selected from a fixed allowlist (e.g. column name from a whitelist array)?
- [ ] Is the SQL built from config constants only (no request data)?
If all four are "no" → confirmed risk. If any is "yes" → suspected or false positive.
Bulk Fix Guidance
When fixing many files across a project:
- Run
scan_sql.shon the whole project, save output to file - Sort findings by controller/callback/payment paths first
- Fix highest-risk files first (payment, callback, login)
- Re-scan after each batch to track progress
- Never mix SQL fix commits with unrelated changes
Scan to join WeChat group