SiteShadow

SQL injection static analysis

Why regex-based SAST misses multi-hop SQL injection

SQL injection rarely announces itself on one line. In real code, user input can move through handlers, helpers, query builders, and only later reach the database.

Browse SQL injection checks View coverage

The bug does not fit on one line

Here is a small Flask example:

from flask import request

def normalize_email(value):
    return value.strip().lower()

def user_lookup_clause(email):
    return f"email = '{email}'"

@app.route("/users")
def find_user():
    email = normalize_email(request.args["email"])
    where = user_lookup_clause(email)
    sql = "SELECT id, email FROM users WHERE " + where
    return db.execute(sql)

The dangerous operation is at the bottom:

db.execute(sql)

But the reason it is dangerous starts several lines earlier:

request.args["email"]

The vulnerability is the path between those two points.

Why pattern matching struggles

A pattern rule can search for string concatenation near a SQL call. That catches some bugs. It can also search for request parameters near a query. That catches some bugs too.

But this example splits the problem across functions:

  1. The request value enters in find_user.
  2. normalize_email changes the string but does not make it safe for SQL.
  3. user_lookup_clause builds part of the SQL statement.
  4. db.execute runs the final query.

No single line tells the whole story.

What taint tracking does differently

Taint tracking follows data from a source, such as request input, through assignments, helper calls, string construction, and return values. Then it checks whether that value reaches a sensitive sink, such as SQL execution.

If user-controlled input reaches a SQL execution call without parameterization, the scanner has a real reason to flag the code. The important difference is not that SiteShadow sees a suspicious function name. It is that SiteShadow follows the flow.

The fix is parameterization

The safer version keeps the SQL structure and the user value separate:

from flask import request

@app.route("/users")
def find_user():
    email = request.args["email"].strip().lower()
    sql = "SELECT id, email FROM users WHERE email = %s"
    return db.execute(sql, (email,))

The database driver treats the email as data, not executable SQL. That is the kind of fix developers should see immediately: what is wrong, why it matters, and what safe code looks like.

Where SiteShadow fits

SiteShadow is built for developers who want security feedback inside their workflow. It combines taint tracking, security rules, heuristic checks, and AI/LLM rule families to help find real risks while code is still fresh in the editor.

2,000+ checks
190+ CWEs
31 heuristic checks
5 AI/LLM rule families

Those numbers are not the story. The story is simpler: if user input becomes a query, command, file path, template, redirect, or model instruction, SiteShadow should help you see the risk before review.

Browse SQL injection checks Get started