← iamidentity.ai/blog
IAMIDENTITY.AI
Agent Identity Defense in Depth Policy as Code IBM Agent Identity IBM (HashiCorp) Vault OPA

Beyond the Sandbox: Governing What AI Agents Can Do.

A working prototype for reviewing identity, secrets, and guardrails together, managing policy changes as code, and exposing drift before audit does.

Robert Graham 12 October 2026 ~15 min read
Agent Policy Lifecycle hero. Five stages, Author, Review, Bundle, Promote, Observe, above three enforcement cards for identity scope, secret scope, and guardrails, and two dark panels for Shared Signals and the right-size loop.

I have not posted in a while. I was down in a rabbit hole of agent sandboxing and policy enforcement, specifically adding on OPA (pronounced OPAH) and rego. Thank goodness for AI to help with code. I am only now climbing out with alot learned and alot to say.

What I am going to show is a real, working build. However, I would consider this still highly conceptual. It comes from months of conversations with customers and people across the industry. The idea is a single management plane for the policies that govern agents. It does not have to be a plane for IBM Agent Identity only, or Vault only. The identity side could be Okta, Ping, or Microsoft Entra. The authorization side could be Cedar, the language behind Amazon Verified Permissions, or OPA, or a relationship engine like OpenFGA. Whatever you run, you are going to end up with disparate policy management, teams each with their own requirements and views, overlap nobody owns, and confusion about which copy is the real one.

When compliance or audit asks you to show the controls around your agents, will you be able to? Will you be able to prove these are in sync because automation keeps them in sync, and not because a person remembered to, or better yet, is the creation and ideology of the policies in sync with the CISO, Security teams, and compliance teams? Who is the ultimate owner? Who is going to stand up in front of a change review board and answer the questions.

I do not want to digress from the policies themselves, but this is a new world. Agentic AI is nearly limitless, and we have a chance to start fresh, build with security in mind, and get it right the first time. Agents can and will break out of the sandbox. I have seen it and I have shown it. The industry has shown it. The walled garden is not the garden of Eden. We cannot rely on a sandbox or a network layer. We need full control over our agents, which means defense-in-depth.

01What the sandbox can and cannot do

I spent weeks inside an open source agent sandbox runtime, on a laptop, on a cloud host, and on OpenShift. One thing in that stack is right; the sandbox never holds a credential value. Out of the box the runtime kept credential values in its own gateway database; you handed it the literal secret and it stored it. The Vault credential driver changes that. The runtime stores only a reference to a Vault path, the driver resolves that reference against Vault at the moment a sandbox is created, the sandbox's environment carries an opaque placeholder, and the egress proxy substitutes the real bearer onto the wire only as a request leaves. Rotation is a write to Vault, Vault stays the single system of record, and inside the sandbox, before egress, there is nothing to exfiltrate.

Then I found two things that changed how I thought about sandboxes.

The first was the default policy. A fresh sandbox from the community base image already carried network groups for every common agent CLI, the package index, and the vendor's own inference endpoint, before I applied anything. This was not a deny-all by default.

The second was the subagent. The demo has a parent agent and a read-only agent it delegates to, and what I needed to enforce is the subagent to run in its own sandbox under a stricter policy: two read-only gateway ports, a read-only filesystem, nothing else. That policy file fine it was correct. But the subagent of the agent harness, invoked through its task tool, and the task tool does not start a new container. It runs the subagent in the same process, in the same sandbox, under the parent's network policy. During a normal run the policy was never applied to anything. What was really enforcing the subagent was the harness's tool allowlist and the hard-cap deny on its identity at token exchange, not the sandbox. Which is exactly why you want a token exchange (rfc8693) that includes INTENT (RAR rfc9396) with an applied enforceable policy, like we do with IBM Agent Identity. Adhering to standards WILL SAVE YOU. There is a reason for 8693 and 9396. To enforce and base a policy on intent of the agent gives you a true scoped OBO. Not a made up OBO with only adhering to partial 8693, like you will find in other IdP's.

The fix was easy, tool on the mcp, delegate_research, that spawns a separate sandbox per delegated task, with the enforced policy and only the read-only credential. To the parent its just another tool call. It now runs in a different container, under a different network policy, as a different identity, and enforced policy with proper AAA. Authentication, Authorization, and Accounting.

Both findings point to the same separation. Isolation determines where the agent process can run and how far a breakout can spread. Authorization determines whether this identity may use this tool for this action at this moment. To a sandbox, a harmless read and a destructive write to the same permitted host can look like the same allowed traffic. It cannot judge intent or business impact. Authorization handles that decision, but it does nothing to stop a kernel exploit or contain a compromised process. You need both, and each has to be verified. The statement “it runs in a sandbox” stopped before the authorization layer was even tested.

Contained, not detected

None of these layers inspect prompts, so nothing here detects prompt injection. The protection is downstream of the attempt: an injected instruction still has to pass through an identity whose grant only covers certain actions. This is mitigation, not detection, and the distinction matters.

So the stack ends up with five places where policy lives: the sandbox's network and file policy, the gateway that enforces per call, the identity service that decides at token exchange, the vault that issues secrets against the token, and the signals that revoke all of it. Five surfaces, four languages, and in most shops, four teams. That is the problem the rest of this post is about. 

02What the industry is measuring

Researchers at the University of Oxford and the UK AI Security Institute published a sandbox-escape benchmark in March 2026. The test gave frontier models shell access inside a container and asked whether they could reach a flag on the host.

The findings:

Takeaway: this was not evidence that models can defeat every sandbox. It showed them exploiting familiar container-security weaknesses. Give a model enough room to keep trying, and those configuration mistakes become opportunities.

On the identities behind the agents: the Cloud Security Alliance and Aembit surveyed 228 IT and security professionals in January 2026, and a second CSA survey with Zenity covered 445 more.

74%
say agents routinely receive more access than their tasks require
CSA / Aembit, n=228
68%
cannot clearly distinguish agent actions from human activity
CSA / Aembit, n=228
47%
had a confirmed security incident involving an AI agent in the prior twelve months
CSA / Zenity, n=445
31%
have formally adopted AI agent governance policies
CSA / Zenity, n=445

The identity findings reveal another gap:

My takeaway: when agents share an account, their permissions and activity become harder to separate. A dedicated identity gives you a clearer basis for defining what each agent may do and holding it accountable for what it actually does.

Gartner put a forecast on where that leads in April 2026, and tied it explicitly to agentic AI built on MCP.

Share of enterprise GenAI applications with security incidents
Gartner forecast, April 2026. 2025 observed against the forecast year.
10% 20% 30% Five or more minor incidentsper application, per year 9%2025: 9% of applications 25%2028 forecast: 25% of applications One or more major incidentsper application, per year 3%2025: 3% of applications 15%2029 forecast: 15% of applications
2025Forecast (2028 minor, 2029 major)
Data table
Measure2025ForecastYear
Five or more minor incidents a year9%25%2028
One or more major incidents a year3%15%2029
Figure 1. Gartner, 9 April 2026. Minor incidents rise from 9% of enterprise GenAI applications in 2025 to 25% by 2028; major incidents from 3% to 15% by 2029.

Where OWASP maps to this build

Two risks in the OWASP Top 10 for Agentic Applications, published in December 2025, map directly to the controls in this post:

Takeaway: a sandbox alone does not address either risk. The controls below check whether the identity's grant covers each requested tool and its tags. That constrains what the agent can do, but it should not be confused with preventing every misuse of an otherwise permitted action.

03Policy is not one thing

The first mistake I see large enterprises make is treating the agent's policy as a single document with a single owner. There are three layers, and each already has a natural home in the organization.

Identity scope is what an agent may do, on behalf of whom, under what conditions. That is IBM Agent Identity: the Agent Registry record the agent lives in, the Authorization Details Type that carries the Rich Authorization Request payload, and the access policy Agent Identity evaluates at token exchange. 

Secret scope is what a given agent identity can pull from Vault, along which paths, for how long. That is infrastructure, owned by platform or security engineering, and it looks like every other Vault policy they already manage.

Once Agent Identity completes the policy checks based on the RAR, two checks remain at the gateway: whether the requested tool is registered and tagged, and whether each delegation remains within its parent's permissions. A small OPA package enforces those checks because Agent Identity cannot make them on its own in this design. Security governance owns this layer and the tag vocabulary, providing an independent guardrail against either of the other policy layers quietly expanding an agent’s access.

Once the layers are separate, the ownership question answers itself. Nobody new gets hired. The people who own access decisions for applications own them for agents.

Three layers, three owners, one pipeline Identity scope IBM Agent Identity: Agent Registry, Authorization Details Types, access policy at token exchange Owner: app / business Secret scope IBM (HashiCorp) Vault short leases, narrow paths bound to the Agent Identity identity, every read audited Owner: platform engineering Registration gate and delegation check in Git, enforced at the gateway by a small OPA package untagged tools deny Owner: security governance One pipeline: authored in Git, reviewed like code, bundled, promoted, observed
Figure 2. Three layers, three owners, one pipeline. The registration gate and delegation check sit underneath both other layers and cannot be overridden by either.

What is glaringly missing is a place where those three owners could see and manage all three layers at once.

04One Policy Plane, Multiple Enforcement Layers

Policy Studio grew out of my work building and managing policies across these layers. It is a workbench behind single sign-on that brings the policies into one place for review, while keeping the repository as the source of record.

The policy library includes:

A reviewer can:

A human via the policy studio kicks off the job, merges the pull request, CI validates it again, and a named operator step pushes the change to the tenant and the gateways. Drafts stay in the browser until the reviewer chooses to send them. It is a private prototype of mine, not an IBM product, and the hosted copy runs on a Worker that gates every request behind IBM Verify sign-in and a container that clones the policy repository fresh and sleeps when idle.

Policy Studio workbench. A sidebar lists the policy library with OPA, Agent Identity and Vault tabs. The main panel shows one request flowing through OPA guardrails, IBM Agent Identity and IBM (HashiCorp) Vault, with a request denied branch under OPA.
Figure 3. The workbench. One request, three policy layers, and the library of 52 policy files on the left.

Here is why I built it: Agents were getting tools faster than policy will be created. The rules were spread across the IBM Agent Identity, Vault, and OPA files on a host, written in different languages and in terms of individual tool names. A change in one layer left no trace in the others, and nobody could answer "what does this agent get to do today" from one screen.

The flow below is the Studio's own animated diagram of itself. The reviewer never holds a GitHub token, the container never holds a Verify secret, and GitHub is the system of record for all three enforcement points.

HTTPS, session cookievalidated requests,never the GitHub tokendraft pull requestas Robert Grahamoperator appliesgenerated policies(dry run, snapshot,read back)gateway config and OPA bundlepolicies(export and plan)read-only reader client,Tenant viewtoken exchangewith tags on the wireReviewer (browser)signs in, edits, validates,opens a PRCloudflare WorkerIBM Verify sign-in (OIDC +PKCE, admin group), sessions,read-only tenant readsStudio containerpolicy repo checkout,validators (OPA, Verifyschemas, Vault HCL, tagrules), PR flowGitHub repositoryagent-policy-lifecycle:system of record, CI validateon every PR, human mergeIBM Agent Identity tenantaccess policies andattributes decide at tokenexchangeSandbox hosteight gateways send tool tagsin the RAR; OPA guardsregistration and delegationIBM (HashiCorp) Vaultsecret paths and capabilities
Figure 4. Policy Studio flow. Pulses follow the eight connections from the reviewer's browser to the three enforcement points.

05Rules written in tags, policies generated from them

The OPA (OPAH) community learned this a few years ago and it transfers directly: never write policy against enumerated names. A rule that lists tool names by hand is for sure not the way to do it. So every tool in the registry carries exactly five tags, and every policy at every layer keys on those five values and nothing else.

# owner approves this file; policy keys on these five values only sensitivity : confidential action : read blast_radius : low owner : records-platform tenant : smt

A rule set is an ordered list of rules over tags, and a generator compiles each file into one CELx custom attribute per rule plus one access policy. This is the parent agent's whole rule set:

{ "rules": [ { "id": "no-sensitive-egress", "when": { "action": "egress", "sensitivity": ["confidential", "restricted"] }, "then": "deny" }, { "id": "approve-writes", "when": { "action": ["write", "execute"] }, "then": "mfa" } ], "default": "allow" }

Two rules; Confidential or restricted data never leaves through an egress tool, and every write or execute needs a push approval. A rule's when may only name the five tags; the validator rejects a tool name outright. Notice the INTENT is applied.

I use the gateway instance in the demo and put the tool's five tags into the RAR it sends, and Agent Identity echoes them back in the OBO token. Decoded from a live exchange, claims only:

"authorization_details": [ { "operationDetails": { "action": "jira_read", "issue_key": "KAN-3" }, "tags": { "sensitivity": "internal", "action": "read", "blast_radius": "low", "owner": "platform-demo", "tenant": "demo" } } ]

With the generated policies bound, the decisions came back the way the rules read: a plain read allowed, an egress of confidential data refused with access_denied, and a write triggering the step-up. The legacy, name-keyed attributes were deleted the same afternoon, after a snapshot of every live object was written to disk and read back.

06What audit gets to see

Back to the question I opened with. When audit asks for the controls around the agents, the answer in this build is a commit hash and three outputs.

The commit hash is the repository at a point in time, and CI at that commit is green: the tag vocabulary on every tool, the Verify objects against their schemas, the OPA bundle rebuilt and compared, the Rego test suite, Vault policy formatting, the grant file's shape, a replay of sample decision logs against the committed bundle, the deny-only proof, and the tag rules compiled and compared. A policy change that breaks any of them does not merge.

The first output is the plan. A script reads the live tenant and reports, per object, whether it matches the repository, has drifted, is missing, or exists in the tenant with no file behind it. The Tenant view in the Studio is that plan on a screen. The second is the decision log, which is also the input to right-sizing. The right-size script reads thirty days of decisions and prints, per agent, what was granted against what was used:

=== spiffe://.../agents/records-reader === actions max_sensitivity egress granted read confidential false observed read confidential false proposed read confidential false tools observed: records/get_record, records/get_record_detail, records/list_records (76 calls) denies: 4 (action-not-granted: 4) === spiffe://.../agents/records-editor === actions max_sensitivity egress granted read, write confidential false observed read confidential false proposed read confidential false

The third output is the revocation path, because a control you cannot turn off is not a control. Suspending the agent's record in the Agent Registry disables its linked OAuth client immediately; a fresh mint while suspended comes back CSIAQ5293E Unable to match an active agent identity, and restoring the record lets the mint succeed again. What I added since the first draft is the other half: a poller that forwards the suspension to Vault. In the rehearsal on 29 September, the record was suspended at 17:52:20, Vault's registration for that agent was deleted and its entity disabled at 17:53:12, fifty-two seconds later, and the restore took twenty-six. The parent agent was untouched throughout and its reads kept returning 200. Agent Identity already refused the suspended agent at token exchange; this makes Vault's own copy of the agent's status agree with Agent Identity's, which is the defense in depth I keep asking for.

Key rotation got the same treatment. The subagent presents a SPIFFE JWT as its actor token, and Agent Identity validates it against Vault's public keys. A cached copy of those keys went stale once and broke delegation. Vault now serves its own keys through a path rule that exposes exactly one URL and nothing else; a forced rotation produced a new signing key, and the subagent's exchange succeeded with it thirteen seconds later.

07The model proposes, the repository decides

The newest view in the Studio is the one I was most unsure about. You paste an MCP server's tools/list answer, name the agent that will use it, type the owner and the tenant, choose a model, and the Studio asks the model for a tags file per tool, the agent's Agent Identity rule set, a Vault policy, and sometimes a deny-only OPA rule.

AI Policy Builder form. A paragraph explains that every MCP server publishes its own tool list and the Studio keeps the pasted answer unchanged as the manifest. Fields for server name, an optional description, and the pasted tools/list JSON for a jira server with one tool, jira_get_ticket.
Figure 5. The AI Policy Builder. The manifest is pasted exactly as the server published it; the model never gets to change it.

The rules I held to make it safe to put a model in the loop are the same rules a human edit lives under. Owner, tenant and the target agent are typed by a person, never guessed. Each pasted tool is registered as a manifest exactly as served. Every draft runs through the same validators as a hand edit, and the OPA that validates model-written Rego runs with a capabilities file that removes http.send and the network functions, so a draft cannot call out. A draft that fails validation goes back to the model once with the error; one that still fails stays in the list with its error, and the pull request is blocked until it is fixed. Nothing is saved or applied. The drafts leave as one draft pull request that a person reviews.

What leaves the Studio for the provider is the pasted manifests, the tag vocabulary, a few example tag files, the agent's current rule set, an example Vault policy, and the gateway instance names. Tenant exports, secrets and session data never go. Each run writes one audit row, who, when, which model, a hash of the prompt and the token counts, and each user gets twenty builds an hour. I wired three providers; the validators treat every draft the same way.

Delivery view. A card titled A pull request, when you're ready, showing the selected policy guardrails/policy.rego with no unsaved changes and a Preview pull request button. Below it, a heading reads Local first, GitHub optional.
Figure 6. Delivery. Every change, human or model-drafted, leaves the same way: a previewed diff and a draft pull request.

08Per agent today, per profile next

The honest cost of this design shows up in Agent Identity. Every agent has a registry record bound to an Agent Identity app, and the agents that do token exchange each have their own Token Exchange app, whose policy pointer names that agent's access policy, whose rules read that agent's attributes. OPA already scales, because the grant file is data keyed by SPIFFE id. Vault is one file per role. Agent Identity grows by one app, one policy and a handful of attributes per agent, and at a thousand agents that is a console nobody can read.

The next model; Tools carry tags and grow with integrations. Profiles are a handful of shapes, read-only, write-with-approval, delegate-only, and grow only with business decisions. Agents are records: identity, owner, tenant, profile. Thousands of agents is fine because they are data. OPA resolves agent to profile to grant with the same engine and one more lookup. Agent Identity keeps the registry record and the Agent Identity app per agent, where suspension, rotation and audit live, and shares one Token Exchange app and one access policy per profile. Vault uses one templated policy per profile, with the profile carried as an identity group.

Today: per agentOPA already scalesIBM Agent Identity grows with every agentVault one file per roleToolmanifest plus tags.yaml(five tags)per agentAgentSPIFFE id, registryrecord, agent_id claimsharedpolicy.regoone universal policyper agentagents/data.json grantkeyed by SPIFFE id: maxsensitivity, actions, egresshostssharedDelegation checka subagent grant stays asubset of its parent grantper agentAgent Registry recordthe agent in Agent Identity, bound to its appsper agentAgent Identity appthe agent owncredentials, actortokenper agentToken Exchange appbound for someagentsper agentauthPolicy to access policythe agent access policy, for exampleParent-Policyper agentRules on CELx attributesattributes read the RAR tags the gateway sentsharedOAuth resource serveridentifies the caller by theagent_id claimper agentRolemaps the agent_id to one policyper agentHCL policyone file per roleRAR tagsSPIFFE idreadsparent and childboundboundauthPolicyrulesagent_idmaps toCost per agent: one Token Exchange app, one access policy, its attributes.Suspension today: parent via its Agent Identity app (suspend the record, Agent Identity refuses the actor mint, CSIAQ5293E);subagent via its bound Token Exchange app (SPIFFE actor, the exchange is refused).Next: per profileOPA same engine, one more lookupIBM Agent Identity share per profileVault one templated policy per profileToolstags; grow withintegrationsper agentAgentsrecords with a profile;thousands are finesharedProfilesa handful: read-only,write-with-approval,delegate-onlysharedpolicy.regosame engineper agentAgent recordagent to profilesharedProfile grantprofile to grant, one morelookupper agentRecord and Agent Identity appkept per agent: suspend, rotate, audit heresharedToken Exchange appone per profile; lists each member STS client;secret rotates per profilesharedAccess policyrule set per profileOption to exploreone shared app whose policy reads a profileclaim (needs a spike)sharedOAuth resource serveridentifies the caller by theagent_id claimsharedIdentity groupprofile carried as a Vaultidentity groupsharedTemplated policysecret/data/agents/<agent_id>/*plus profile serversRAR tagsrecordprofilereadsprofilememberauthPolicyoragent_idprofileAttribution moves from client name to actor sub and agent_id.The shared app lists each member STS client. Token Exchange secrets rotate per profile.Precondition: before sharing Token Exchange apps, move every agent to the Agent Identity actor mint,so suspension stays per agent.LegendTools and agentsOPAIBM Agent IdentityVaultone per agentshared across agentsoption to exploreprecondition
Figure 7. The policy model, from the Studio's own guide page. Today, per agent, live. Next, per profile, under review.

Suspension today works through whichever app the agent's actor token comes from. An agent whose gateways still present a SPIFFE actor relies on its bound Token Exchange app for that. Before any Token Exchange app is shared across a profile, every agent has to move to the Agent Identity actor mint, so that suspension stays per agent when the app no longer is.

09What the plane needs from any engine

I built this against IBM Agent Identity, IBM (HashiCorp) Vault and OPA because that is mostly what I have been asked to demo and talk about. The shape does not depend on them. To sit under one management plane, a policy engine or identity provider has to offer five things, and I would ask these of Okta, Ping, Entra, Cedar or OpenFGA before I believed a single-pane slide.

10Who writes the rules

The sandbox is one layer. The gateway is another. Identity decides, Vault issues, signals revoke. Each of those has a policy. Really no different than today with your IdP and your api gateway or your oauth rs or your secrets manager in AWS or Azure or Delinea..I brought it all together for a single view and enforcement of Agentic Agents.

Policy for agents is written by the same people who write it for applications, in terms of what a tool is rather than what it is called, stored and reviewed the way infrastructure code is, generated where it can be so the copies cannot disagree, compared against the live system on a screen anyone can open, and right-sized from the decisions the agent actually made. A model can draft it. A person merges it. Automation keeps it honest.

Who writes the rules? The same people who always did. Now they can see all of them at once.