Insights
AI Engineering Oct 2024

Giving AI Agents an Identity: Least-Privilege Access for Autonomous Workflows

When an autonomous agent acts on production data, the question "who did this?" needs a real answer. Here's how we modelled agent identity, scoped permissions and audit trails for an enterprise rollout.

The Problem

Six months into an autonomous workflows project for a mid-sized financial services client, the auditor asked a question that stopped the room: "When an agent updated this customer record, who actually did it?"

The honest answer was "a service account shared by the entire agent fleet." That answer didn't survive the meeting.

Agent identity sounds like a paperwork problem. It isn't. It's a control plane problem and getting it wrong means you can't answer questions about authorisation, attribution or blast radius - the three things any security review will eventually ask about.

What Agent Identity Actually Means

A non-human identity is not new - service accounts and machine credentials have existed for decades. The difference with agentic AI is that the identity is no longer tied to a piece of code with a fixed scope of actions. An agent's behaviour is shaped by prompts, tools and runtime context. The same agent identity might write to the CRM in one turn and read from the data warehouse in the next.

The standard reference - the one most large security teams now point to - treats every agent instance as a first-class principal: it has a stable ID, a scoped credential, a defined set of permissions and a logged action trail that ties every operation back to the originating conversation.

The Least-Privilege Complication

Three things make this harder than human IAM.

Permissions are bursty. Agents don't need a static role; they need different capabilities at different moments. An intake agent legitimately needs to write to a draft table during a conversation and not after.

Tool access is the real attack surface, not the model. An agent without tools is a chatbot. Granting tool access is the actual permission decision and most platforms still bundle tool access at the agent-template level rather than per-instance.

Delegation is implicit. When an agent calls another agent, whose permissions apply? The caller's? The callee's? The original user's? Most teams ship without an explicit answer and that's where blast radius silently expands.

The Fix

Three things we built for the client, in order of impact.

1. Per-instance ephemeral credentials. Every agent invocation gets its own short-lived credential, scoped to the tools and data its current task needs. We back this with our existing identity provider and issue tokens via OAuth client credentials with custom scopes.

# Issued per agent run, expires when the conversation closes.
token = identity_provider.issue_token(
    subject=f"agent:{agent_template_id}",
    instance_id=run_id,
    scopes=task.required_scopes,  # e.g. ["crm:contacts:read", "draft:write"]
    ttl_seconds=3600,
    on_behalf_of=user_id,
)

The on_behalf_of claim is what made the auditor stop asking questions. Every write was attributable to a specific user, a specific agent template and a specific run.

2. Tool-level policy enforcement, not just agent-level. The MCP servers we exposed to agents enforce scopes themselves, independent of what the model thinks it's allowed to do. If a model hallucinates a tool call outside its scope, the call is rejected at the server, not the prompt.

3. An explicit delegation model. When agent A invokes agent B, agent B receives a derived token whose scopes are the intersection of agent A's scopes and agent B's template scopes. No more, no less. Delegation can narrow permissions; it can never widen them.

What We'd Do Differently

We would integrate with the identity provider from day one. The first three months of the project used a flat API-key scheme because it was faster to ship. Rebuilding on top of OAuth was a six-week project we could have avoided.

We would also push harder on per-tool audit logging earlier. The platform logged agent reasoning traces, but the auditor wanted "show me every customer record this agent touched in March." Reconstructing that from reasoning traces was slow. A simple action log keyed by (run_id, tool_name, target_resource) would have answered the question in seconds.

Takeaway

Treat every agent run as a principal with its own credentials, its own scopes and its own audit trail. The cost upfront is a few weeks of identity engineering. The cost of skipping it is an audit finding you can't talk your way out of.