QueryShield

How do I prevent stacked queries from LLM-generated SQL?

Stacked queries (also called "piggybacked queries" or "multi-statement attacks") chain multiple statements with ; so a single LLM-emitted string runs as N statements at the DB. Example:

SELECT * FROM orders WHERE id = 1; DROP TABLE users;

Three layers of defense, run all of them:

1. AST multi-statement detection. Parse the LLM output. If the parser returns more than one top-level statement, reject — there is no legitimate text-to-SQL use case for emitting multiple statements in a single string. Log as decision=reject rule=multi_statement. 2. Driver single-statement mode. Configure your DB driver to refuse multi-statement strings: Postgres' libpq is single-statement by default for parameterized queries; MySQL JDBC needs allowMultiQueries=false; mysql2 Node driver needs multipleStatements: false; psycopg2 is safe by default but raw cursor.execute with ;-separated strings may still execute multiple statements depending on version. 3. Least-privilege DB role. Even if a stacked DROP slipped through, the agent's role lacks DROP privilege.

QueryShield's parser detects stacked queries before the driver is touched, with a clean error message the LLM can recover from ("multi-statement queries are not permitted; please emit a single SELECT").

This is also the canonical defense against the apocryphal "Bobby Tables" pattern adapted for LLMs — when the LLM is the one writing the chained ;, parameterization can't help, but AST-level rejection can.