Blog
Research
Why an agent sandbox still needs a policy engine
NVIDIA OpenShell controls which tools an agent may reach, not necessarily whether each use fits rules peculiar to some organisation; plugged into OpenShell’s existing hook with no change to OpenShell, TRACE stopped every violation of those rules in our test, before any credential was used.
5 min read

An agent does useful work only through what it may touch: files, tools, and the network. Each of those grants is also an avenue for harm, for a mistaken or hijacked action carries whatever authority the agent holds. Sandboxing has become the standard answer. Claude Code and Codex both offer sandboxes that limit the agent’s file writes and network destinations, so that a harmful action does not inherit the host’s full authority [1, 2].
The decision OpenShell leaves open
NVIDIA OpenShell, the sandbox we use throughout this post, approves the tool and the destination but not necessarily the use. It is an agent sandbox runtime that isolates the agent’s execution and checks every outbound connection against a policy [3, 4]. For network calls, that policy can match the destination, the HTTP method and path, and the MCP tool name. Nonetheless, it does not match the tool’s arguments, which its own validator reports as not yet supported, and it keeps no state from one call to the next [5].

Figure 1. Above the dashed line, what OpenShell’s native policy checks on an isolation request; below it, what is left open.
Approving a tool is therefore half a decision. The other half, whether this particular use fits the organisation’s rules, needs the arguments and the earlier calls. At one extreme, the tool is left to validate its own inputs. At the other, the sandbox grows until it judges content and history as well as identity, a direction OpenShell may partly take [5]. In between sits a policy engine beside the sandbox, reading the arguments and the history where the sandbox already inspects the call. This post makes the case for the middle.
Recent research from Pennsylvania State University, Dartmouth College, and Université Montpellier shows how the first extreme fares. The study examined tools exposed over MCP and reports 253 verified bugs in 118 of 273 tested servers [6]. Among the failures are tools that accept inputs their own advertised restrictions forbid, and a prompt injection can produce exactly such inputs. These are bugs in the tools, not an OpenShell exploit. A sandbox that admits the tool, however, admits those inputs too, for it only matches the tool’s name and not what the call carries.
An incident-response agent with the sandbox alone
Consider an agent investigating a suspected compromise. It must read machine records and isolate affected machines from the network, and isolation is the consequential half of the task, since cutting off the wrong machine interrupts a service the organisation depends on. We built a controlled incident-response experiment around this task.
The task, which we call the job, permits reads and Full isolation of machines a, b, and c; machine d and Selective isolation fall outside it. On top of the job, the organisation adds three rules of its own: never isolate critical server b automatically, read a machine before isolating it, and isolate each machine only once (Figure 2). The job is the permission; the rules are the conditions on using it.

Figure 2. The job, with critical server b inside it and machine d outside, and the three organisational rules beneath.
The set-up has three parts (Figure 3). The agent runs inside OpenShell. A test API outside the sandbox returns machine records and accepts isolation requests; it needs a credential (its API key). Our credential proxy, outside OpenShell, holds that key, and every call to the API passes through the proxy. The isolation tool names the machine and the mode in its machine_id and isolation_type arguments, and the proxy checks both against the job before it attaches the credential.

Figure 3. Solid arrows follow a call within the job through the proxy to the test API; the dashed red path carries the refusal of a call outside it back to the agent.
We run this set-up in two configurations. We call the first OpenShell: the sandbox with our proxy and nothing else. OpenShell’s supervisor controls which tools the agent may call, our proxy checks the job, and nothing on the path checks the three rules before a call reaches the API. We call the second OpenShell + TRACE. It differs in two respects: TRACE checks each call at OpenShell’s existing hook, and our proxy requires TRACE’s permit, an approval for that exact call, before it attaches the credential. The next section describes both.
The agent makes the same ten calls in each configuration, a sequence we constructed to mix permitted work with violations of the three rules. Figure 4 lists them with the outcome of each in both configurations and follows any call, step by step, on its way to the API.
Figure 4. Select a configuration and a scenario, then press Next step; the table marks the current call.
Read the OpenShell column of Figure 4 first. The API accepts the isolation of unread c at call 5 and of critical server b at call 9. Both fit the approved tool and the job, so neither OpenShell’s supervisor nor the proxy has grounds to object. Calls 4 and 7 also arrive at the API, and only the API’s own refusal of a repeat stops them. The organisation’s rules need a decision point that can read the machine named in the call and the calls that came before it.
TRACE as an add-on at OpenShell’s existing hook
TRACE, Cogent’s policy engine for tool calls, supplies that decision point. It checks each tool call against written rules the organisation owns; in this integration it answers allow or deny from deterministic rules alone. In particular, it reads the two things OpenShell’s native policy does not: the call’s arguments, and the calls that came before it in this sandbox. OpenShell already exposes a middleware hook, a point at which an external service inspects each permitted request before any credential is used [7], and TRACE attaches there (Figure 5).

Figure 5. TRACE sits outside the OpenShell box and attaches at the supervisor’s hook; the footer lists the two configuration entries.
The key property of the integration is that nothing inside OpenShell changes. On the OpenShell side, the addition amounts to two configuration entries and no code changes: one entry registers the middleware hook with the gateway, and the other binds it to the API endpoint in the sandbox policy. The rules live with TRACE, separate from both the sandbox and the proxy, so the organisation revises them without touching either, and TRACE records the rule behind each policy denial. Where OpenShell injects the credential itself, a deny at the hook already withholds it; our proxy is a second check at the credential, independent of the hook.
The policy we gave TRACE is short. Specifically, it repeats the job limits and adds the organisation’s three rules. OpenShell’s supervisor asks TRACE at the request hook. If TRACE denies, OpenShell returns its own HTTP 403 to the sandbox, and no credential is attached. If TRACE allows, it issues a permit for that exact call, valid for five seconds and authenticated with a key the sandbox never sees; our proxy checks the job and requires that permit before it attaches the credential. TRACE writes each allowed call to that sandbox’s history; with OpenShell + TRACE selected, Figure 4 shows these entries as markers. After the API responds, OpenShell’s response hook carries the proxy’s execution report back to TRACE, which updates that entry.
Three violations stopped before any credential was used
The comparison on call 9 is the heart of the result; Figure 4 follows it step by step in either configuration. With OpenShell, the isolation of critical server b reaches the API with the credential and is accepted. With OpenShell + TRACE, the critical-server rule matches b at the hook, OpenShell’s supervisor returns 403, and the credential is never used (Figure 6). Each configuration ran twice, with a fresh sandbox, empty history, and fresh API state; both runs gave the same outcome.

Figure 6. Call 9 in both configurations: on the left it reaches the API and b is isolated; on the right OpenShell returns a 403.
The OpenShell + TRACE column in Figure 4 shows the same sequence with TRACE in place. TRACE stops the three calls that break the organisation’s rules: the repeat on a at call 4, the isolation of unread c at call 5, and the isolation of critical server b at call 9. Calls 2 and 10 are refused in both configurations, by our proxy alone in the first and by TRACE in the second. Call 7, the isolation of c after its read, is now accepted, for the denied attempt at call 5 never reached the API. Isolation requests that reach the API therefore fall from five to two in this constructed sequence, and the two that remain are the permitted isolations of a and c.
Two notes on scope. TRACE’s history is per sandbox and lives in one process: a restart clears it, and other sandboxes do not share it. By default, OpenShell blocks requests on middleware failure [7]; for one test only, we configured the hook to forward them and made TRACE’s middleware fail. OpenShell then forwarded all ten calls without permits, and our proxy refused every one: without TRACE’s permits, the proxy lets nothing through. The test covers a middleware failure, not theft of the shared key behind the permits.
A second case: the URL sent to an approved remote tool
TRACE can also read what an approved tool is about to receive. We tried this on the screenshot-page tool from the same study’s webpage-screenshot-mcp server, which checks only that a URL parses before passing it to a browser [6]. In the set-up we modelled, the tool’s server and its browser sit on a remote host outside the agent’s sandbox. OpenShell sees the agent’s request to the server; it cannot see what the browser then fetches. A screenshot of a public page should never reach files or private addresses on that host. This is the familiar server-side request forgery problem, wherein a server is tricked into fetching what its caller could not reach directly.
The ten URLs we chose include file paths, loopback addresses in several spellings, and the cloud metadata address (Table 1). We tried two rule forms: the URL text rule reads the raw URL text, which the current integration already carries, and the URL classifier rule reads a parsed scheme and host, which only our test harness supplied (Figure 7).

Figure 7. Two URLs at the hook: the file URL is denied and never sent; the short loopback spelling passes, and the remote browser can resolve it.
Supplied URL scheme or host | URL text rule | URL classifier rule |
|---|---|---|
| Denied | Denied |
| Denied | Allowed |
| Allowed | Allowed |
DNS name resolving to | Allowed | Allowed |
| Allowed | Allowed |
Table 1. Ten URLs against the two rule forms; every Allowed cell is a miss.
Both rules catch file URLs and the standard loopback names, and the text rule also catches 0.0.0.0. Both miss the other spellings of 127.0.0.1, DNS names that resolve to it, and the cloud metadata address. A stronger rule could catch those spellings and the metadata address, for both are literal text in the URL. The key limit is that the hook sees the URL and not the fetch: the remote host determines the final destination, through name resolution, redirects, and the loaded page’s own requests. Connecting TRACE there is future work.
Two layers rather than a sandbox that does everything
Sandboxes have come a long way, and agents need them. OpenShell gives the agent an isolated environment and controls which tools it may reach. What it does not do, at the versions we tested, is judge each use of an approved tool. TRACE does that from the hook OpenShell already has, with no change to OpenShell, and with rules the organisation revises without touching the sandbox or the proxy. In our ten-call test, the combination caught what the sandbox alone let through.
We contend that the sandbox should not try to do everything. It should enforce. A policy engine beside it should decide, from the arguments and the history, whether this call fits the rules, and the sandbox should act on that answer as it acts on its own policy.
NVIDIA draws the same line. Its Agent Policy Fabric [8], open-sourced in October 2026, decides requests at the same hook with Cedar rules over the tool’s arguments, and OpenShell enforces the answer. It keeps no record of earlier calls, however. In our test, rules over earlier calls stopped calls 4 and 5, and that history is the part TRACE adds.
Our focus is to bring this layer to other runtimes with the same light touch: an existing hook, configuration rather than code, and rules that stay with the organisation. If OpenShell adds native argument matching, we intend to use it; its open proposal still defers history-dependent policy [5]. Shared history across sandboxes, checks during execution, and OpenShell’s proposed session hooks [9] are next on our list. OpenShell enforces, TRACE decides, and neither has to do the other’s job.
References
Anthropic, Beyond permission prompts: making Claude Code more secure and autonomous.
OpenAI, Running Codex safely at OpenAI.
NVIDIA OpenShell, tested commit; native policy and hook behaviour checked against 16 October 2026 source.
NVIDIA, OpenShell overview.
OpenShell, policy validator, policy schema, and Cedar policy proposal, which defers history-dependent policy.
ToFu: Exploiting and Fuzzing the LLM-Orchestrator-Tool Trust Boundary, October 2026. Paper with institutional affiliations.
OpenShell, supervisor middleware.
NVIDIA, Agent Policy Fabric, source checked on 22 October 2026.
OpenShell, proposed HTTP request and response session hooks, open at the 16 October 2026 source check.







