Every product that gives a language model tools eventually hits the same question: what is the model actually allowed to touch? The tempting answer is a service account. Give the AI layer a privileged connection, let it do what it needs, and filter the results afterwards.
I think that answer is wrong, in a way that is hard to see until it bites. A service account means your authorisation rules now exist twice — once in the checks your application already performs, and once in prompt instructions and post-hoc filtering. The second copy is written in English, evaluated by a probabilistic system, and drifts the moment someone adds a permission.
The alternative is no new privileges at all
The approach I settled on: the AI layer holds no database credentials whatsoever. When the model decides to create something, it does not write a row. It calls the ordinary application API — the same one the web client uses — carrying the credentials of the human it is working for.
user ──credentials──▶ app API ──service token──▶ AI layer
│
tool call │ (carries the USER's credentials)
▼
app API
rate limit → identity → permissions
▼
handlerThe consequence is structural rather than procedural. Every check already in the request path applies unchanged. If the human cannot delete that record, neither can the model — not because a prompt told it not to, but because the request comes back 403.
What it costs
- Latency. A tool call becomes a real HTTP round trip rather than an in-process function call. Against a model call measured in seconds, this disappears.
- Credential lifetime. The user's token has to stay valid across an agent loop that can run longer than a normal request, so refresh handling has to be deliberate rather than incidental.
- Error surface. The model now encounters 402s, 403s and 429s. Those need to become sentences it can reason about, not raw status codes.
The failure this actually prevents
The risk here is not the dramatic one. Nobody's assistant drops the production database. What actually happens is quieter: a user asks a broad question, a retrieval step running with elevated credentials pulls in a document belonging to someone else, and the model helpfully summarises it. No alarm fires. Nothing looks broken.
That is a tenancy bug, and tenancy bugs are the kind that end companies. Making the AI use the user's own credentials means the cross-tenant read is impossible for the same reason it is impossible in the web app: the query never had access to begin with.
If you take one thing
Do not build a second authorisation system for your AI features. You already have one, it is already tested, and it is already the thing you patch when someone reports a bug. Route the model through it and let it inherit every guarantee you have already paid for.