# We caught a SQL injection with an offline AI security scanner — here's the exact query it found

> Source: <https://dev.to/jomynn/we-caught-a-sql-injection-with-an-offline-ai-security-scanner-heres-the-exact-query-it-found-4187>
> Published: 2026-08-20 13:57:45+00:00

Here's a login endpoint from a small Express demo app

([ scan-target-demo-apps/apps/01-sql-injection](https://github.com/sendwavehub/scan-target-demo-apps)):

``` js
app.post('/login', (req, res) => {
  const { username = '', password = '' } = req.body;
  const query = `SELECT id, username, email, is_admin FROM users
                  WHERE username = '${username}' AND password = '${password}'`;
  const result = db.exec(query);
  // ...
});
```

You've seen this shape before. `username`

and `password`

go straight into the SQL string — no

parameter binding, no escaping. Submit `admin' --`

as the username and anything as the password,

and the query becomes:

```
SELECT id, username, email, is_admin FROM users
WHERE username = 'admin' -- ' AND password = 'anything'
```

`--`

comments out the rest of the line. The password check never runs. `{"ok": true}`

, logged in

as `admin`

.

Classic CWE-89, OWASP A03:2021. Nothing novel here — the interesting part is what caught it.

We ran this target through **AI Security Studio**, an offline security research platform we've

been building. No cloud calls, no code leaves the machine — everything you're about to read

happened on a laptop with no network access to anywhere but `localhost:3001`

.

The important design decision: **the LLM never sees raw source code or raw HTTP traffic.**

The pipeline looks like this:

```
Parser → Rule Engine → Knowledge Retrieval → Summarization → LLM → Reasoning → Finding → Report
```

A deterministic parser and rule engine do the actual *finding*. The local LLM only explains

*already-discovered* evidence — it's not asked to eyeball a codebase and guess where the bugs

might be. If the evidence isn't there, the platform is built to say "Needs Manual Verification"

instead of inventing a conclusion.

For this run, that meant:

`http://localhost:3001`

as scope.`server.js:56`

and the missing
auth check on the state-changing `POST /login`

route.The generated report includes the actual vulnerable line as evidence, not a generic "possible SQL

injection" label:

``` js
Source preview.
  app.post('/login', (req, res) => {
    const { username = '', password = '' } = req.body;
    const query = `SELECT id, username, email, is_admin FROM users WHERE username = '${username}' AND password = '${password}'`;
```

Risk: High. Root cause: CWE-89 (and a separate CWE-306 finding for the missing authorization

check). Suggested fix: parameterized queries. Confidence explicitly marked — some findings from

this pass are "Confirmed," others are flagged "Needs Manual Verification" rather than asserted,

because header/config-level evidence alone doesn't prove exploitability the way a manual

Active Test / Repeater run does.

It's easy to gloss over "runs locally" as a checkbox feature. In practice it means:

The demo app is intentionally vulnerable and open source — safe to point any scanner at, including

this one:

Built for security researchers, pentesters, and AppSec teams doing work they're authorized to do.

Next up in this series: stored XSS — one comment field, every visitor who loads the page.
