# The Attack Was Authorized: The Missing Security Boundary for AI Agents

# The Attack Was Authorized: The Missing Security Boundary for AI Agents

Give an AI agent access to GitHub—or to any API or MCP server authenticated with a bearer credential—and the agent receives authority to act in your name.

The credential may not sit inside the model's context. A well-designed system might keep it in the agent runtime, a tool, or a credential broker. But the downstream API sees the same thing: a valid caller making an allowed request. It cannot see whether you intended that particular action.

That distinction matters. If an agent opens the wrong pull request, sends private data to the wrong place, or delegates a task with more authority than you expected, every conventional authorization check may still pass.

The attack was authorized. You just did not authorize the attack.

## The service is protecting itself, not you

Simon Willison describes a dangerous combination for tool-using AI as the [lethal trifecta](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/): access to private data, exposure to untrusted content, and a way to communicate externally. Put all three in one system and prompt injection can become data exfiltration.

Model-level guardrails help, and we should use them. But I do not think probabilistic detection should be the final authorization boundary. A model cannot always distinguish an instruction from its operator from an instruction hidden in a web page, email, issue, or tool result. Both eventually arrive as input.

The problem is not that GitHub has weak security. GitHub has fine-grained permissions, repository controls, branch protection, short-lived installation tokens, and years of work behind its authorization model. From GitHub's perspective, the agent is simply a valid caller exercising the permissions it was given.

GitHub asks: **Is this caller allowed to perform this operation?**

It does not ask: **Did the human intend this operation as part of the current task?**

Nor should GitHub be expected to answer that second question. It does not have the context. The missing boundary belongs between a person and the software acting on that person's behalf.

## Machines have always held credentials. Agents are still different.

Service accounts, CI pipelines, and backend applications have used credentials for decades. So it would be wrong to claim that identity systems assume every credential holder is human.

The important difference is behavior.

Traditional automation usually follows code that somebody wrote in advance. It may contain bugs, but its possible actions are relatively predictable. An agent is useful precisely because it chooses the next action at runtime. That choice may be influenced by a user's request, a retrieved document, a web page, another agent, or the output of the previous tool call.

This makes least-privilege design harder. With a CI job, I can usually predict which repository it needs and which operations it will perform. With a general-purpose coding agent, I may not know the complete sequence until it is already working.

I can give the agent a narrowly scoped token, and I should. But static scopes do not capture everything I may want to say:

- Work only in this repository.
- Read source code, but do not read secrets.
- Open a pull request, but do not merge it.
- Spend no more than two dollars on external services.
- Stop after ten minutes.
- Ask me before calling a destructive tool.
- If you delegate, give the child less authority than you have.

Some of these constraints can be assembled from existing products. Others require application-specific policy. The awkward part is keeping them intact when one agent delegates to another.

## Delegation is where the model starts to bend

Agents increasingly delegate: a planner starts a coding agent, which starts a researcher, which may call a scraper or another tool. The authority passed down that chain should become narrower, never wider.

OAuth 2.0 Token Exchange, defined in [RFC 8693](https://www.rfc-editor.org/rfc/rfc8693.html), can represent delegation and identify actors through the `act` claim. It can even express a history by nesting those claims. But prior actors in that nested history are informational for access-control purposes. The claim records the chain; it does not, by itself, prove that every child received a strict subset of its parent's authority.

That is the property I care about: **attenuation**.

If an agent can read and write five repositories, it should be able to delegate read-only access to one repository. The child should be able to narrow that authority again. It should never be able to add a repository, restore write access, increase a budget, extend an expiry, or reset the delegation depth.

And I do not want this guarantee to depend on the child agent interpreting a natural-language instruction correctly. A widening attempt should fail verification.

## The shape of the authority I want

The design question I kept returning to was simple: what would I be comfortable handing to a sub-agent running somewhere I do not control?

My answer has four parts:

1. **Task-specific limits.** Authority should describe resources, operations, effects, budgets, expiry, and delegation depth.
2. **One-way narrowing.** A holder may remove authority, but may not add it.
3. **Proof of possession.** Copying the credential chain should not be enough; the caller must prove it holds the key to which the authority was issued.
4. **Verifiable records.** After execution, I want evidence of what was approved and what actually happened.

These are not new cryptographic ideas. Macaroons and Biscuit tokens already demonstrate holder-side attenuation and capability-style authorization. Recent agent research is moving in the same direction. South et al.'s 2025 paper, [*Authenticated Delegation and Authorized AI Agents*](https://arxiv.org/abs/2501.09674), extends OAuth and OpenID Connect with agent-specific credentials and delegation metadata. The 2026 [Agent Identity Protocol](https://arxiv.org/abs/2603.24775) explores verifiable identity, attenuated authorization, and provenance across MCP and agent-to-agent communication.

I am not claiming that nobody else is working on this problem. They are. Delegent is my attempt to explore a particular point in the design space: a small, local, inspectable authorization layer that does not require you to trust another hosted service.

## What I built

Delegent is open source. Its core is intentionally boring: a standard-library-only Go package with no database, network dependency, or external runtime. The authorization logic should be small enough to read, test, and argue about.

The basic unit is a **slip**. A slip is a signed statement describing limits such as scope, effects, budget, expiry, and remaining delegation depth. It is also bound to an agent's public key.

An agent can narrow its slip into a weaker child slip, offline. The signed chain travels with the request. A verifier then:

- verifies every signature;
- intersects the limits along the chain;
- checks expiry and delegation depth;
- rejects any widening attempt; and
- verifies that the caller holds the private key associated with the final slip.

In simplified form, the protocol looks like this:

```go
pub, priv, _ := protocol.NewKeypair()

slip, _ := protocol.SignSlip(
    protocol.SlipBody{/* limits */},
    protocol.NewEd25519Signer(priv),
)

chain, _, _ := protocol.Narrow(
    parent,
    caveats,
    childPub,
    parentSigner,
    nonce,
)

result := protocol.VerifyChain(
    chain,
    callerPub,
    callerSig,
    requestBytes,
    trustedRoots,
    now,
)
```

A gateway sits in front of the tools. Existing agents connect to it over MCP instead of connecting directly to every upstream server. The gateway evaluates the slip, requests human approval for sensitive operations, and records its decision.

The approval channel is deliberately flexible. It can be the agent's chat interface, a terminal prompt, or a message sent to a channel the user is actually watching. The important part is that approval happens outside the model and is enforced before the tool call reaches its destination.

## Authorization tells me what may happen. Receipts tell me what did.

Every gateway decision produces a signed receipt. Receipts are hash-chained per principal, so deleting, reordering, or editing an entry breaks verification.

A database row that says an agent performed an action is a claim made by the database operator. A signed receipt chain gives another party something it can verify independently.

This distinction will matter as agent systems become subject to compliance reviews and incident investigations. The [EU AI Act](https://eur-lex.europa.eu/eli/reg/2024/1689/oj) already establishes traceability and logging requirements for certain high-risk AI systems, although its requirements and application dates depend on the system category and are being phased in. Regardless of whether a particular agent falls within that regime, the engineering question remains useful: can I reconstruct what the agent was authorized to do, who approved it, and what it actually did?

An OAuth exchange log shows that a token was issued. It does not necessarily show that each later action stayed within the human's task-level intent. That is the gap the receipt chain is meant to cover.

## Where this stands

Delegent is early. The MCP gateway market is already crowded, and mature products will support more connectors and enterprise controls. I am not trying to win that feature-count contest.

I am interested in a narrower set of questions:

- What is this agent allowed to do right now?
- Who granted that authority?
- Can the agent delegate without increasing its authority?
- Can someone independently verify what happened afterward?

My bet is that these questions become more important as agents run longer, touch more valuable systems, and start delegating work to one another.

The code is at [github.com/iluxav/delegent](https://github.com/iluxav/delegent). The protocol package is only a few hundred lines of dependency-free Go. You can inspect it yourself rather than taking my description on faith.

To try the CLI:

```bash
go install delegent.dev/protocol/cmd/delegent-proto@latest
```

Mint a slip, attenuate it, try to widen it, and verify the resulting receipt chain. If the design is wrong, incomplete, or solving the wrong boundary, I would genuinely like to hear why. This is early enough that disagreement is useful.

