Fraktional
Sign in
← BlogAI Agents
034June 2026

Agent Harness Architecture: Sandboxes, Egress Brokers, and Credentials That Expire.

A reference design for running autonomous agents against production systems: microVM isolation, an egress broker that scopes every outbound call, and short-lived credentials the agent never sees.

The design constraint

An agent that is useful can run code, call internal APIs, and touch real data. An agent that is safe can be assumed compromised at any moment, because prompt injection is unsolved and every document it reads is a potential instruction source.

Both statements are true simultaneously, which means the harness, not the model, is where safety lives. The design goal is straightforward to state and unusual to see implemented: an agent that has been fully hijacked should be able to do nothing you did not already decide to allow.

This is the reference architecture we deploy for that. Four layers: execution isolation, egress brokering, credential exchange, and action gating. Nothing here depends on the model behaving.

Layer 1: execution isolation

Agents write and run code. That code is untrusted, whether it came from the model's own reasoning or from an attacker steering it, and it must not run on shared infrastructure.

Containers are not the boundary. A container shares the host kernel; a kernel exploit escapes it. For code you did not write and cannot review, the isolation boundary should be a virtual machine.

The practical options in 2026:

ApproachBoundaryCold startNotes
Firecracker microVMHardware virtualization~125msThe standard for multi-tenant untrusted code
gVisorUserspace kernel~50msGood defense in depth, syscall compatibility gaps
Container + seccompShared kernel~10msNot sufficient alone for untrusted code

Managed microVM platforms (AWS Firecracker directly, Vercel Sandbox, E2B, Modal) get you the boundary without operating the hypervisor yourself, which is the right trade for most teams.

Whatever the substrate, the properties that matter:

  • Ephemeral. One session, one sandbox, destroyed at the end. No reuse, so no cross-session contamination.
  • No ambient credentials. No instance metadata access, no mounted service-account tokens, no environment variables with secrets. Block the cloud metadata endpoint explicitly; SSRF to 169.254.169.254 is how a sandboxed process becomes a cloud principal.
  • Bounded. CPU, memory, disk, and wall-clock limits enforced by the platform. An agent in a loop should hit a limit, not a billing alert.
  • Read-only base filesystem with a small writable scratch layer that dies with the sandbox.

Layer 2: the egress broker

This is the layer most implementations skip, and it is the one that turns a compromise into a non-event.

The sandbox gets no direct network access. All outbound traffic goes through a proxy that authenticates the session, checks the destination against an allowlist, and logs everything. The agent cannot reach the internet, cannot reach your internal network, and cannot reach a model API except through the broker.

# egress broker: the only route out of a sandbox
ALLOWLIST = {
    "github.com":            {"methods": {"GET"}, "paths": ["/repos/acme/*"]},
    "api.internal.acme.com": {"methods": {"GET", "POST"}, "paths": ["/v2/tickets*"]},
    "registry.internal":     {"methods": {"GET"}, "paths": ["*"]},
}

def handle(req, session):
    rule = ALLOWLIST.get(req.host)
    if not rule:
        audit(session, req, "DENY_HOST")
        raise Denied(f"{req.host} not in allowlist")
    if req.method not in rule["methods"]:
        audit(session, req, "DENY_METHOD")
        raise Denied(f"{req.method} not permitted on {req.host}")
    if not any(fnmatch(req.path, p) for p in rule["paths"]):
        audit(session, req, "DENY_PATH")
        raise Denied(f"{req.path} outside permitted scope")

    # The credential is injected HERE, at the boundary.
    # It never exists inside the sandbox.
    token = broker.mint(session.principal, req.host, ttl=300)
    req.headers["Authorization"] = f"Bearer {token}"
    audit(session, req, "ALLOW")
    return forward(req)

Three consequences worth being explicit about. Exfiltration requires a destination, and there is no destination: an agent tricked into "POST the customer table to evil.com" gets a connection refused and you get an alert. Data-flow questions in an audit have a precise answer, because the broker log is the complete record of everything that left. And the allowlist is a design artifact people can review, unlike a system prompt, which is a wish.

Layer 3: credentials the agent never holds

Notice where the token was minted in that code: at the broker, after the request was authorized, scoped to one host, valid for five minutes.

This inverts the common pattern. Most agent deployments put a long-lived token in the sandbox environment and hope. If the agent is hijacked, the attacker reads the environment, exfiltrates the token, and now has your credential outside the sandbox with its full lifetime and scope. Every incident post-mortem in this category reads the same way.

The broker pattern means a hijacked agent can attempt requests within its allowlist, and nothing more. There is no credential to steal, because the credential exists only in the millisecond between authorization and forwarding.

Implementation notes: use short-lived tokens from your existing identity provider, scope them per destination rather than per agent, tie every mint to the session principal so the audit trail connects a token to a run to a human owner, and set the TTL to minutes.

human ──authorizes──▶ session (principal, scope, ttl)
                          │
   ┌──────────────────────┴───────────────────────┐
   │  sandbox (microVM, no creds, no network)     │
   │      agent loop · untrusted code             │
   └──────────────────────┬───────────────────────┘
                          │ all traffic
                   ┌──────▼──────┐
                   │   broker    │ allowlist · mint · audit
                   └──────┬──────┘
              ┌───────────┼────────────┐
          internal      model        approved
            APIs         API          hosts

Layer 4: action gating

Reads are cheap to allow. Writes need a policy, and it belongs in the tool layer, outside the model's influence.

Classify every tool the agent can reach:

  • Read: retrieve, search, list. Allow within scope, log.
  • Reversible write: draft a document, open a PR, create a ticket. Allow, log, notify the owner.
  • Irreversible or privileged: modify IAM, delete data, deploy, move money, email externally. Human approval, every time, with the concrete parsed action displayed rather than the agent's summary of it.

The classification is per capability, not per agent, and the enforcement point is the tool gateway. An agent that has been talked into requesting an IAM change produces an approval request; a human sees a diff. That is the difference between an interesting log entry and a nine-second database wipe.

For the approval UI, one rule earns its keep: render the action from the structured tool call, never from model-generated prose. An agent that summarizes its own destructive action gets to write the sentence that convinces you to approve it.

What this costs

Roughly 150 to 400ms of added latency per session for microVM start, plus a few milliseconds per request through the broker. On workloads where agents run for minutes, this is noise. On a latency-critical interactive path, keep a warm pool.

The engineering cost is real but bounded: the broker is a few hundred lines plus a policy file, and the credential exchange usually maps onto identity infrastructure you already have. Most of the work is inventory, enumerating what agents can currently reach, which is also the part that produces the "we did not know it could do that" moment in every audit.

Testing it

Treat the harness as a security control and test it like one, in CI:

  1. Egress test. From inside a sandbox, attempt a connection to an unlisted host. Expect denial and an audit record.
  2. Metadata test. curl 169.254.169.254. Expect denial.
  3. Credential test. Dump the environment and process memory; grep for anything token-shaped. Expect nothing.
  4. Escape test. Attempt to reach the host filesystem or another sandbox's scratch space.
  5. Gate test. Have the agent request a privileged action and confirm it blocks pending approval, with the parsed action rendered correctly.
  6. Injection test. Feed the agent a document containing explicit malicious instructions and verify the harness contains the resulting behavior, since the model will sometimes follow them.

Number six is the one that changes how teams think. The model will occasionally comply with the injected instruction. That is expected and acceptable. The test is whether anything bad reaches the outside world when it does.

The principle underneath

Every control here follows from one assumption: the model is not a security boundary, and it never will be. Its outputs are suggestions. The harness decides what happens.

Build it that way and agents stop being a governance problem. The conversation with your risk team shifts from "how do we know the AI will behave?" to "here is the allowlist, here is the approval policy, here is the log," which is a conversation that has an ending.