How do I implement row-level security for LLM agents?
Two layers, both required:
Database RLS (Postgres CREATE POLICY, MSSQL CREATE SECURITY POLICY) ties row visibility to a session variable like current_setting('app.tenant_id'). The agent's DB connection sets this on every request. This catches the agent if it manages to issue a raw query — but only for SELECT/UPDATE/DELETE against tables you've protected.
AST-level policy (the QueryShield approach) inspects the parsed query *before* it hits the DB and rejects anything that doesn't include the required predicate. Example policy:
agents:
customer-support:
tables:
orders: { require: "tenant_id = :ctx.tenant_id" }
users: { require: "id = :ctx.user_id" }
The AST layer is faster (no round trip on rejected queries), produces clear error messages the LLM can recover from ("missing required predicate tenant_id"), and works on databases without native RLS (SQLite, MySQL pre-8.0 in some configs, DuckDB). Run both: AST layer rejects 99% of bad queries with explainable errors; RLS is your last-line guarantee.