AI agents
Give AI agents only the access their user allows
An agent that reads documents, files tickets or sends emails on someone’s behalf needs the same question answered as any other part of your app: may it do this, here? With Permio you treat each agent as its own principal and grant it a subset of what the user can do.
Everything on this page uses Permio features that exist today: principals, grants on resource paths, check-only API keys, the audit log and revocation. There’s no separate agent product.
The pattern
- A principal per agent. Use an ID such as
agent:assistant:user_42for each user-agent pair (or one per agent, if it acts for no one in particular). Principal IDs are your own strings, so no registration is needed. - Grants scoped to resources. When the user connects the agent, grant its
principal only the permissions it needs, on only the part of the tree it needs, for example
docs.readon/workspaces/acme/docs. Inheritance means new documents there are covered automatically; everything else is denied. - A check-only key for the agent’s backend. The service that runs the agent uses an API key with check access: it can run checks and list what the agent may see, but it can’t create grants, so a misbehaving agent can’t widen its own access.
- Check before every tool call. Call
checkorassertwith the agent’s principal before acting, and uselistAccessto limit search and retrieval to what it may read. - Audit and revoke. Grant changes appear in the audit log. Revoke the agent’s grants when the user disconnects it, or invalidate its key to stop it immediately.
Keep the agent’s grants a subset of the user’s. A simple rule: only offer the user grants on
resources where permio.check passes for the user themself.
1. Grant the agent a scoped role
import { Permio } from 'permio';
// Your app's backend, with a full-access key. Runs when the user connects the agent
// and chooses what it may touch.
const admin = new Permio({ apiKey: process.env.PERMIO_ADMIN_KEY!, project: 'default' });
const agent = `agent:assistant:${user.id}`; // one principal per user-agent pair
await admin.permissions.grantUserPermissionsAndRolesOnResource({
projectName: 'default',
userId: agent,
resourceUri: '/workspaces/acme/docs', // only this part of the tree
permissions: ['docs.read'], // read, not write
}); 2. Check before the agent acts
import { Permio, resource } from 'permio';
// The agent's backend, with a check-only key: it can ask questions,
// but it can't grant itself anything.
const permio = new Permio({ apiKey: process.env.PERMIO_AGENT_KEY!, project: 'default' });
// Before every tool call, check the agent's principal, not the user's.
async function readDocument(agent: string, docId: string) {
await permio.assert({
principal: agent,
resource: resource('workspaces', 'acme', 'docs', docId),
permission: 'docs.read',
}); // throws PermioDeniedError when not allowed
return db.docs.get(docId);
}
// Only search or retrieve what the agent may read.
const access = await permio.listAccess({
principal: agent,
permission: 'docs.read',
under: '/workspaces/acme',
});
const visible = candidates.filter((d) => access.allows(d.path)); 3. Revoke when access ends
// When the user disconnects the agent, remove its grant (full-access key).
await admin.permissions.revokeUserPermissionsAndRolesOnResource({
projectName: 'default',
userId: agent,
resourceUri: '/workspaces/acme/docs',
permissions: ['docs.read'],
}); Why not reuse the user’s identity?
- A separate principal lets the agent do less than the user, not everything the user can.
- You can see the agent’s grants on their own, test its access in the Access tester, and remove them without touching the user’s.
- A check-only key limits the blast radius if the agent’s environment is compromised: the key can be invalidated without affecting the rest of your app.
Coming soon: an MCP server for Permio.