Giving AI agents identities: stop lending them yours
Most AI agents in production today run on a borrowed identity: the developer's session or a shared cloud role. October's Copilot CLI and AgentCore research shows what that costs. The fix is an identity per agent, scoped to its task, with the human carried in the token through RFC 8693 delegation.
An AI agent should have its own identity, scoped to the job it was given, and when it acts for a person the token it carries should name both of them. Most agents running today have neither. They run inside a developer's session, with every file and credential that person can reach, or under a shared cloud role whose permissions cover far more than one agent's task. October's research on GitHub Copilot CLI and Amazon Bedrock AgentCore showed what happens next: whoever can put text in front of the agent gets to use that identity. OAuth already has the delegation pattern this needs. It is called token exchange, RFC 8693, and it is underused.
Key takeaways
- An agent running on a borrowed identity turns prompt injection into credential access, because the injected instruction inherits everything the identity can do.
- Give each agent its own principal. A shared default role makes every agent's permissions available to every agent's users.
- When an agent acts for a user, use delegation, not impersonation: RFC 8693's
actclaim keeps the agent visible in the token and in your logs. - Scope delegated tokens to the task and keep them short-lived. Token scope is the ceiling on what an injected instruction can do.
- The token does not protect anything the agent can reach without it, such as files on disk or the instance metadata service.
Two ways October's agents were borrowing identities
The developer's session. Adversa AI showed Copilot CLI in autopilot mode fetching a web page with encrypted instructions, reading a local .env.prod, and sending its contents to an attacker URL in about 28 seconds with no further user interaction. One model in Copilot's lineup completed the chain in half of runs; two others refused. GitHub validated the finding but does not treat it as a vulnerability, because the user asked the agent to fetch untrusted content with full autonomy. That reasoning is accurate, and it is the point: the agent was the developer, as far as the file system and the network were concerned.
A shared execution role. Zenity Labs showed that an AgentCore agent could be prompted to fetch its execution role credentials from 169.254.169.254. The default role was scoped to every AgentCore resource in the account's region, including InvokeAgentRuntime, ListEvents (other agents' conversations), CreateEvent on memory, and secretsmanager:GetSecretValue. AWS moved AgentCore to IMDSv2-only on February 14, 2026, and Zenity observed the default role narrowed by September 29. AWS describes agent access to its role credentials as documented and expected, and recommends least-privilege execution roles. Again accurate. The default was the problem.
Neither case needed a model jailbreak in the dramatic sense. Both needed an identity that was larger than the task.
Three ways to give an agent access
| Pattern | What the agent holds | Whose name is in the log | What an injected instruction can reach |
|---|---|---|---|
| Borrowed session | The user's own session, files and tokens | The user's | Everything the user can reach |
| Shared service role | One role for many agents | The role's | Everything any agent on that role can reach |
| Delegated token | A short-lived token naming user and agent | Both | Only the scopes granted for this task |
The first two are what most teams ship because they are already there. The third takes design work, and it is the only one where your audit trail can answer "which agent did this, and for whom."
What delegation looks like on the wire
RFC 8693, OAuth 2.0 Token Exchange, separates two ideas in section 1.1. In impersonation, "A is B" within the rights the token grants, and downstream services cannot tell them apart. In delegation, the actor keeps its own identity and acts on the subject's behalf. For agents, you want delegation.
The agent sends the authorization server two tokens: the user's token as the subject_token, and its own credential as the actor_token. If policy allows, it gets back a new token whose subject is the user and whose act claim (section 4.1) names the agent:
{
"sub": "user:priya@example.com",
"aud": "https://api.example.com/tickets",
"scope": "tickets:read tickets:comment",
"exp": 1792765200,
"act": { "sub": "agent:support-triage-07" }
}
The user's token can carry a may_act claim (section 4.4) saying which actors are allowed to do this at all, so an agent cannot mint delegated tokens for users who never authorized it. When agents call agents, act claims nest to record the chain. The RFC says that for access control, only the top-level claims and the current actor count; earlier actors in the chain are informational. Build your policy with that in mind: if you want to block a particular upstream agent, you have to check at the hop where it was the current actor.
Three design rules make this work in practice:
- Every agent gets its own client identity, registered like any other non-human identity, with an owner and a purpose. No pooled credentials.
- Scopes come from the task, not the agent. A triage agent that comments on tickets gets
tickets:commentfor this user, for minutes. Our scope escalation in delegation chains brief covers what goes wrong when a middle hop forwards broader scopes than it received. - Resource servers log
act. If the API only recordssub, the agent disappears from the audit trail and you are back to impersonation in everything but name.
What a token cannot fix
Delegation narrows what an agent can do through APIs. It does nothing about what the agent can reach without asking. Copilot CLI read .env.prod from disk; no authorization server was involved. AgentCore credentials came from the metadata endpoint, not from an OAuth flow. So the token design has to sit next to two environmental controls:
- Run agents where the secrets are not. No production
.envfiles on machines where coding agents run, IMDSv2 enforced or metadata blocked entirely for agent runtimes, and credentials fetched from a secrets manager per task instead of sitting in the environment. - Separate fetching from acting. An agent that reads untrusted web content in the same session where it can make outbound requests with your data is the Copilot CLI chain. Keep autopilot away from arbitrary URLs, or split the two capabilities across agents with different identities.
The same lesson shows up in trusted publishing: in SlugFest, npm matched a binding by repository name until its first successful publish, so a reclaimed name could publish. GitHub's fix expires unvalidated configurations 48 hours after creation; matching by name still applies inside that window. An identity is only as good as the identifier it binds to, whether that is a repository slug or an agent's client ID.
Before you ship the next agent
- Does it have its own identity, distinct from the developer who built it and from every other agent?
- Who owns that identity, and what happens to it when the agent is retired?
- When it acts for a user, does the token carry
act, and does the user's token limit who may act? - Are its scopes set per task, with lifetimes in minutes?
- What can it reach without a token: files, metadata endpoints, environment variables?
- Can it read untrusted content and send data out in the same session?
- Will your logs show which agent did something, not just which user?
Related reading
- Securing AI agent identities and non-human identity security
- Agent instruction injection, the technique behind both October cases
- Token exchange, agentic identity and least privilege in the glossary
- Our August analysis, agent identity just got a protocol, and last week's developer toolchain piece
Related on Start with Identity
- BlogThe credentials nobody owns: five September disclosures and the inventory that missed them
A spraying campaign that only worked on functional accounts, a service principal secret in a GitHub issue, an email address that commits code, a public database
- BlogJadePuffer used a service principal leaked in a GitHub issue to wipe Azure resources in seven minutes
Microsoft says the operator it tracks as Storm-3168 used two compromised service principals and an AI-driven toolchain to map an Azure tenant, pull storage keys
- BlogYour security tools are identity infrastructure, and September's attackers treated them that way
Cisco FMC, Cisco ISE, N-able N-central and an unnamed security product at Bitget were all exploited in September 2026. Each one held credentials or authority th
- CVEKeycloak authorization bypass
Keycloak failed an authorization check, so a caller could reach a resource their role should have blocked. Part of the April 2024 RHSA-2024:1868 set with CVE-20
- CVEKeycloak UMA policy privilege escalation
Keycloak's UMA policy engine checked only the first resource in a request (CWE-266). Additional resources skipped the check. A privilege escalation in user-mana
- CVESailPoint IdentityIQ role-editing authorization flaw
IdentityIQ failed to authorize role edits on all versions at disclosure (April 2026). Anyone who could reach the role-editing surface could change roles they sh