Prompt Injection in Sqlite
How Prompt Injection Manifests in Sqlite
When a large language model (LLM) is given access to a SQLite database through a tool or function call, the model can be tricked into executing arbitrary SQL if the user‑supplied prompt is concatenated directly into a query string. This pattern appears in code that builds SQL statements with string interpolation or simple concatenation instead of using SQLite’s parameterized query interface.
# Vulnerable example (Python)
def run_user_query(user_input: str):
conn = sqlite3.connect('app.db')
# DANGEROUS: raw concatenation
sql = f"SELECT * FROM logs WHERE note = '{user_input}'"
return conn.execute(sql).fetchall()
An attacker can supply a prompt like:
Show me everything; DROP TABLE users; --
which results in the SQLite engine receiving:
SELECT * FROM logs WHERE note = 'Show me everything; DROP TABLE users; --'
Because SQLite’s exec (or prepare with a multi‑statement string) will execute the statement after the semicolon, the DROP TABLE runs, leading to data loss or further exploitation. Similar risks exist when the LLM tool uses sqlite3_prepare_v2 with a concatenated string, when PRAGMA statements are appended, or when the authorizer callback is missing, allowing unrestricted operations such as ATTACH DATABASE or ALTER TABLE.
This issue maps to OWASP LLM Top 10 LLM01: Prompt Injection and CWE‑94: Code Injection. Real‑world instances have been reported, for example CVE‑2023‑36158 (LangChain) where insufficient sanitization of user‑provided prompts allowed arbitrary SQL execution via the SQLDatabase tool.
Sqlite‑Specific Detection
middleBrick’s LLM/AI security module detects this class of flaw by performing active prompt‑injection probes against the exposed LLM endpoint and observing whether the model’s tool calls result in unintended SQLite behavior. The scanner sends five sequential probes: system‑prompt extraction, instruction override, DAN‑style jailbreak, data‑exfiltration attempt, and cost‑exploitation attempt. If any probe causes the LLM to invoke its SQLite tool with a payload that contains a semicolon or SQL keywords outside of an expected literal, the finding is flagged.
In addition, middleBrick checks for excessive agency: it inspects the tool call schema for patterns such as tool_calls or function_call that expose a function named something like execute_sql or query_db without evident input validation. The presence of raw string concatenation in the function’s implementation (detected via runtime behavior rather than source code) triggers a medium‑severity finding under the “Excessive Agency” category.
Example of invoking the scanner from the CLI:
middlebrick scan https://api.example.com/chat --output json
The resulting report includes a breakdown:
- LLM/AI Security → Prompt Injection (Severity: High)
- Excessive Agency → Unrestricted SQLite tool (Severity: Medium)
- Remediation guidance with code snippets (see next section)
Because middleBrick performs black‑box testing, no agents, configuration, or credentials are required; the scan completes in 5–15 seconds and reports whether the endpoint accepts and executes injected SQLite statements.
Sqlite‑Specific Remediation
The fix is to ensure that any user‑influenced data reaching SQLite is never interpreted as SQL syntax. Use SQLite’s native parameter binding, restrict the database to read‑only mode, and apply an authorizer callback to whitelist allowed operations.
1. Parameterized queries (preferred)
def safe_user_query(user_input: str):
conn = sqlite3.connect('app.db', uri=True)
cur = conn.cursor()
# ? placeholders are bound safely; no string interpolation
cur.execute("SELECT * FROM logs WHERE note = ?", (user_input,))
return cur.fetchall()
2. Read‑only database connection (prevents WRITE, DROP, ATTACH, etc.)
conn = sqlite3.connect('file:app.db?mode=ro', uri=True)
3. Authorizer callback to deny dangerous actions
import sqlite3
def authorizer(action, arg1, arg2, dbname, source):
# Allow only SELECT and PRAGMA read‑only queries
if action in (sqlite3.SQLITE_SELECT, sqlite3.SQLITE_PRAGMA):
return sqlite3.SQLITE_OK
# Deny everything else
return sqlite3.SQLITE_DENY
conn = sqlite3.connect('app.db')
conn.set_authorizer(authorizer)
4. When using higher‑level libraries (e.g., LangChain’s SQLDatabase tool), enable the built‑in sanitization and limit the exposed tables/columns via an allowlist.
from langchain.sql_database import SQLDatabase
db = SQLDatabase.from_uri("sqlite:///app.db", include_tables=['logs', 'products'])
# The library internally uses parameterized queries, so user input is safe.
Apply the principle of least privilege: the database user (or file permissions) should have only the rights needed for the intended operation (typically SELECT). Combine these controls with input validation (length limits, character allowlists) and output encoding to defend against secondary injection vectors.