Skip to content

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

  1. A principal per agent. Use an ID such as agent:assistant:user_42 for 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.
  2. 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.read on /workspaces/acme/docs. Inheritance means new documents there are covered automatically; everything else is denied.
  3. 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.
  4. Check before every tool call. Call check or assert with the agent’s principal before acting, and use listAccess to limit search and retrieval to what it may read.
  5. 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.

Next steps