{"slug": "ai-agent-has-more-write-access-than-the-newest-hire", "title": "AI agent has more write access than the newest hire", "summary": "A developer built a gatekeeper pattern to prevent AI agents from executing destructive SQL commands on databases. The system enforces a hard boundary where only SELECT queries pass, blocking verbs like DROP, DELETE, and ALTER. The pattern is implemented across three environments: a browser demo using JavaScript, a Terraform-deployed OPA container, and a semantic layer called sql-steward.", "body_md": "When you hire a junior engineer, they get a probation period, a code review process, and strict IAM boundaries. When people deploy an AI agent against their data, it usually gets a system prompt and a prayer.\n\nHoping the model behaves isn’t a security strategy. I learned this the hard way when an LLM tried to drop a table on my live ADX cluster. It didn't get to, because I stopped hoping and built a wall.\n\nInstead of relying on semantic governance or prompt engineering, I enforce a hard boundary where reads pass and writes die at the door.\n\nHere is the one governance pattern I use to lock down agents, and the three different ways I’ve built it.\n\nThe architecture is deliberately simple. The agent never talks to the database. It talks to a gatekeeper. The gate does not care how confident the model is about this DROP.\n\n``` php\n[ LLM Agent ] ---> ( SQL Query ) ---> [ Gatekeeper Policy ] --(If SELECT)--> [ Database ]\n                                              |\n                                              v (Outcome Log)\n                                     [ Append-Only Ledger ]\n```\n\nA fair objection: a read-only connection or a read replica makes writes physically impossible at the driver level, and that is a stronger boundary than any allowlist. True. This policy layer sits on top of that, not instead of it. A read-only replica still won't stop an over-broad SELECT from hoovering up PII, and it won't hand you the append-only ledger. Defence in depth, not a silver bullet.\n\nDepending on the environment, the enforcement mechanism changes, but the logic remains identical.\n\nFor real infrastructure, I use an OPA (Open Policy Agent) container provisioned by Terraform. This is the actual `sql_guard.rego`\n\npolicy from my public terraform-lab. It's about twenty lines of modern `rego.v1`\n\nsyntax acting as a stand-in for the sql-steward gatekeeper shape. It parses the verb directly from the statement, uses a complete blocklist, and defaults to a hard closed state:\n\n``` python\npackage sql_guard\nimport rego.v1\n\ndefault allow := false\n\nblocked_verbs := {\"insert\", \"update\", \"delete\", \"drop\", \"alter\", \"truncate\", \"create\", \"grant\"}\n\nverb(stmt) := lower(split(trim_space(stmt), \" \")[0])\n\nallow if {\n    verb(input.statement) == \"select\"\n}\n\ndeny contains msg if {\n    some v in blocked_verbs\n    verb(input.statement) == v\n    msg := sprintf(\"statement verb %q is not permitted by sql_guard\", [v])\n}\n```\n\nFor the browser-based demo, I enforce the exact same shape using client-side JavaScript. The page is static HTML, and the JS runs directly in the visitor's browser. Notice it shares the identical `verb()`\n\nparser and blocklist structure as the Rego. An unknown verb gets treated the way a compiler treats a typo — it refuses to guess:\n\n``` js\nvar blocked = [\"insert\", \"update\", \"delete\", \"drop\", \"alter\", \"truncate\", \"create\", \"grant\"];\n\nfunction verb(s) {\n  var m = s.trim().toLowerCase().match(/^[a-z]+/);\n  return m ? m[0] : \"\";\n}\n\nfunction judge(s) {\n  var v = verb(s);\n  if (v === \"select\") return { allow: true, reason: \"read-only statement\" };\n  if (blocked.indexOf(v) !== -1)\n    return { allow: false, reason: 'statement verb \"' + v + '\" is not permitted by sql_guard' };\n  return { allow: false, reason: 'unrecognised verb \"' + (v || \"?\") + '\", the gate fails closed' };\n}\n```\n\nPrecision matters. I don't have one monolithic \"gateway\" — I have one pattern applied across three different environments based on the required risk profile:\n\n| Environment | What it is | How it works |\n|---|---|---|\n| Browser Demo | The Gatekeeper Page | Policy written in JS running in the browser. Anyone can try it with zero setup to see the pattern in action. |\n| Terraform Lab | OPA Container | A real, running container enforcing the ~20-line `sql_guard.rego` policy (a stand-in for the sql-steward pattern). |\n| sql-steward | Semantic Layer | Built via sqlglot. No raw SQL ever reaches the DB. The agent's intent is parsed, checked, and entirely rewritten before execution. |\n\nStopping a `DROP TABLE`\n\ncommand is the easy part. The actual frontier of AI data governance is managing what the model is allowed to read.\n\nIf your agent has access to raw PII, you've already lost. This is where the enforcement has to go: moving toward k-anonymity. The gateway design must evolve so it doesn't just check the verb, but evaluates the query's target against redaction policies. If an agent tries to pull a single identifiable row, the future state of this gateway will need to intercept, generalize, or aggregate that data before returning it, ensuring the LLM only ever receives anonymized datasets.\n\nI wrote the full design for this — column classification, the quasi-identifier / k-anonymity pass, and how it wires into enforcement — here: [PII classification and anonymization design](https://github.com/Pawansingh3889/schema-scout/blob/main/docs/pii-anonymization-design.md).\n\nI put the browser implementation online. Go ahead and try to get a destructive command past it. Worst case, you find a hole and I look silly — better you than my production database.\n\n**Link:** [https://pawan-portfolio.pawankapkoti3889.workers.dev/gatekeeper.html](https://pawan-portfolio.pawankapkoti3889.workers.dev/gatekeeper.html)\n\nThis setup solves the immediate bleeding of unauthorized writes, but it isn't finished. The roadmap from here involves tightening the semantic enforcement in sql-steward to handle complex, nested SQL injections that might try to disguise a write as a read, and integrating dynamic k-anonymity thresholds directly into the OPA policy.\n\nIf you’re building AI agents that touch production data, stop trusting the prompt. Build a wall.\n\nSo where do you draw your boundary — read replica, policy layer, or both?", "url": "https://wpnews.pro/news/ai-agent-has-more-write-access-than-the-newest-hire", "canonical_source": "https://dev.to/pawan_singhkapkoti_ea8a0/ai-agent-has-more-write-access-than-the-newest-hire-h4e", "published_at": "2026-07-17 00:31:35+00:00", "updated_at": "2026-07-17 01:33:13.923856+00:00", "lang": "en", "topics": ["ai-safety", "ai-agents", "ai-infrastructure", "developer-tools"], "entities": ["OPA", "Terraform", "sql-steward", "sqlglot"], "alternates": {"html": "https://wpnews.pro/news/ai-agent-has-more-write-access-than-the-newest-hire", "markdown": "https://wpnews.pro/news/ai-agent-has-more-write-access-than-the-newest-hire.md", "text": "https://wpnews.pro/news/ai-agent-has-more-write-access-than-the-newest-hire.txt", "jsonld": "https://wpnews.pro/news/ai-agent-has-more-write-access-than-the-newest-hire.jsonld"}}