Short answer
Give the model its own database account that can only read the few tables or views it needs, and let your code, not the model, run every query, with values passed as parameters. That way, even a confused or manipulated model can only see what you chose to show it and cannot change anything.
Follow-up question How do you stop it from changing data it shouldn't?
Connecting a language model to a database is one of the most useful things you can build: staff can ask "How many orders did we ship to Quebec last month?" in plain words and get an answer from live data. It is also one of the easiest ways to cause damage, because the model's output turns into commands that a real system carries out.
The safe approach does not depend on writing a clever prompt. It depends on limits the database itself enforces. Assume the model will sometimes misunderstand a request, and that someday a document or message it reads will try to trick it. This trick is called prompt injection: text hidden in the input that tells the model to do something its owner never intended. Then design the connection so that neither mistake can hurt you.
The risk has a name: excessive agency
The OWASP Gen AI Security Project, part of the OWASP open security community, lists excessive agency among the top risks for AI applications. It means an AI system can take harmful actions because of a bad, unclear or manipulated output from the model. OWASP traces it to three causes:
| Cause | Database example | Fix |
|---|---|---|
| Too much functionality | The tool can run any SQL, not just look things up | Offer only the lookups the task needs |
| Too many permissions | The account can also insert, update and delete | A read-only account limited to specific tables |
| Too much autonomy | Results or changes go out with nobody checking | A person approves anything with real impact |
OWASP's own example fits exactly: a feature meant only to read data that connects with an account allowed to write and delete as well. Its recommendation is to enforce the limit with database permissions on the account the AI uses, not with instructions to the model.
A worked example: a read-only role in PostgreSQL
Here is a setup for PostgreSQL, a popular open-source database. The goal is an assistant that answers sales questions from an orders table, without seeing customer names or emails.
-- 1. A dedicated account for the AI tool, used by nothing else
CREATE ROLE ai_reader LOGIN; -- set its password from your secrets manager
-- 2. A view that exposes only the columns the assistant needs
CREATE SCHEMA reporting;
CREATE VIEW reporting.order_summary AS
SELECT order_id, order_date, region, total
FROM public.orders;
-- 3. The smallest set of privileges that makes it work
GRANT CONNECT ON DATABASE shop TO ai_reader;
GRANT USAGE ON SCHEMA reporting TO ai_reader;
GRANT SELECT ON reporting.order_summary TO ai_reader;
-- 4. Safety nets for every session of this account
ALTER ROLE ai_reader SET default_transaction_read_only = on;
ALTER ROLE ai_reader SET statement_timeout = '5s';
Why each part matters. In PostgreSQL, a new table belongs to whoever created it, and other accounts get nothing on it until someone grants a privilege, a named permission such as SELECT (read) or UPDATE (change). So ai_reader starts with no access to orders at all. The view, a saved query that looks like a table, shows four columns and hides the rest; the OWASP SQL injection guidance recommends exactly this, granting access to a view instead of the underlying table. USAGE on the schema lets the account find the view, and SELECT lets it read. The last two lines make every session read-only by default and stop any query that runs longer than five seconds, so a runaway request cannot slow the database for everyone.
Try it: On a test copy of your database, create a role like ai_reader, connect as it, and try UPDATE reporting.order_summary SET total = 0;. You should get a permission error. Then try reading public.orders directly, which should fail too.
Let your code run the queries
The second layer is in your application. There are two common designs.
Fixed queries with parameters. You write the SQL, and the model only picks the query and fills in values, such as a region and a month. Your code passes those values separately from the SQL text, which is called a parameterized query. OWASP's SQL injection guidance names this its first defense: the database always treats a parameter as data, so input like tom' or '1'='1 is searched for as text rather than run as code.
import psycopg
def orders_by_region(region: str, month_start: str):
with psycopg.connect(AI_READER_DSN) as conn:
conn.read_only = True
with conn.cursor() as cur:
cur.execute(
"SELECT count(*), sum(total) FROM reporting.order_summary "
"WHERE region = %s AND order_date >= %s::date "
"AND order_date < (%s::date + interval '1 month')",
(region, month_start, month_start))
return cur.fetchone()
Model-written queries with review. For open-ended questions, the model drafts a SELECT statement. Before running it, your code checks that it is a single read statement, shows it in the logs, and executes it only through the ai_reader connection. The check is a convenience; the role is the real protection.
How do you stop it from changing data it shouldn't?
Make change impossible at the database level for the account the AI uses. If the role holds only SELECT on a view, an UPDATE or DELETE fails no matter what the model writes or what an injected message tells it to do. The read-only session setting adds a second barrier, but treat it as a safety net rather than the lock, since a session can switch that setting back off; missing privileges cannot be switched on from inside the session.
When the assistant does need to write, for example to create a support ticket, give that job a separate account and a separate tool that can insert into one table only. Prefer a narrow function with fixed inputs over free-form SQL. For anything with real impact, such as refunds or deleting records, follow OWASP's advice to require a person's approval before the action runs, and enforce that approval in your own system rather than asking the model to respect it.
Finally, keep a record. OWASP notes that logging and rate limits do not prevent misuse, but they show you quickly when something unexpected is happening. Log every query the AI runs, with the user who asked and the time, and review the log after the first week.
Sources
- LLM06:2025 Excessive Agency — OWASP Gen AI Security Project, May 5, 2025
- PostgreSQL 18 Documentation: 5.8. Privileges — The PostgreSQL Global Development Group, June 8, 2026
- SQL Injection Prevention Cheat Sheet — OWASP Cheat Sheet Series, July 7, 2026