DEV Community

Auth By Example
Auth By Example

Posted on Originally published at permit.io

Before an agent's tool call runs, the check needs to know who delegated it

A normal permission check asks whether a user can take an action on a resource. Once an agent is acting for that user, the check has more inputs:

can(delegator, agent, scope, action, resource, conditions)

The delegator is the human who handed off the work. The agent is its own principal. Scope is what the human actually agreed to, like reading invoices or triaging tickets. Conditions cover tenant, time, trust level, and whether this action needs a person to approve it.

Problems tend to show up in the space between what the user consented to and what the agent's credentials allow. Say someone connects an agent to their mailbox so it can schedule meetings, and the token behind it can also send mail to outside addresses. Nobody agreed to that, and nothing blocks it.

Some things that help:

  • Give the agent short-lived tokens scoped to the delegation, and keep the real upstream credentials away from it. Then revoking the human's access or consent cuts the agent off on its next call.
  • Check each tool call that has a side effect. A session that started as "summarize my invoices" can drift into "buy gift cards" after a prompt injection, and a check at session start won't catch it.
  • Cap the agent below the human. An admin can still run an agent that is limited to read-only on one tenant.
  • Keep a decision log next to your traces. A trace shows that a tool ran. The decision log records who delegated, which agent acted, which rule matched, and whether a required approval happened.

Disclosure: I work at Permit.io. This is a short version of a longer post from our team, which also covers making an agent identify itself before tools unlock and putting enforcement in front of MCP servers:

https://www.permit.io/blog/ai-era-same-bar-higher-bar?utm_source=devto&utm_medium=social&utm_campaign=ai-era-same-bar-higher-bar&utm_content=authbyexample-article

Top comments (2)

Collapse
 
techsavant_lab profile image
TechSavant Security Lab •

The can(delegator, agent, scope, action, resource, conditions) framing is useful. Two things I've run into when testing this in practice:

  1. Delegation chains. Once an agent hands work to a sub-agent or calls an MCP server that calls another tool, the effective scope has to be the intersection along the chain, not whatever the last hop's token allows. If each hop re-derives scope from its own credentials, you get privilege widening without anyone consenting to it.
  2. Log what was consented, not just who. In the decision log, store the scope as the user saw it ("schedule meetings in my calendar"), plus the policy version that matched. When something goes wrong, "the token had mail.send" doesn't answer "did the user agree to this?".

A cheap test for the per-call check: inject an instruction into a document the agent reads that asks for a side-effecting call outside the delegated scope, and confirm you get a deny and a log entry with the delegator. If you only get the deny, the audit trail is the next gap.

Collapse
 
authbyexample1 profile image
Auth By Example •

Intersection along the chain is the failure mode I keep hitting too. A hop that re-mints from its own service identity can widen the leaf call past what the user approved. On logging, I keep the consented scope string the user saw plus the policy version id, because "mail.send allowed" alone never answers whether it matched the grant. Your inject-into-document test is a clean deny check; asserting the decision record still names the original delegator covers the audit half.