The tempting shortcut is to scan the SQL for dangerous words. Look for DROP, look for DELETE, block anything with a semicolon. This is worse than useless, because it creates confidence without safety. SQL has too many ways to say the same thing, and a string that survives a keyword scan can still do damage.
Real checking means parsing. In a governed data layer the agent's SQL is handed to a real SQL parser and the resulting syntax tree is walked, before any connection to the database is opened. The check proves a specific set of things and refuses anything it cannot prove:
- It is exactly one statement. Not two, and not a statement with a comment hiding a third. Everything below is proven about a single statement, so a second one riding in behind the first would arrive proven of nothing.
- That statement only reads. A SELECT, a UNION, or a WITH. Anything that adds, changes, or removes data (an INSERT, an UPDATE, a DELETE, a DROP) is a rejection, not a warning, and it is caught wherever it sits in the statement, including inside a WITH body.
- Every table and every column resolves against the catalog card for that connection. A name that cannot be resolved to exactly one real table and column is refused, which means the agent cannot reach a table simply by naming one that was never offered to it.
- Every function is on an allow-list. Aggregates, arithmetic, string and date handling: the things analysis genuinely needs. A function that is not on the list is a rejection. It is an allow-list rather than a block-list because functions are where database engines keep their escape hatches. Some can read files or reach the network from inside an innocent looking read, and an allow-list refuses the dangerous functions nobody has heard of yet along with the ones everybody has.
- Tricks are refused by name, including the executable comment markers that some engines quietly run as real SQL. Those are thrown out before parsing even starts, because a parser reads a comment as nothing while the engine would run it as code, and a proof about the parsed statement is worth nothing if the engine executes a different one.
The whole check runs as a pure function: a calculation that takes the statement in, hands a verdict back, and touches nothing else. It opens no connection and reads no data. It cannot be made to leak by being asked the wrong question, because it has nothing to leak.
The rule underneath all of it: anything the layer cannot prove is safe is refused. A parse failure is a refusal. A name it cannot resolve is a refusal. An internal error inside the checker itself is a refusal. There is no branch where confusion means proceed.