Gene's Field Notes
A glowing CRT terminal displaying aws sts get-caller-identity output in a dark room.
← All Posts

The Authorization Layer AI Agents Actually Need

AI agents inherit developer credentials by default. Here is how to give agents a separate identity, enforce a permission ceiling with Cedar and Amazon Verified Permissions, and make the ceiling unbreakable even by admins.

Gene WrightGene WrightMay 27, 202612 min read

Only 1% of organizations have fully implemented just-in-time (JIT) privileged access. Ninety-one percent run on standing access. Those same organizations are now running AI tools in their developer environments, and those tools are inheriting whatever standing access their developers already have.

Nobody decided that was acceptable. Nobody decided it wasn't. It just happened because the default is inheritance.

This post is about changing that default, specifically for the AI agents and MCP (Model Context Protocol) servers you build and deploy against internal systems. The architecture is not theoretical. I built a reference environment, ran it, and tried to break it. Here is what I found.


The Old Frame Does Not Fit

The standard answer to developer access problems is network segmentation. Isolate the machine, restrict the network, apply zero trust principles. That works for servers because servers have defined roles and static access patterns. A web server needs to reach a database on port 3306 and nothing else. You can define that rule, enforce it, and monitor deviations.

Developer machines break that model. Developers need legitimate access to nearly everything depending on what they are doing: source control, CI/CD, staging environments, internal APIs, documentation, ticketing, logging, and occasionally production. The access pattern changes constantly. Least privilege defined statically ends up being nearly everything, which is not least privilege at all.

Zero trust does not solve this either. Zero trust verifies that it is really you requesting prod database access. It does not help if you always have prod database access.

The problem is not authentication. It is that nobody has defined what the agent is allowed to do, separately from what you are allowed to do.


The Agent Inherits Your Access

When a developer runs an AI tool on their workstation, that tool inherits the developer's credentials. Not because anyone decided the agent should have that access. Because nobody decided it shouldn't.

This is not a theoretical risk. In December 2025, researcher Ari Marzouk disclosed 30+ vulnerabilities across 10+ AI coding products, a disclosure he named IDEsaster. The GitHub Copilot VS Code flaw (CVE-2025-64660) allows code execution when a developer opens a malicious repository, making tokens, signing keys, and build pipeline credentials accessible to an attacker. One hundred percent of tested AI IDEs were found vulnerable.

The agent is on your machine, running as you, with your credentials. When something goes wrong, the blast radius is your access scope.

Credential model: before vs. after

Before — Default Inheritance

DevelopercredentialssamecredentialsAI AgentsamecredentialsInternal Systems

After — Separate Principals

DeveloperAI AgentpersonaltokendedicatedidentityInternal Systems

Instructions are not a security control. Telling the agent not to access sensitive data relies on the model respecting the instruction every time, under every prompt, including adversarial ones. That is not a ceiling. It is a suggestion.


Agents Are First-Class Principals

The fix starts with a conceptual shift: agents are not extensions of your identity. They are separate principals that need their own credentials, scoped to what the agent actually needs.

In practice, this means:

  • Cloud provider: A dedicated role attached to the agent process, separate from your SSO role
  • Source control: A scoped token with access only to the repos and actions the agent needs, not your personal token
  • Secrets manager: A dedicated service identity scoped to the secrets the agent needs, injected at runtime, not written to the environment permanently
  • Database: A read-only service account for the agent vs. read/write for the developer

Consider what this looks like without the separation: a developer's Cursor IDE running with their production database credentials because nobody provisioned a separate service account for it. The IDE has access because the developer has access. That is the default today.

In practice, the separation comes down to where each credential lives. The agent's token goes in a project-scoped configuration file. The developer's token stays in the user-level shell environment. An agent process picks up only what is in its scope and cannot see the rest by default.

The honest residual is that this separation relies on developer discipline. Nothing prevents a developer from putting their personal token in that project-scoped file to test something quickly, and that single action collapses the isolation silently. What it does not collapse is the authorization ceiling enforced at the policy layer. The policy engine still denies the agent access to sensitive fields regardless of which token it presents. What breaks is the audit trail and the blast radius: the access log now shows the developer's identity instead of the agent's, so agent-specific behavior becomes invisible, and if something goes wrong the developer's broader access is now in play. Credential separation and the permission ceiling are two separate controls doing two different jobs. The ceiling is the hard technical guarantee. The credential separation is defense in depth that protects your audit trail and limits blast radius if things go sideways. Name that distinction in your runbooks rather than treating them as a single thing that either holds or fails together.


The Control Plane Security Teams Have Been Missing

Separate credentials are necessary but not sufficient. You also need a place to define, enforce, and audit what each principal can do, including a ceiling that developers cannot exceed when configuring their own agents.

This is where existing tooling falls short.

Cloud IAMs tell you what a principal can call at the API layer. They cannot enforce what a principal can see inside a call that the IAM has already permitted. Field-level access, row-level access, and agent permission ceilings all require the application layer.

A CASB (Cloud Access Security Broker) sits at the network or proxy layer. It sees "this principal is calling this endpoint." It cannot distinguish between an AI agent querying a data store and an engineer querying the same data store if both calls look like the same HTTP request to the same endpoint. The CASB log says a principal called an API endpoint. An application-layer policy engine log says which principal attempted which action on which resource and was denied, with the specific policy recorded. That distinction matters when you are tracing a prompt injection attempt after the fact.

An application-layer policy engine sits inside the application itself. Before every data access, the application makes an explicit authorization check, passing the principal, the action, and the resource to a central policy engine. The engine evaluates that structured request and returns a permit or deny. Every decision is logged, including the denials.

An application-layer policy engine answers three questions centrally that nothing else currently answers together:

  1. What can this human do?
  2. What can this human's tools do?
  3. What is the maximum permission ceiling a human is allowed to grant their own agent?

The third question is new. No standard mechanism currently prevents a developer from giving their MCP server the same permissions they have. A centrally enforced policy ceiling changes that, and it has to live at the application layer to work.


The Scenario

My theory was that a Cedar policy enforced by AVP (Amazon Verified Permissions) would hold even when a developer with full administrative access to AVP tried to grant the agent more access. I built a reference environment to find out.

A security incident knowledge base: a KB (knowledge base) agent and a security engineer portal running as sibling processes inside a single container, on the same OS, sharing the same network namespace.

Two principals. One incidents table. Every data access request calls AVP's IsAuthorized API before any query runs. AVP evaluates the Cedar policies and returns a permit or deny. The application acts on that decision. No query runs on a deny.

The KB agent has its own service identity, its own credentials, and its own Cedar principal. The security engineer has a separate identity with broader access. The KB agent's credentials were stripped from the engineer's process environment at launch, and the engineer's credentials were stripped from the agent's process environment. Same OS, same container, different process environments.

I configured AVP to allow the KB agent to read incident titles, severity, and status. Sensitive fields, affected customers, internal notes, and remediation details, were explicitly forbidden at the policy layer. The security engineer could read public fields by default and request JIT elevation for the sensitive ones.

Authorization architecture

WORKSPACE CONTAINER — SAME OS, SAME NETWORKKB Agentprincipal: kb-agentSecurity Engineer Portalprincipal: security-engineerIsAuthorized(kb-agent, read, incidents)IsAuthorized(security-engineer,read, incidents_sensitive)Amazon Verified PermissionsCedar policy evaluationALLOW / DENY + logIncidents Tablepublic: title, severity, statussensitive: customers, notes, remediation

The Ceiling Held

Then I tried to break it.

The engineer had full administrative access to AVP, so I used the AWS CLI to add a permit granting the KB agent access to sensitive fields. The AWS API accepted it. AVP returned a policy ID. By every measure, the policy existed and was active.

I went back to the KB agent and asked for the sensitive fields.

User: What are the affected customers for incident 1?

KB Agent: I don't have access to that information. Affected customer
details are not within my current authorization scope.

The agent had no idea a new policy existed. It just could not reach the data, so it said so.

The security team's forbid policy, defined in Terraform before any of this started, overrides any permit at evaluation time. That is how Cedar works: explicit deny always wins, regardless of what permits exist or who added them. The engineer could not grant what the ceiling did not allow, even with full administrative access to AVP.

// This forbid cannot be overridden by any permit policy, regardless of who adds it.
forbid(
  principal is AgentIdentity::Agent,
  action == AgentIdentity::Action::"read",
  resource == AgentIdentity::DataStore::"incidents_sensitive"
);

Cedar policy evaluation: forbid overrides permit

Engineer adds permit:kb-agent → incidents_sensitiveAVP Policy Storepermit:kb-agent read incidents_sensitiveforbid:ANY agent read incidents_sensitiveboth policies evaluatedCedar Evaluationexplicit deny always winsDENYforbid overrides permitdecision logged with policy ID

One detail worth naming explicitly: the KB agent had no awareness of any of this. I stripped all AVP and Cedar language from the agent's system prompt, tool descriptions, and error messages. When a tool call failed because of a permission denial, the agent received a generic error and reported accordingly. The control is in the policy layer, not the model layer.

You cannot rely on instructing an agent to refuse. Instructions can be overridden by injection, jailbreak, or model drift. Policy cannot.

The KB agent in the reference environment is not a toy. It is a stand-in for the MCP servers already running in your developers' environments.


This Is the MCP Problem

If you have read about how MCP servers inherit developer credentials, this is the enforcement layer that closes that gap.

The MCP security best practices name this risk directly. The spec warns that MCP servers run with the same privileges as the client and recommends scope minimization as the mitigation. AVP is the enforcement layer that makes that recommendation structurally binding rather than advisory.

The architecture maps 1:1. Give each MCP server its own service identity. Put an AVP authorization check in the tool handler before any data access. Define a Cedar permission ceiling in your policy store that no MCP server can exceed regardless of what developers configure.

This pattern applies to servers you build or control, meaning the server code has to be written to read a dedicated service identity token and use it to authenticate against AVP. Third-party MCP servers that do not support explicit credential injection give you no clean place to enforce the separation, and you are back to credential inheritance by default. The MCP spec defines how servers are launched and how OAuth handles remote authentication, but it has no standard for machine identity at the server level. A spec-level credential injection mechanism would make agent-as-principal enforceable by default rather than by discipline, and that is a gap worth closing.

One boundary to name clearly. The MCP spec defines two transport types: STDIO servers running locally as a child process, and HTTP servers accessed remotely via OAuth 2.1. This architecture applies to the first category: service MCP servers you build and deploy against internal APIs and databases where you control the credential model.

For remote HTTP servers, OAuth handles authentication. It verifies that the caller is who they claim to be. It does not control what data the server can access once that call is permitted. AVP sits at the application layer, above the OAuth handshake, and that is where the ceiling is enforced.

For OAuth-delegated MCP servers, the kind that connect to Google Drive, Gmail, or Slack by authenticating as you, agent identity separation is an unsolved problem at the industry level. The user is the principal by design. There is no separate service identity to scope. NIST NCCoE's February 2026 concept paper on software and AI agent identity is pointing toward a solution, but the tooling does not exist yet.

For the servers you build and control, the tooling exists today.


What About OPA?

If your platform team already runs OPA (Open Policy Agent) centrally, the same pattern applies. Agent principals, ceiling policies, and decision logging all work in Rego.

One important difference: Cedar's deny-overrides-permit is enforced at the language level. You cannot accidentally write a Cedar policy that lets a developer override a security team forbid. In OPA/Rego, deny-overrides-permit is a convention you implement in your policy logic. At enterprise scale with a platform team owning the policies, this is a manageable discipline requirement. In smaller teams, it is worth auditing explicitly.

The pattern is the point. AVP and Cedar are the reference implementation in this post because that is what I built and tested. If you are running OPA, the architecture maps directly.


Start Here

The reference environment is available at github.com/ewright3/avp-agent-identity. Clone it, run the demo, and try to grant the KB agent access to sensitive fields using a Cedar policy. Watch AVP deny it anyway.

The ceiling is not a code comment or a system prompt. It is a Cedar policy. That is the difference.

If you are building MCP servers or AI agents against internal systems and you have not yet separated agent credentials from developer credentials, that is the first step. The access your agents inherit right now is the access they will use if something goes wrong.

Subscribe to Field Notes for the MCP governance series, covering how to operationalize these controls across a development team.

Stay in the loop

2-3 field notes a month on cloud security, AI governance, and what's actually happening in regulated environments. No roundups, no filler.

Work with me

I take on a limited number of consulting engagements: cloud security architecture, security posture assessments, and compliance readiness for teams moving fast in regulated environments.

Learn more →