An AI agent with database access writes SQL nobody reviewed. Most of the time it gets it right. When it does not, the mistake is a DELETE without WHERE, a read of the customers table it had no reason to make, or a SELECT * that pulls the whole table.
Prompt and output guardrails (the ones that filter what the model reads or answers) do not see that statement. A database guardrail does: it checks each SQL statement between the agent and the database, before it runs, and decides with rules, not with another model. This guide shows how to implement one step by step, with what Vericto does today.
Where we stand. We build Vericto, database guardrails for AI agents. The steps hold with any tool; the examples use ours, and we also say what is left out.
1. Take stock: which agent, which database, which identity
Before you write a rule, list every agent that touches a database: a text-to-SQL assistant, a support agent, a coding agent with a database MCP server, a job that generates SQL. For each one, which database it uses and with which credential.
Give each agent its own identity. Vericto recognises the agent by the API key making the call (Runtime API, SDKs, CLI and MCP) or by the session's database user (TCP proxy). An agent that shares a credential with your application cannot be scoped or blocked on its own.
2. Put the check in the SQL path
A guardrail the agent can skip is a suggestion. Pick the path by where the agent runs:
- TCP proxy (PostgreSQL and MySQL): it sits between the application and the database, with no code changes. It is the only path that is a control for everything: it blocks, masks and enforces the allowlist before the statement reaches the database.
- Runtime API and SDKs (Python and Node, all six dialects): your code asks before it executes and runs the query that comes back approved.
- MCP: a coding agent calls
check_sqlwhile it writes the SQL, before there is a commit. - CLI and GitHub Action: migrations and SQL files are checked in the pull request, with an exit code from the verdict.
A Python LangChain agent goes through the SDK by changing one line:
pip install "vericto[langchain]"
export VERICTO_API_KEY=vtro_... # API key with the proxy:evaluate scope
export VERICTO_DATABASE_ID=<database-id> # the database you registered
from langchain_community.agent_toolkits import SQLDatabaseToolkit, create_sql_agent
from vericto.langchain import VerictoSQLDatabase
# Was: db = SQLDatabase.from_uri("postgresql+psycopg://app@db/app")
db = VerictoSQLDatabase.from_uri("postgresql+psycopg://app@db/app")
agent = create_sql_agent(llm, toolkit=SQLDatabaseToolkit(db=db, llm=llm))
A coding agent connects to the remote MCP server with an API key with the ci_dryrun:execute scope:
claude mcp add --scope user --transport http vericto https://mcp.vericto.com/mcp \
--header 'Authorization: Bearer ${VERICTO_API_KEY}'
3. Start in observe
Do not turn blocking on the first day. When you connect your first database, choose observe mode: for 7 days every BLOCK becomes a FLAG, nothing is stopped, and the dashboard shows each statement that would have been blocked, with the rule and the tree node that triggered it.
Let the agents work. What you see usually falls into three groups: real agent mistakes (what you want to stop), legitimate flows a rule touches (what you need to tune before enforcing) and unbounded reads flagged with VERICTO-050.
While observe mode lasts your database is not protected: everything is forwarded. Use it to calibrate, not as the end state.
4. Scope each agent
The standard rules stop what is destructive. They do not stop a well-formed read of a table the agent should not touch, which is exactly what an injected instruction produces. For that, on Team and Enterprise:
- A per-agent allowlist: the tables and columns it may use, read-only or read-write. Everything else is denied with
VERICTO-087. It starts in observe and is simulated against the agent's queries of the last 7 days before you move it to enforce. - Sensitive columns: tag emails, phone numbers or cards and decide whether the query that reads them is blocked, flagged or returns the masked value. Through the TCP proxy the database returns the value already masked; through the API, the agent gets the approved query it must run.
5. Write your business's rules
What is dangerous in your stack is in no standard list. A custom rule (from Builder) is a YAML condition on the AST. This one blocks a DELETE without WHERE on payments only:
rule: block-delete-on-payments
node_type: DeleteStmt
condition:
relation: payments
where_clause: null
A new rule starts in monitor: it is evaluated and recorded, and never blocks. Before you promote it, the rule simulator replays it against your workspace's query history (30 days on Builder, 90 on Team) and tells you which legitimate flows it would have blocked. Nothing runs against your database.
6. Move to enforce, layer by layer
When the log is clean, leave observe mode in Dashboard → Rules: from then on a BLOCK stops the statement. Promote custom rules and allowlists one at a time, starting with the production databases agents use.
A block has to be useful to the agent. Each BLOCKED verdict comes back with the rule that fired and a suggested fix, so the agent rewrites the statement instead of retrying it or getting stuck:
BLOCKED 1 of 3 statements must not ship as written.
line 4 · VERICTO-001 (critical)
Fix: DELETE FROM users WHERE id = $1
7. Plan for the bad day
If an agent loops on DELETE or starts reading outside its task, stop it first and understand why later. With the per-agent kill switch, the owner or an admin blocks its API key, its database user or every agent in the workspace, with a mandatory reason. On the API paths the block is immediate; on the TCP proxy it arrives at the next rule sync (30 seconds if you configure it so).
Write down who decides to block and unblock, and run a drill at least every quarter. The incident playbook has a runbook template to fill in.
8. Keep the evidence
Every decision is in the audit trail, blocks included: what an agent tries after a refusal is exactly what you want to review. Exported reports are signed and can be verified offline.
Checklist before you give an agent production
- One identity per agent: its API key or its database user.
- The agent's SQL goes through a check it cannot skip (the TCP proxy on Postgres and MySQL).
- At least a week in observe, with the false positives reviewed.
- A table and column allowlist, and the sensitive columns tagged.
- Your business's rules simulated against real traffic before they enforce.
- A kill switch tested in a drill, and a named owner.
What a database guardrail does not do
- It does not replace your database's roles, grants and row-level security: it is defence in depth.
- It does not see the prompt or detect an injection: it limits what the resulting statement can do.
- It does not govern the rest of what the agent does (other tools, APIs, spend): only its data access through SQL.
- Outside the TCP proxy, masking and the approved query depend on the application running what comes back.
The goal is not to say no to agents. It is to be able to say yes to AI agents on production data, knowing each statement goes through a check that does not depend on the model getting it right.