DEV Community

Cover image for How a SQL injection bug passed 3 rounds of code review (and how AI caught it instantly)
Muhammad Usman Shabir
Muhammad Usman Shabir

Posted on

How a SQL injection bug passed 3 rounds of code review (and how AI caught it instantly)

It was a Friday afternoon. The sprint was ending. Everyone wanted to go home.

A pull request came in for a new user lookup feature. Three senior developers reviewed it. All three approved it. It shipped to production that evening.

Two weeks later a security audit flagged it.

SQL injection. A textbook one. The kind that every developer learns about in their first week.

Here's the code that passed three rounds of review:

async function getUserById(userId) {
  const query = "SELECT * FROM users WHERE id = " + userId
  const result = await db.execute(query)
  return result.rows[0]
}
Enter fullscreen mode Exit fullscreen mode

Three experienced developers looked at this. Nobody caught it.

Why Humans Miss These

Before explaining how AI caught it instantly, it's worth understanding why three smart developers missed something so fundamental.

Review fatigue is real.

That PR was the 14th one reviewed that Friday. By the time a developer reaches their 10th PR of the day their brain is actively looking for shortcuts. They scan for the obvious — broken logic, missing edge cases, typos. Deep security analysis requires a different kind of focus that nobody has at 4pm on a Friday.

Security issues don't look obviously wrong.

That SQL query looks perfectly reasonable at first glance. It's readable. It's concise. The variable name makes sense. Nothing visually jumps out as dangerous unless you specifically stop and think "wait, is this concatenating user input directly into a database query?"

Time pressure kills thoroughness.

Sprint deadlines create invisible pressure on reviewers. Nobody says "approve it faster" — but everyone feels the unspoken expectation. A thorough security review of one file can take 20-30 minutes. Nobody has that kind of time when there are 14 PRs in the queue.

What a Malicious Request Looks Like

Once that code shipped, any user could send this as their userId:

1 OR 1=1
Enter fullscreen mode Exit fullscreen mode

Which turns the query into:

SELECT * FROM users WHERE id = 1 OR 1=1
Enter fullscreen mode Exit fullscreen mode

Which returns every single user in the database.

The fix took literally 10 minutes once we found it. Parameterized queries:

async function getUserById(userId) {
  const query = "SELECT * FROM users WHERE id = $1"
  const result = await db.execute(query, [userId])
  return result.rows[0]
}
Enter fullscreen mode Exit fullscreen mode

How AI Caught It Instantly

After that incident I started building GetCodeReviews — an AI code reviewer powered by Claude. I fed it that exact function. It caught it in 4 seconds. Here's the actual output:

Score: 23/100

CRITICAL — SQL Injection Vulnerability
Your query concatenates user input directly into a SQL string.
This allows an attacker to manipulate the query.

Fix: const query = "SELECT * FROM users WHERE id = $1"
      const result = await db.execute(query, [userId])
Enter fullscreen mode Exit fullscreen mode

No fatigue. No time pressure. No Friday afternoon distraction. Just a clear, specific diagnosis with the exact fix to apply.

Setting Up Automated Security Scanning in 2 Minutes

The real power isn't the manual paste-and-review. It's catching these issues automatically before they ever reach a human reviewer. Here's how to add GetCodeReviews to your GitHub Actions workflow:
→ Sign up at getcodereviews.com and get your API key from the dashboard
→ Add your API key to GitHub Secrets: Settings > Secrets > CODESCAN_API_KEY
→ Add this workflow file to .github/workflows/code-review.yml:

name: AI Code Review
on:
  pull_request:
    types: [opened, synchronize]
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run GetCodeReviews
        uses: getcodereviews/action@v1
        with:
          api-key: ${{ secrets.CODESCAN_API_KEY }}
          min-score: 70

Enter fullscreen mode Exit fullscreen mode

The min-score: 70 means any PR scoring below 70/100 automatically fails and cannot be merged until issues are fixed.

What It Catches Beyond SQL Injection

SQL injection is the obvious one but the AI catches a lot more that humans routinely miss:

Hardcoded credentials:

const apiKey = "sk-prod-abc123xyz789"
const db = connect("postgres://admin:password123@prod-db")
Enter fullscreen mode Exit fullscreen mode

Missing error handling:

// No catch block = silent failures in production
async function fetchUserData(id) {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
}
Enter fullscreen mode Exit fullscreen mode

Off-by-one errors:

// i <= data.length accesses data[data.length] = undefined
for (var i = 0; i <= data.length; i++) {
  console.log(data[i].name)
}
Enter fullscreen mode Exit fullscreen mode

The Uncomfortable Truth About Code Review

Human code review is essential. I'm not arguing we should replace it.

But human code review was never designed to catch every security vulnerability in every PR every day. Modern development — fast sprints, large teams, 10-15 PRs per day — has stretched code review far beyond what it was designed for.

AI doesn't replace the human review. It handles the mechanical part — catching the obvious security issues, the missing error handling, the dangerous patterns — so the human reviewer can focus on what humans are actually good at: architecture, business logic, maintainability.

Try It Yourself

Paste that vulnerable function into GetCodeReviews (getcodereviews.com) and see what happens. Free to try, no card needed.

Or drop your worst code review horror story in the comments. What's the most embarrassing bug that made it to production through a human review?

I'll go first: SQL injection in a user lookup function. Three senior developers approved it. Two weeks in production before anyone noticed.

We don't talk about that sprint anymore.

Top comments (0)