# SQL injection pre-scan (ARION service)

Deterministic scanner for SQL-injection construction patterns in
source files. No server, no database, no network — every check is a
property of the source line with lightweight per-file state
(block comments, pending multi-line calls, f-string blocks).

**Honest scope:** this is a *pre-scan*, not an audit. It cannot prove
taint — a FLAG means "this line builds SQL from non-literal parts;
review where the value comes from". Queries assembled via builder
objects across many lines, ORM-safe APIs, and stored procedures are
out of scope. A clean report is not proof the codebase is
injection-free.

## Coverage

- **`SQLI-QUERY-INTERP` (HIGH)** — interpolation, concatenation, or
  format-expansion into a query-execution call:
  - Python `f"..."`, `%`, `.format()`, `+` concat into
    `execute()/executemany()/executescript()`, and `cursor`-family
  - JS/TS template literals `` `...${x}...` `` and `+` concat into
    `db.query()/pool.query()/prepare()/run()/all()/get()`
  - PHP `"$var"` / `{$var}` / `.` concat into `mysql_query`,
    `mysqli_query`, `pg_query`, `->query`, `->exec`
  - Ruby `#{...}` into `.where()/.find_by_sql()/.order()`
  - Go `+` concat / `fmt.Sprintf` into `db.Query/QueryRow/Exec`
  - Java `+` concat / `String.format` into `executeQuery`/`createQuery`
  - C# `$"...{x}..."` / `+` / `string.Format` into `SqlCommand`,
    `ExecuteReader`, `FromSqlRaw`
  - ORM predicate APIs (`.where/.having/.joins/.find_by` everywhere;
    `.select/.filter/.exists/.order/.group` in py/rb/php/ex — the
    array/DOM meanings in JS/TS are excluded to avoid false hits)
- **`SQLI-RAW-UNSAFE` (HIGH)** — same interpolation into explicitly
  raw/unsafe APIs: `.raw()`, `$queryRaw`/`$executeRaw` (Prisma),
  `knex.raw`, `sequelize.query`, `find_by_sql`, `unprepared`,
  `connection.execute`, `FromSqlRaw`, `ExecuteSqlRaw`
- **`SQLI-IDENT-INTERP` (MEDIUM)** — interpolation into
  identifier/order positions (`orderBy`, `.order`, `groupBy`, column/
  table name slots) — cannot be parameterized and is still injectable
  when attacker-controlled. Simple-identifier interpolations whose
  names look structural (`${column}`, `{table_name}`) are
  automatically tiered MEDIUM for the same reason.

## Severity downgrades

- **LOW** — interpolated expression wrapped in a same-line sanitizer
  (`intval`, `floatval`, `(int)`, `escape_string`, `->escape`,
  `->quote`, `pg_escape`), or the file sits under a
  test/spec/fixture/mock path.

## Suppression

- Parameterized calls (`execute("...%s", (v,))`, `query("...?", [v])`,
  `:name`/`$1`/`@p` placeholders) — only interpolation triggers flag.
- Full-line comments and `/* ... */` block bodies.
- Variables passed by name (`db.query(q)`) — flags land on the line
  where a sink receives non-literal construction.
- Multi-line calls: an open sink is followed up to 6 lines, so
  `execute(\n  f"SELECT {x} FROM t"\n)` still flags; triple-quoted
  f-string bodies flag `{name}` inside the open call.

## Targets

`.js .jsx .ts .tsx .mjs .cjs .py .rb .go .java .kt .php .cs .rs
.ex .exs`. Walks directories, skipping `.git`, `node_modules`,
`vendor`, `dist`, `build`, `target`, `__pycache__`, `.venv`,
`coverage`, `.next`, `out`.

## Usage

```bash
python3 sqli_prescan.py <file-or-dir>          # human report
python3 sqli_prescan.py <file-or-dir> --json   # machine-readable
python3 sqli_prescan.py --selftest             # 37 built-in vectors
```

Stdlib only. Exit 0 = no HIGH/MEDIUM findings, 1 = findings,
2 = usage error — safe to wire into CI as a pre-review gate.

## Validation

- 37/37 selftest vectors pass (all injection forms, all stacks,
  identifier-position tier, sanitizer/test downgrades, comment and
  parameterized suppression, multi-line calls).
- Live-validated on a 253-file TypeScript service repo: 6 findings,
  all genuine dynamic-SQL patterns (`${sets.join(',')}` update
  builder, `${IS_AUTHOR}` fragment interpolation, `${column}`
  identifier slots, `whereSql`/`orderBy` fragment assembly) — zero
  false positives after JS array/DOM verbs were excluded.
- 163-file second repo: 5 findings incl. a simulation-script
  interpolated query (HIGH) and test-file correctly tiered LOW.
