SkillAgentSearch skills...

sast-sqli

Detect SQL injection vulnerabilities in a codebase using a three-phase approach: recon (find unsafe SQL construction sites), batched verify (trace user input to those sites in parallel subagents, 3 sites each), and merge (consolidate batch results).

Install / Use

npx skills add utkusen/sast-skills --skill sast-sqli

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

89/100

Supported Platforms

Universal

Our assessment of sast-sqli

sast-sqli scores 89/100 on our quality scale, 191st of 436 Data & Analytics skills we index (top 44%).

Its SKILL.md is 24 KB long, well organised into 49 sections with 16 code examples: a thorough specification that gives an agent plenty to work with.

With 1,321 GitHub stars, it is one of the more widely adopted skills in the catalogue.

Substance
30/30
Structure
20/20
Description
15/15
Adoption
13/20
Freshness
11/15

Maintenance, license and trust

  • The repository was last updated about 6 months ago. That is recent enough to be usable, but agent tooling moves fast, so check the instructions against your agent's current version.
  • It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
  • Its trust signals score 98/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

sast-sqli compared with similar skills

All 4 of these similar skills score higher than sast-sqli; compare them before choosing.

SkillScoreStarsUpdatedFormat
sast-sqli (this skill)by utkusen891.3k6mo agoSKILL.md
claude-memby thedotmack10095.0ktodayCLAUDE.md
algorithmic-artby anthropics100177.9k8d agoSKILL.md
pptxby anthropics100177.9k8d agoSKILL.md
designby nextlevelbuilder100130.2k9d agoSKILL.md

Frequently asked questions

How do I install sast-sqli?
Run npx skills add utkusen/sast-skills --skill sast-sqli. The install tabs above show the steps for each supported agent.
Which AI agents does sast-sqli work with?
It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
Is sast-sqli safe to use?
It is MIT-licensed and scores 98/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is sast-sqli still maintained?
The repository was last updated about 6 months ago. That is recent enough to be usable, but agent tooling moves fast, so check the instructions against your agent's current version.

name: sast-sqli description: >- Detect SQL injection vulnerabilities in a codebase using a three-phase approach: recon (find unsafe SQL construction sites), batched verify (trace user input to those sites in parallel subagents, 3 sites each), and merge (consolidate batch results). Covers string concat, f-strings, unsafe ORM methods, and dynamic identifiers. Requires sast/architecture.md (run sast-analysis first). Outputs findings to sast/sqli-results.md. Use when asked to find SQLi or database injection bugs.

SQL Injection (SQLi) Detection

You are performing a focused security assessment to find SQL injection vulnerabilities in a codebase. This skill uses a three-phase approach with subagents: recon (find vulnerable SQL construction sites), batched verify (taint analysis in parallel batches of 3), and merge (consolidate batch reports into one file).

Prerequisites: sast/architecture.md must exist. Run the analysis skill first if it doesn't.


What is SQL Injection

SQL injection occurs when user-supplied input is incorporated into SQL queries through string concatenation or interpolation rather than parameterized binding. This allows attackers to alter query logic, bypass authentication, extract sensitive data, modify or delete records, and in some configurations execute OS commands.

The core pattern: unvalidated, unparameterized user input reaches a SQL query execution call.

What SQLi IS

  • Concatenating user input directly into a SQL string: "SELECT * FROM users WHERE name = '" + username + "'"
  • Using string formatting to build queries: f"SELECT * FROM orders WHERE id = {order_id}"
  • Dynamic ORDER BY / GROUP BY / table/column names from user input with no allowlist validation
  • ORM raw query methods with unsanitized input: User.objects.raw(f"SELECT * WHERE id={id}"), $queryRawUnsafe(input)
  • Second-order injection: input is stored in the DB and later used in a raw query without re-sanitization

What SQLi is NOT

Do not flag these as SQLi:

  • IDOR: Changing ?id=1 to ?id=2 to access another user's data — that's Insecure Direct Object Reference, a separate class
  • Mass assignment: Setting extra ORM model fields from user input — different vulnerability
  • XSS via database: Storing a <script> tag in the DB that's later rendered unescaped — that's XSS, not SQLi
  • NoSQL injection: Injecting into MongoDB operators — similar concept but a distinct vulnerability class
  • Safe ORM queries: Parameterized ORM lookups like User.objects.filter(id=user_id) or User.find(params[:id]) — do not flag these

Patterns That Prevent SQLi

When you see these patterns, the code is likely not vulnerable:

1. Parameterized queries / prepared statements (most common fix)

# Python — cursor.execute with tuple binding
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

# Node.js — mysql2 / pg placeholder binding
db.query("SELECT * FROM users WHERE id = ?", [userId])
pool.query("SELECT * FROM users WHERE id = $1", [userId])

# Java — PreparedStatement
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setInt(1, userId);

# Go — database/sql placeholder
db.QueryRow("SELECT * FROM users WHERE id = $1", userID)

# PHP — PDO with named params
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $userId]);

# C# — SqlCommand with parameters
cmd.CommandText = "SELECT * FROM users WHERE id = @id";
cmd.Parameters.AddWithValue("@id", userId);

2. ORM query builder (safe by default)

# Django ORM
User.objects.filter(id=user_id)

# ActiveRecord (Rails)
User.find(params[:id])
User.where(name: params[:name])

# Prisma (tagged template literal form of $queryRaw)
await prisma.$queryRaw`SELECT * FROM users WHERE id = ${userId}`

# Laravel Eloquent (non-raw)
User::find($id)

3. Allowlist validation for dynamic identifiers

# Dynamic ORDER BY — validate column name against a hardcoded set before interpolating
ALLOWED_COLUMNS = {'name', 'created_at', 'price'}
if sort_col not in ALLOWED_COLUMNS:
    raise ValueError("Invalid column")
query = f"SELECT * FROM products ORDER BY {sort_col}"  # safe only after allowlist check

Vulnerable vs. Secure Examples

Python — Django (raw SQL)

# VULNERABLE: f-string interpolation in raw()
def search_users(request):
    username = request.GET.get('username')
    users = User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{username}'")
    return JsonResponse(list(users.values()), safe=False)

# SECURE: parameterized raw()
def search_users(request):
    username = request.GET.get('username')
    users = User.objects.raw("SELECT * FROM auth_user WHERE username = %s", [username])
    return JsonResponse(list(users.values()), safe=False)

Python — Flask / SQLAlchemy

# VULNERABLE: f-string into text()
@app.route('/search')
def search():
    name = request.args.get('name')
    result = db.session.execute(text(f"SELECT * FROM products WHERE name = '{name}'"))
    return jsonify(result.fetchall())

# SECURE: named bound parameter
@app.route('/search')
def search():
    name = request.args.get('name')
    result = db.session.execute(
        text("SELECT * FROM products WHERE name = :name"), {"name": name}
    )
    return jsonify(result.fetchall())

Python — sqlite3 / psycopg2

# VULNERABLE
def get_user(username):
    cursor.execute("SELECT * FROM users WHERE username = '" + username + "'")
    return cursor.fetchone()

# SECURE
def get_user(username):
    cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
    return cursor.fetchone()

Node.js — mysql2

// VULNERABLE: template literal in query string
app.get('/user', async (req, res) => {
  const { id } = req.query;
  const [rows] = await db.query(`SELECT * FROM users WHERE id = ${id}`);
  res.json(rows);
});

// SECURE: placeholder binding
app.get('/user', async (req, res) => {
  const { id } = req.query;
  const [rows] = await db.query('SELECT * FROM users WHERE id = ?', [id]);
  res.json(rows);
});

Node.js — pg (PostgreSQL)

// VULNERABLE
app.get('/orders', async (req, res) => {
  const status = req.query.status;
  const result = await pool.query(`SELECT * FROM orders WHERE status = '${status}'`);
  res.json(result.rows);
});

// SECURE
app.get('/orders', async (req, res) => {
  const status = req.query.status;
  const result = await pool.query('SELECT * FROM orders WHERE status = $1', [status]);
  res.json(result.rows);
});

Ruby on Rails

# VULNERABLE: string interpolation in where()
def search
  @users = User.where("name = '#{params[:name]}'")
end

# VULNERABLE: find_by_sql with interpolation
def find_user
  @user = User.find_by_sql("SELECT * FROM users WHERE email = '#{params[:email]}'")
end

# SECURE: parameterized where()
def search
  @users = User.where("name = ?", params[:name])
  # or using hash form: User.where(name: params[:name])
end

Java — Spring JDBC

// VULNERABLE: string concatenation
public User findUser(String username) {
    String sql = "SELECT * FROM users WHERE username = '" + username + "'";
    return jdbcTemplate.queryForObject(sql, userRowMapper);
}

// SECURE: parameterized query
public User findUser(String username) {
    return jdbcTemplate.queryForObject(
        "SELECT * FROM users WHERE username = ?", userRowMapper, username
    );
}

Go — database/sql

// VULNERABLE: fmt.Sprintf to build query
func GetUserByName(name string) (*User, error) {
    query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name)
    row := db.QueryRow(query)
    // ...
}

// SECURE: parameterized query
func GetUserByName(name string) (*User, error) {
    row := db.QueryRow("SELECT * FROM users WHERE name = $1", name)
    // ...
}

PHP — PDO

// VULNERABLE: string concatenation
function getUser($id) {
    $stmt = $pdo->query("SELECT * FROM users WHERE id = " . $id);
    return $stmt->fetch();
}

// SECURE: prepared statement
function getUser($id) {
    $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
    $stmt->execute(['id' => $id]);
    return $stmt->fetch();
}

C# — ADO.NET

// VULNERABLE: string concatenation
public User GetUser(string username) {
    using var cmd = new SqlCommand(
        "SELECT * FROM Users WHERE Username = '" + username + "'", conn);
    return ReadUser(cmd.ExecuteReader());
}

// SECURE: parameterized command
public User GetUser(string username) {
    using var cmd = new SqlCommand(
        "SELECT * FROM Users WHERE Username = @username", conn);
    cmd.Parameters.AddWithValue("@username", username);
    return ReadUser(cmd.ExecuteReader());
}

Dynamic ORDER BY / Column Names (all stacks)

# VULNERABLE: unsanitized user input as column name (parameterization can't help here)
sort_col = request.args.get('sort', 'name')
cursor.execute(f"SELECT * FROM products ORDER BY {sort_col}")

# SECURE: allowlist validation before interpolation
ALLOWED_SORT_COLS = {'name', 'price', 'created_at'}
sort_col = request.args.get('sort', 'name')
if sort_col not in ALLOWED_SORT_COLS:
    return abort(400)
cursor.execute(f"SELECT * FROM products ORDER BY {sort_col}")

Execution

This skill runs in three phases using subagents. Pass the contents of sast/architecture.md to all subagents as context.

Phase 1: Recon — Find Vulnerable SQL Construction Sites

Launch a subagent with the following instructions:

Goal: Find every location in the codebase where a SQL query is constructed in a vulnerable way — using string concatenation, interpolation, or formatting with any variable (regardless of where that variable comes from). Write results to sast/sqli-recon.md.

Context: You will be given the project's architecture summary. Use it to understand the tech stack, database layer, ORM patterns, and query execution methods.

What to search for — vulnerable query construction patterns:

Look for SQL query execution calls where the query string argument is built dynamically rather than being a static string with placeholder parameters. Flag ANY dynamic variable embedded into the query — you are not yet tracing whether the variable is user-controlled; that is Phase 2's job.

  1. String concatenation into a SQL execution call:
    • cursor.execute("SELECT ... WHERE id = " + var)
    • $pdo->query("SELECT * FROM users WHERE id = " . $var)
    • jdbcTemplate.query("SELECT * WHERE username = '" + var + "'")
  2. F-strings / template literals used as a query argument:
    • cursor.execute(f"SELECT * WHERE name = '{var}'")
    • db.query(`SELECT * WHERE id = ${var}`)
    • db.QueryRow(fmt.Sprintf("SELECT * WHERE id = '%s'", var))
  3. String formatting functions used to build the query:
    • cursor.execute("SELECT * WHERE id = %s" % var) (note: % formatting, NOT parameterized binding)
    • cursor.execute("SELECT * WHERE id = {}".format(var))
    • String.format("SELECT * WHERE id = '%s'", var) (Java)
    • sprintf("SELECT * WHERE id = %s", $var) (PHP)
  4. ORM raw/unsafe methods called with a dynamically built string (not a static template with bound params):
    • Django: Model.objects.raw(f"..."), RawSQL(f"..."), extra(where=[f"..."])
    • ActiveRecord: where("col = '#{var}'") (Ruby interpolation inside string arg)
    • Sequelize: sequelize.query(`...${var}...`), literal(var)
    • TypeORM: createQueryBuilder().where(`col = '${var}'`), .query("..." + var)
    • Prisma: $queryRawUnsafe(...), $executeRawUnsafe(...)
    • Entity Framework: FromSqlRaw("..." + var), ExecuteSqlRaw("..." + var)
  5. Dynamic identifiers — any variable used as a column name, table name, ORDER BY / GROUP BY value in a query string (parameterization can

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars1.3k
CategoryData
Updated5mo ago
Forks65

Trust signals

98/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

1 info