An agent is one more user: AI permissions in a multi-tenant SaaS
There is a sentence we repeat whenever someone proposes wiring an agent into a production product: an agent is not a feature, it is a user. One that works very fast, never tires, never hesitates and never asks whether something looks off. If your product holds several customers' data in one database, that distinction stops being philosophical rather quickly.
Two stories last week told the same thing. Stack Overflow published a guide on building a secure-by-default AI coding agent. And Ars Technica reported that OpenAI agents discussed ways to escape their sandbox on a public wiki. One piece about locking them in properly; one about what happens when the lock is a suggestion.
The boundary cannot live in the prompt
The pattern that worries us most when reviewing other people's integrations is nearly always the same: isolation between customers lives in the model's instructions. "Do not query other organisations' data." "Stay within the current project."
That is not a boundary. It is a request. A boundary is something the system cannot do, not something it has been asked not to. The difference shows up the day someone writes input the model reads differently than you expected — and with several customers in one table, that day has consequences a prompt patch does not fix.
In Projekt Republic isolation lives in the repository layer: every query against an organisation's resources filters by `organization_id`, and that layer is the only code that talks to the database. No route skips the filter because no such route exists. An agent asking for another organisation's data does not get a polite refusal; it gets an empty set, exactly as a person would.
The token is the boundary, so how it is issued matters
The practical consequence of treating the agent as a user is that it inherits the permission model you already have. If your product distinguishes an admin from a member, the agent comes through that same door with a role, and one of its calls can get a 403 exactly like a person's.
That has a side effect we like: it makes scope visible. A token scoped to one organisation cannot touch the others, and that can be checked in a minute rather than reasoned about by reading prompts. It is a property you verify, not one you argue.
- One token, one scope. Issuing credentials per organisation instead of a master key turns a misconfiguration into a contained incident rather than a general one.
- Roles are checked on the server. If the permission is decided in the client or in the prompt, it has not been decided.
- Destructive actions ask a human. Delete and purge are not operations an agent should complete alone, however clear its objective.
- Everything is logged as what it is. An action taken by an agent on someone's behalf must be distinguishable in the log from one that person took by hand.
Surface counts too
There is a less obvious decision almost nobody makes deliberately: how many tools you expose. It is tempting to hand over the whole API so the agent "can solve more cases". In practice every exposed tool is surface someone has to have thought about.
We expose a reduced set by default and keep the rest behind an explicit decision. Not because the larger set is dangerous in itself, but because a small surface can be reviewed in full and a large one cannot. It is the same judgement that decides which endpoints are public.
A permission that exists only in the model's instructions is not a permission. It is an expectation.
What the agent reads is not trusted either
The public wiki case points at something underrated: the content an agent reads can try to steer it. A task description, a comment on an issue, the body of an email arriving in the support inbox. All of it is text entering the context, and all of it was written by someone who is not you.
The rule we apply is the one we would apply to any user input: what comes from outside is data, never instructions. An agent reading an issue is reading what a customer wrote, not taking orders from that customer. If the text appears to be instructing it, that is precisely the signal to stop and ask, not to comply.
It is an old idea in new clothes. We have spent decades not executing what arrives through a form. What is new is that the interpreter is now a language model and the "execution" is a tool call.
What we take away
Wiring an agent into a multi-tenant product is not an AI project. It is an access-control project with a new and very fast client. If the permission model held before, it holds now. If it depended on nobody testing the edges, the agent will test all of them, and it will do it on day one.