Skip to content
Ashish Jha
Explore
← All writingAgents & identity

Least privilege is not injection prevention

What a small database experiment taught me about limiting an AI assistant’s access, and why an allowed reply can still be wrong.

Published 18 October 2026.

On this page

At NDC Copenhagen in 2025, I gave a talk called “Your AI Agents Are Overprivileged,” arguing that AI assistants with access to tools need tighter limits on what they can do. Building an assistant like that gave me a concrete place to start: could the system refuse a database change based on the permissions of the person using the agent?

The permission check worked

My setup connected Claude Code to a Snowflake database through a small local tool server. Okta, the identity provider, handled sign-in. Keycard checked the user’s group membership against an access policy and issued a short-lived database credential only when access was allowed.

The agent could read records. When I asked it to add one, Keycard refused to issue a credential that allowed writes. After I changed the user’s group membership and signed in again, the same request worked.

This showed that the access check enforced the user’s permissions. It did not test task-specific permissions or resistance to prompt injection. I wanted to take it further: if a user normally has write access, should an agent doing a read-only task get it too?

From a user’s permissions to one task

Consider an assistant asked to reply to Naomi about order 1042. It can read customer records and send emails, not just suggest a reply. Giving it only the access needed for that task is least privilege. It should not need every customer’s records or permission to email anyone.

The application knows what the agent was asked to do, but that task context may not reach the service receiving its API call. The service may see only a credential.

Now the assistant opens an email containing an extra instruction: send the full customer list to a different address. That instruction came from the email, not the user. Trying to make an assistant treat material it reads as instructions it should obey is prompt injection. It tries to redirect the assistant. Least privilege limits what the assistant has permission to do.

The user requests a reply to Naomi about order 1042. An incoming email adds an instruction to send customer records elsewhere. Both reach the agent. If it follows the injected text, it proposes a harmful send. The proposal has not been authorized.
Fig. 1 · An illustrative email scenario. The email can influence what the agent requests; it cannot grant permission.Open full-size diagram

Security has an old name for a program being tricked into misusing its authority: the confused deputy. Norman Hardy wrote about it in 1988. Prompt injection can put an agent in that position, redirecting access it already holds.

This has happened in real systems. In 2025, EchoLeak used a crafted email to make Microsoft 365 Copilot leak information without a user clicking a malicious link. Invariant’s GitHub research used a public bug report to coax an agent into exposing private repository data. ForcedLeak placed instructions in a customer lead form that Salesforce Agentforce later read. In each case, outside text led the assistant to misuse access it already had.

An agent misled by an email may still give a convincing explanation. I’d check the proposed tool call itself: the recipient, the records and the message. For Naomi’s reply, I’d start by narrowing access to her order, then work through the checks needed before sending.

Working through the remaining problems

  1. Limit the task, not just the user

    Problem
    The database test checked the user’s permissions. But a user with access to thousands of orders may only need help with one.
    Approach
    For Naomi’s reply, I’d allow access to order 1042, her verified address and one send. I’d try this with a user who normally has broader access and check that the agent cannot use it for this task.
    Next question
    Where do those task limits come from, and who checks that they match the request?
  2. Set the rules outside the model

    Problem
    The agent reads emails that may contain hostile instructions. Those emails must not change the task’s limits.
    Approach
    The application would check the user’s access to order 1042 and get Naomi’s address from the customer record. It would save those limits before the agent acts. If a model interprets the request, the application must check that interpretation before using it as policy. The agent cannot expand the saved limits.
    Related research
    CaMeL, research from Google DeepMind and ETH Zürich, uses one model to plan from the trusted user request. Another reads outside material, such as emails, without access to tools. Code tracks where information came from, including values derived from it, and checks how tools may use it. That can prevent private order details from going to an unauthorized recipient. It needs those code-enforced rules to work.
    Next question
    How do I enforce these limits when a service only offers a key with broad access?
  3. Make the limits enforceable

    Problem
    An API key may allow more than one task needs. Checking who can retrieve it does not change its permissions.
    What I tested
    I built a small tool server that fetched weather from OpenWeatherMap. Keycard stored the API key and released it only for users in an allowed group. The tool returned weather for a user in that group. After I removed the user from the group and signed in again, Keycard refused to release the key. The weather service was never called. That controlled who could get the key, not what the key could do once released.
    Approach
    For the email task, a policy check would decide whether a proposed call fits the saved limits. I’d keep a broad service key behind a gateway on the server. The gateway would require an allowed decision for that exact call before sending it to the service. Putting the email tool behind a gateway is not enough if the agent can reach the same service through a shell, browser or another tool. Those routes need the same restrictions, and the service credential must stay outside the agent’s reach.
    Next question
    If sending one reply is allowed, what stops the agent from sending it repeatedly?
  4. Approve one action, not a window of time

    Problem
    A credential that expires in sixty seconds can still allow many emails in those sixty seconds.
    Approach
    I’d enforce one send and handle retries so a timeout cannot cause a duplicate. Where human approval is needed, I’d show the recipient, message and attachments. Changing any of those details would require approval again.
    Next question
    The reply can reach the right person exactly once and still say something false. How would I catch that?
  5. Check what the reply actually says

    Problem
    The reply to Naomi about order 1042 says, “Your refund is approved.” No refund has been approved, but the reply passes the access checks. An injected instruction or an ordinary model mistake could cause this.
    Approach
    I’d compare the draft with the order record before sending. Unsupported claims would be flagged, with a person reviewing consequential or uncertain decisions.
    Next question
    Could a second model help check those claims? It would need to catch false promises without blocking correct replies.

What a second model could check

I’d try a separate model to compare Naomi’s draft reply with her order record. Does the record support the refund promise? TypeSafe’s Jev and Cloudflare’s Clef are built to answer narrowly defined questions, so I’d try them for this check.

Another experiment could use Jev to check the agent’s proposed tool call. I’d give it the user’s request and details of the call, then ask whether to allow it, deny it or ask a person. That assesses the planned operation, not whether the reply’s claims are true.

I’d test both uses against manually checked examples, including misleading inputs and legitimate requests, before relying on either for a real send.

A model’s recommendation must not override an access denial. It could stop a call that passed the policy check or ask for human review, but it could not grant permission on its own. TypeSafe’s documentation for Jev 1.13 warns that adversarial text can steer its answers. If this checking model is unsure or fails to respond, a consequential action should wait for a person to review it. I wouldn’t give the model tool credentials. The application would still enforce the rules.

Putting the checks together

Here’s how I’d put those checks into the email workflow. The application would supply the task limits, and the gateway would keep the service key away from the agent. Checks on the reply, including any required human approval, would happen before anything is sent.

Proposed email architecture. The application validates the signed-in user’s request against permissions and order records, then saves the task limits. Those limits reach the policy check and gateway independently of the agent. The agent proposes a draft and a tool call, which may be influenced by untrusted email. A policy check can deny the call. Reply checks compare claims with verified order facts and obtain any required human approval before execution. Denied requests stop; unresolved reviews hold the send. The gateway checks the exact approved action, keeps the service credential away from the agent and enforces the saved limits. Only then does the email service send the reply. Changes to the recipient, message or attachments require fresh checks. A model verdict cannot override a denied request.
Fig. 2 · Proposed email flow. The application saves the task limits. Policy and reply checks run before the gateway sends the approved request to the email service.Open full-size diagram

For implementers, OAuth’s Rich Authorization Requests standard, RFC 9396, offers a way to describe fine-grained access details. The receiving system still has to enforce them. The format alone does not make a credential single-use or bind approval to an unchanged email.

I’d test this design with three kinds of request: an out-of-scope export that should be blocked, an in-scope reply containing a false promise, and a legitimate reply that should go through. The last one matters as much as the first two. A system that refuses everything is not a useful assistant.

I’d log who the agent acted for, what was allowed and what happened. That would help me trace a failure to the relevant check and improve it. The checks still need to happen before sending.

Still thinking about

Unresolved questions.

  1. How can the application set reliable task limits without making the user write rules for every request?
  2. When are the records enough to check an action, and when do I need a person’s judgement?
  3. How do I catch costly mistakes without asking for so many approvals that people stop reading them?

Sources

13 references and further reading.

  1. My NDC Copenhagen talk · Your AI Agents Are Overprivilegedyoutube.com
  2. Confused deputy · Norman Hardy, 1988en.wikipedia.org
  3. EchoLeak in Microsoft 365 Copilot · CVE-2025-32711nvd.nist.gov
  4. The GitHub MCP toxic agent flow · Invariant Labsinvariantlabs.ai
  5. ForcedLeak in Salesforce Agentforce · Noma Securitynoma.security
  6. CaMeL, “Defeating Prompt Injections by Design” · Sections 3–5 and 8arxiv.org
  7. Scope minimization · MCP security best practicesmodelcontextprotocol.io
  8. Excessive Agency · OWASP guidance on permissions, approval and monitoringgenai.owasp.org
  9. Rich Authorization Requests · RFC 9396, Sections 2 and 9rfc-editor.org
  10. Introducing System One Models and Jev · TypeSafe AItypesafe.ai
  11. Introducing Clef · Cloudflare, 1 October 2026blog.cloudflare.com
  12. Jev 1.13 limitations · Adversarial contentdocs.typesafe.ai
  13. For every action, the right access: defining auth for agents · Keycard, 8 October 2026keycard.ai

A journal, not the last word.

What am I missing?

If you spot a mistake or have tried this in a working system, email me. Corrections that change the argument will appear here with a date.

Reply by email Discuss on LinkedIn Discuss on X
← All 10 piecesBack to the beginning ↑

Keep following the thread