Build1 publisher3 min readPublished
A system prompt is not a GRANT: read-only agents have to be enforced below the model
A dev.to walkthrough argues that write protection for AI database agents belongs in the role, the routing and the query parser. The prompt is the one layer text can argue past.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened
- An AI agent that can query your production database is also, by default, an AI agent that can UPDATE, DROP and TRUNCATE it.
- The post argues the fix is not to keep AI away from your data but to make write operations structurally impossible, enforced at layers the model cannot talk its way past.
- The post calls adding "only run SELECT queries, never modify data" to the system prompt a tempting shortcut that should not be relied on, because prompt instructions are suggestions, not enforcement.
- Large language models are probabilistic and hallucinate, and can misread a vague prompt like "clean up the test users" as an instruction to actually delete rows.
- A cleverly worded user request, an injected instruction hidden in data the model reads, or a plain misunderstanding can all lead the model to generate a destructive statement.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A walkthrough published on dev.to makes a narrow and useful claim: an agent that can query your production database is, by default, an agent that can UPDATE, DROP and TRUNCATE it, so the answer is to make writes structurally impossible rather than merely discouraged [1][2]. That matters because the mitigation most teams actually ship is a sentence in a system prompt, and the post is blunt that prompt instructions are suggestions, not enforcement [3].
The failure modes it lists are not exotic. Models are probabilistic and hallucinate, and a vague request like "clean up the test users" can be read as an instruction to delete rows [4]. A cleverly worded user request, an injected instruction hidden in data the model reads, or a plain misunderstanding are all enough to produce a destructive statement [5]. None of those are defeated by more emphatic wording in the prompt.
What the post proposes instead is defense in depth, with at least three independent layers enforced below the model [6]. The bottom one is a database role whose entire vocabulary is SELECT, which the author calls the single most important step because the database engine enforces it regardless of what SQL arrives [7]. In PostgreSQL that is a login role plus CONNECT on the database, USAGE on the schema, SELECT on all tables in the schema, and ALTER DEFAULT PRIVILEGES so tables created later are covered too [8]. Connected as that role, a DELETE against orders returns "permission denied for table orders" [9]. MySQL gets the same guarantee from GRANT SELECT ON app_production.* [10]. The default-privileges line is the part people skip, and it is the difference between a policy that holds and one that expires the next time someone runs a migration.
The post also argues against a blanket read-everything role: scope the grant to the tables that answer real questions, because the assistant probably has no business reading password_resets or internal_audit_log [11]. Read-only is not the same as read-all, and the blast radius of a prompt injection is exactly the set of rows you granted.
Layer two is routing. Send agent traffic to a read replica, which is physically read-only in a way even a superuser cannot override, and which keeps a few accidental full-table scans off the production write path [12]. On SQL Server Always On, ApplicationIntent=ReadOnly routes the session to a secondary and refuses to promote it to the primary [13]. Single-node deployments can fall back to SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY, after which an INSERT fails with "cannot execute INSERT in a read-only transaction" [14].
Layer three is a broker: instead of the model holding a database connection, it talks to an intermediary that holds the credentials, inspects every query and executes only what is allowed, which is the pattern Model Context Protocol servers implement [15]. Stacked, the three mean a write has to defeat the engine, the routing and the parser at once, rather than relying on what the model promised [16]. Worth noting the ordering: the broker is the layer you write yourself, so it is the layer with bugs. The grant is the one enforced by software that does not read English.
Things to check this week: whether the connection string your agent uses resolves to a replica, whether the role it authenticates as can write anything at all, and whether ALTER DEFAULT PRIVILEGES was ever run.