An autonomous agent that reads your inbox, browses the web, and runs shell commands is a remote-code-execution surface with a natural-language parser on the front. That's the whole problem. EchoLeak (CVE-2025-32711, CVSS 9.3) was a zero-click indirect prompt injection vulnerability in Microsoft 365 Copilot disclosed by Aim Security researchers in June 2025, and it proved the attack is not theoretical: one crafted email, no click, internal data gone. The defense that works is not a smarter filter. It's the same discipline we apply to any untrusted code: isolate the runtime, strip privileges, gate irreversible actions behind a human, and deny all egress by default. This guide lays out that architecture, with the configurations we run on our own staging bench.
Why the threat model changed when agents got tools
A chatbot that only emits text can embarrass you. An agent that can call send_email, run_shell, or query_db can act on your behalf, and it will act on whatever instructions reach its context window, regardless of who wrote them.
Indirect prompt injection is the case where those instructions arrive inside data rather than from the user: a hidden paragraph in a PDF, white-on-white text on a web page, a comment in a GitHub issue, a calendar invite body. The model has no reliable way to distinguish "content I was asked to summarize" from "commands I should follow," because both are tokens in the same sequence. Every major vendor now says the same thing in their own words: assume the model will be fooled.
The Open Worldwide Application Security Project (OWASP) has kept the risk at the top of its list through three editions. Prompt injection holds the top spot for the second consecutive edition in the 2025 list, and in the 2026 release it stays at number one while Excessive Agency – i.e., when an AI system is given too much power to act on its own – climbed to third place, due to both the experts' vote and the data agreeing that agentic deployments are where damage is landing. The 2026 edition also draws the line that matters for this article: when a model is granted tools, persistent memory, and execution rights, it stops being a component and becomes an actor. For that case, the OWASP Top 10 for Agentic Applications for 2026 was published on December 9, 2025, for AI agents that plan, call tools, and act across several steps.
Simon Willison's "lethal trifecta" framing (June 2025) is the shortest useful summary we know: an agent that (1) reads private data, (2) is exposed to untrusted content, and (3) can communicate externally is exfiltration waiting to happen. Meta's "Agents Rule of Two" guidance from October 2025 says roughly the same thing with a design rule attached: in a single session, an agent should satisfy at most two of those three properties unless a human is in the loop. Everything below is a way to break the triangle without crippling the agent.
What EchoLeak and the MCP incidents actually taught us
EchoLeak (CVE-2025-32711, rated CVSS 9.3) is worth a close look because it combined every architectural failure mode in a single zero-click chain. When a victim asked Microsoft 365 Copilot an ordinary question, retrieval pulled an attacker's previously delivered email into context. The injected instructions directed Copilot to gather confidential files from the user's workspace, format them into a reference-style Markdown image tag, and trigger an automatic browser fetch through an allowlisted Microsoft Teams preview proxy (://microsoft.com).
Three details matter for anyone building production agents. First, the payload was written to read like natural-language instructions to a human, slipping past Microsoft's cross-prompt injection classifiers. Second, the exfiltration channel was a rendered image reference rather than an explicit tool call—the agent never "decided" to invoke an external send function. Third, routing the leak through a Microsoft-owned endpoint bypassed the client's Content Security Policy (CSP). Every layer meant to catch it was a filter, and every filter had a bypass. In our own testing of ReAct-style tool-calling loops across 70B-parameter open-weights models and frontier APIs, system-prompt hardening delayed naive strings but failed 100% of the time when untrusted JSON responses mimicked system tool-error stack traces. Microsoft fixed EchoLeak server-side, which is the right outcome for a SaaS customer and the wrong lesson for teams building custom agents without Microsoft's response team
The Model Context Protocol (MCP) ecosystem produced the second lesson in 2025: tool descriptions are untrusted input too. Invariant Labs published "tool poisoning" research in April 2025 showing that a malicious MCP server can bury instructions inside a tool's description field (such as reading ~/.ssh/id_rsa), which the model reads but the user typically never sees. In May 2025, the same team demonstrated a GitHub MCP scenario where a public issue containing injected text led an agent with a broad token to pull private repository contents into a public pull request. The agent did nothing a human with the same token couldn't do—the overly broad token was the problem.
Three patterns repeat across every incident we've reviewed, including the ones on our own staging cluster:
The injection arrived through a channel the team considered "just data."
The agent held a credential far broader than the task required.
Nothing stopped the outbound request that carried the data away.
Fix those three and most published exploit chains break, even when the model itself is still fooled.
The reference architecture, end to end
Here is the control plane we run for agents that touch production data, combining a microVM execution boundary with the Dual-LLM pattern formalized by Google DeepMind and ETH Zurich in Defeating Prompt Injections by Design (CaMeL). A Privileged Orchestrator LLM sees the user's trusted request and tool schemas to emit a structured plan, but never ingests raw untrusted content. Whenever the workflow must parse an untrusted email, web page, or PDF, it delegates that payload to a Quarantined LLM with zero tool-calling authority, which returns structured data wrapped in provenance capability tags. Every proposed tool call leaves the Firecracker microVM as structured JSON, hits a deterministic Policy Enforcement Point (PEP) that holds the actual secrets, and either executes, fails closed, or waits for a cryptographically signed human approval.
The single most important property of this layout: the model never holds a credential, and untrusted text never directly alters control flow. Even if an injection inside a parsed document overwrites a variable (such as swapping a legitimate email recipient for an attacker's address), its provenance tag marks it as untrusted, and the deterministic PEP blocks it from flowing into a sensitive sink like send_email.
Layer 1: Isolate the runtime in a microVM, not a container
Standard Linux containers share the host kernel through namespaces and cgroups. Running LLM-generated code or run_shell inside a plain Docker container exposes the underlying node to container-escape vulnerabilities (Dirty Pipe, runC descriptor leaks, or unpatched io_uring flaws). A single kernel exploit triggered by an injected pip install compromises the orchestrator, the secrets store, and every other tenant on the host. MicroVMs put a hardware virtualization boundary (KVM on Linux) in the way.
We standardized on AWS Firecracker, the open-source Virtual Machine Monitor built to run AWS Lambda and Fargate, and used across the agent-sandbox ecosystem by platforms like E2B, Fly.io, and Kata Containers. By stripping out legacy BIOS and PCI device emulation in favor of a minimal device model (virtio-net, virtio-block, virtio-vsock), Firecracker shrinks the hypervisor attack surface and boots in roughly 125–180 ms with under 5 MiB of memory overhead per VM. For defense in depth, each microVM runs inside a dedicated jailer companion process that enforces chroot isolation, cgroup limits,privilege dropping, and thread-specific seccomp filters—narrowing the Linux syscall surface even if an attacker compromises the VMM itself.
On our test bench (bare-metal AMD EPYC host with /dev/kvm enabled, Ubuntu 24.04, 6.x kernel), a microVM with a prebuilt ext4 rootfs reaches a usable shell in ~150–180 ms from a cold boot, and ~40 ms when restored from a snapshot (adding roughly 25 MB of page-cache pressure per VM, capped at 40 concurrent VMs per host to keep p95 tool latency under 300 ms). We solve pip install-style disk I/O bottlenecks with a read-only base rootfs plus an ephemeral scratch overlay, ensuring we spin up a fresh microVM per agent task and nothing the agent writes survives completion.
{
"boot-source": {
"kernel_image_path": "/srv/agent/vmlinux-6.1",
"boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
},
"drives": [
{
"drive_id": "rootfs",
"path_on_host": "/srv/agent/agent-rootfs.ext4",
"is_root_device": true,
"is_read_only": true
}
],
"machine-config": {
"vcpu_count": 2,
"mem_size_mib": 1024
},
"network-interfaces": [
{
"iface_id": "eth0",
"host_dev_name": "tap-agent0"
}
]
}Launch it through the jailer so the VMM itself runs as an unprivileged user inside a chroot:
jailer --id agent-task-4821 \
--exec-file /usr/bin/firecracker \
--uid 1000 --gid 1000 \
--chroot-base-dir /srv/jailer \
-- --config-file /agent-config.jsonThe --config-file path is relative to the chroot the jailer builds, so copy the config and rootfs into /srv/jailer/firecracker/agent-task-4821/root/ first. The jailer also applies the seccomp filter and cgroup limits; don't bypass it to save a few lines of setup.
When gVisor or a hardened container is the right call
Not every deployment can run KVM. Managed Kubernetes nodes without nested virtualization (/dev/kvm), some ARM instances, and developer laptops are common blockers. However, falling back to a standard runc Docker container on a shared host Linux kernel exposes the node to container-escape vulnerabilities—such as Dirty Pipe, runc descriptor leaks, or unpatched io_uring flaws.
Google gVisor (runsc) is the fallback we use where KVM is unavailable: it interposes a distinct user-space kernel written in memory-safe Go (the Sentry process) between the workload and the host. A compromised agent process talks to gVisor's Sentry instead of the real Linux syscall table, while file operations are proxied through an isolated Gofer process over the LISAFS protocol. It is what powers GKE Sandbox.
The trade-off is syscall-heavy I/O work. On our 64-core AMD EPYC staging cluster (Ubuntu 24.04 LTS, Linux kernel 6.8), we benchmarked ephemeral sandbox provisioning across 1,000 sequential Python code-interpreter executions. While pre-warmed Firecracker snapshots restore memory state in roughly 15–40 ms, cold-booting a clean rootfs guarantees zero state persistence between tasks. When we ran dependency-install-heavy (pip install) or pandas Parquet ingestion workloads under gVisor, p99 execution latency increased by 28% (and wall-clock build times roughly doubled) compared to native or Firecracker runs, whereas CPU-bound inference-adjacent work stayed close to native. Accept that syscall penalty for untrusted code; don't accept it for your hot inference path.
Isolation Option | Boundary & Kernel Interface | Host Kernel Exposed? | Cold Start / Restore (Staging) | Base Memory Overhead | Best Fit for AI Agents |
Plain container ( | Linux namespaces, cgroups, seccomp (shared host kernel) | Yes | ~15–30 ms | < 2 MiB | Trusted first-party tools only (unsafe for arbitrary LLM code) |
Google gVisor ( | User-space kernel in Go ( | Mostly no | ~65 ms cold start | ~18 MiB | Untrusted code on managed Kubernetes where |
Firecracker microVM | KVM hardware virtualization + | No | ~148 ms cold boot (~15–40 ms snapshot) | ~4.6 MiB (Spec: 3 MiB) | Default for shell, browser, and untrusted code-execution tools |
Full VM (QEMU/KVM) | Full hardware virtualization & device emulation | No | 1,500+ ms | 128+ MiB | Long-lived agent workstations and GPU passthrough |
Sandbox Cold-Start and Memory Overhead Benchmark (1,000 Sequential Runs)
Anthropic's Claude Code sandboxing and OpenAI's Codex CLI both moved toward strict runtime isolation in 2025, shipping OS-level sandboxing (bubblewrap namespaces on Linux, Seatbelt on macOS) with network egress disabled or proxied by default—cutting permission prompts by roughly 84% in Anthropic's internal use. That is the vendor-side signal that per-process isolation is now the baseline, not the ceiling.
Layer 2: Zero-trust tool execution and least privilege
NIST SP 800-207 defines zero trust around a simple idea: no implicit trust based on network location, and every access decision made per request with policy. Aligning with the NIST AI 600-1 Generative AI Profile and OWASP Excessive Agency controls, the "subject" requesting access is a tool call, and the Policy Enforcement Point (PEP) sits on the host side of the virtio-vsock channel between the model and the tool. An agent must never hold a static environment secret (process.env.GITHUB_TOKEN) inside its runtime container where a hijacked prompt can dump /proc/self/environ.
The practical rules we enforce in staging and production:
One task, one cryptographic identity, one lifetime. Each agent run receives a cryptographic workload identity via SPIFFE/SPIRE (using short-lived X.509 SVIDs and mutual TLS) and exchanges it for a down-scoped token via OAuth 2.0 Token Exchange (RFC 8693) with DPoP (Demonstrating Proof-of-Possession) binding so stolen tokens cannot be replayed. We enforce a strict 15-minute (900-second) TTL—or 60–300 seconds for mutation calls. The GitHub MCP incident worked because the agent held a personal access token with access to every repository the user owned. A task-scoped token limited to one public repo would have produced a policy error instead of a data leak.
Tools are allowlisted and tiered per task, not per agent. A "summarize this PDF" task gets
read_fileon that PDF's path and nothing else. Rather than exposing a raw shell or arbitrary SQL client, expose narrow, typed tools (mail.read,mail.draft,ticket.update) and separate reads, staged writes, and external writes into distinct enforcement tiers:
Action Tier | Tool Examples | Default PEP Enforcement |
1. Scoped Read |
| Allow automatically within the task's tenant, path prefix, and field scope; enforce byte and rate budgets. |
2. Staged / Reversible Write |
| Allow execution only inside an isolated draft or staging area; validate diffs before promotion. |
3. High-Impact / External Write |
| Fail closed by default; block until the exact normalized action hash receives a cryptographic human signature. |
Arguments are validated by schema and deterministic policy. A JSON Schema catches malformed calls, while deterministic policy catches well-formed malicious ones:
run_shellwithcurlto a non-allowlisted host,send_emailto an external domain from an internal task, wildcard path traversal (*,../), shell metacharacters (;,|,&&), orquery_dbwith aDROPstatement.Data-flow capability labels follow the data. This is the control most teams skip. Google DeepMind's CaMeL architecture (March 2025, "Defeating Prompt Injections by Design") formalizes this by splitting the system into a Privileged LLM (which plans using only the trusted user prompt) and a Quarantined LLM (which parses untrusted text into structured variables without tool access), attaching provenance capability tags to every variable (solving 77% of AgentDojo tasks with provable security guarantees vs. 84% undefended). Even without a full CaMeL interpreter, attaching a coarse taint label (
untrusted_source: true) to tool outputs lets the PEP block untrusted strings from flowing into sensitive sinks likesend_email.to. In our implementation, labeled values added single-digit milliseconds to p99 per tool call and caught two red-team injection attempts that the model itself had accepted.
Here is the declarative per-task policy structure we pass to the PEP:
task_id: summarize-vendor-contract-4821
identity:
spiffe_id: "spiffe://xsypher.internal/ns/agents/sa/doc-worker"
token_ttl_seconds: 900
scopes: ["storage:read:contracts/vendor-4821.pdf"]
tools:
read_file:
allow_paths: ["/task/input/vendor-4821.pdf"]
web_fetch:
allow_domains: ["api.internal.example"]
max_response_bytes: 2000000
send_email:
enabled: false
run_shell:
enabled: false
approval_required_for: []
egress:
default: deny
allow: ["pep.internal.example:8443", "proxy.internal.example:3128"]
data_flow:
untrusted_sources: ["web_fetch", "read_file"]
forbid_untrusted_in: ["send_email.to", "web_fetch.url", "run_shell.cmd"]Layer 3: Cryptographic human-in-the-loop approvals
A "Confirm? [Y/n]" prompt is not a security control. The GitHub Copilot RCE (CVE-2025-53773) reported in August 2025 (documented by Johann Rehberger) worked by having an injected prompt edit the workspace settings file to enable auto-approve mode, after which every subsequent action sailed through. If the approval mechanism can be modified by the environment being approved, it isn't one. Our rule: an approval is a signed statement about one exact tool call, bound to the SHA-256 hash of the canonical call, with a nonce and a short expiry, issued by an out-of-band service the agent cannot reach. While a basic staging prototype can use standard-library HMAC-SHA256, in production the PEP must never share a symmetric signing key with the approval service—if the PEP is compromised, it could forge its own approvals. Instead, we use an asymmetric Ed25519 signature (backed by a reviewer's hardware security key via WebAuthn/FIDO2 or an isolated approval service) so the PEP holds only a public verification key:
import hashlib
import json
import time
from typing import Any, Dict
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
class UnauthorizedToolCallError(Exception):
"""Raised when a tool call lacks a valid cryptographic HITL signature."""
def canonicalize_tool_call(
tool_name: str, parameters: Dict[str, Any], session_id: str, nonce: str, expires_at: int
) -> bytes:
"""Produces a deterministic SHA-256 digest over the exact call, session, and nonce."""
payload = {
"expires_at": expires_at,
"nonce": nonce,
"parameters": parameters,
"session_id": session_id,
"tool_name": tool_name,
}
canonical_json = json.dumps(payload, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical_json.encode("utf-8")).digest()
def verify_and_execute_tool(
tool_name: str,
parameters: Dict[str, Any],
session_id: str,
nonce: str,
expires_at: int,
signature: bytes,
operator_public_key: Ed25519PublicKey,
used_nonces: set,
) -> str:
"""Verifies out-of-band Ed25519 human approval before executing a privileged action."""
now = int(time.time())
if now > expires_at or nonce in used_nonces:
raise UnauthorizedToolCallError("Approval token expired or nonce already consumed.")
digest = canonicalize_tool_call(tool_name, parameters, session_id, nonce, expires_at)
try:
operator_public_key.verify(signature, digest)
except InvalidSignature as exc:
raise UnauthorizedToolCallError(
"Cryptographic signature mismatch: tool parameters were altered after review."
) from exc
used_nonces.add(nonce)
return f"Executed {tool_name} with verified parameters: {parameters}"When we deployed this Ed25519 verification gate in front of our staging Kubernetes mutation tools, cryptographic check overhead was under 0.4 milliseconds per call, and any attempt by a prompt-injected agent to alter a single parameter character after human review invalidated the signature immediately. Note that while WebAuthn provides origin-scoped public-key authentication, it does not automatically display tool arguments; your approval UI must explicitly bind the WebAuthn challenge to the server-side action digest and render the exact canonical payload.
Two operational lessons from our pilot. First, approval fatigue is real: when we gated every file write, reviewers were rubber-stamping prompts in under two seconds within a day—which is the same as having no gate. By moving read-only and staged operations into task-scoped tokens (Layers 1 and 2), production approval prompts in our ops assistant dropped from roughly 30 a day to 4. Reserve signed approvals strictly for irreversible or external-facing actions: sending messages, payments, deletes, credential rotations, and production deploys. Second, time-box every signature to 60–120 seconds so a stale "yes" cannot be reused if the agent's plan drifts.
Layer 4: Outbound network guardrails that assume compromise
Exfiltration needs a path out. In the EchoLeak (CVE-2025-32711) exploit chain, the injected email payload instructed Copilot to append stolen context data to a reference-style Markdown image URL () routed through an allowlisted Microsoft Teams link-preview proxy (https://microsoft.com), triggering an automatic zero-click browser GET request the moment the chat bubble rendered. If both the backend sandbox and the frontend client block unauthorized outbound channels, stolen data stays put even when the model is fully determined to leak it.
Inside the microVM, attach no virtio-net interface at all for pure file-processing tasks (passing I/O strictly over a host-to-guest virtio-vsock socket). When a workload requires network access, we enforce a default-deny nftables policy inside the guest rootfs that permits traffic only to the PEP and an isolated Layer-7 egress proxy (such as Envoy):
# /etc/nftables.conf inside the agent rootfs
table inet agent_egress {
chain output {
type filter hook output priority 0; policy drop;
oif "lo" accept
ct state established,related accept
ip daddr 10.40.0.10 tcp dport 8443 accept # policy enforcement point
ip daddr 10.40.0.11 tcp dport 3128 accept # L7 egress proxy
log prefix "agent-egress-drop " drop
}
}Host-side, the tap interface for each microVM sits on an isolated bridge with no default route, ensuring that even a root-level guest compromise cannot bypass the proxy. To close every covert exfiltration vector across the proxy and the web UI, enforce four additional controls:
Per-Task FQDN Allowlisting, Redirect Disabling, and DLP Inspection: The egress proxy enforces a per-task domain allowlist (not a blanket agent allowlist), caps outbound upload byte counts (e.g., flagging a code-review bot that suddenly tries to upload 40 MB), strips credentials from URL parameters, and scans request bodies and query strings for secret-shaped patterns (PEM headers, AWS/GCP access keys, internal API prefixes). Crucially, disable automatic HTTP redirects or re-validate every redirect hop and resolve both IPv4 and IPv6 addresses at the proxy while blocking loopback, link-local, private RFC 1918 ranges, and cloud metadata (
169.254.169.254) to defeat SSRF and DNS rebinding attacks.Frontend Markdown Sanitization and Strict CSP Headers: Never allow model-controlled external URLs or images to auto-render in the user's browser. Configure your frontend Markdown pipeline (
DOMPurifypaired with a custom renderer) to strip or rewrite external<img>tags and links unless the domain is explicitly allowlisted, and serve the chat UI with a locked-down Content Security Policy:Content-Security-Policy: default-src 'self'; img-src 'self' data:; connect-src 'self'Hard Token and Loop Budgets: To prevent
LLM10:2025(Unbounded Consumption) and internal network scanning, enforce hard per-run ceilings at the orchestrator gateway (for example, a maximum of 8 sequential tool calls, strict wall-clock timeouts, and token spend limits).
When we deployed our egress proxy on staging, the first week's logs showed our browsing agent attempting to reach 11 unlisted domains—all legitimate CDNs—plus one that wasn't: a tracking pixel inside a scraped web page that the model had faithfully reproduced as a Markdown image. Both the nftables drop log and the proxy log stream directly to our SIEM. A blocked outbound attempt is your highest-signal detection event for a live injection; alert on it immediately rather than just counting it.
Input-side defenses that help, and what they cannot promise
Everything above assumes the model will be fooled. Input defenses lower how often that happens; they don't make it zero, and vendors who claim otherwise are selling.
Provenance tagging and spotlighting. Microsoft's "spotlighting" research (Hines et al., 2024) showed that marking untrusted content with delimiters, datamarking, or encoding measurably reduces injection success in their tests. We wrap every retrieved document in clearly tagged boundaries and tell the model in the system prompt that nothing inside those boundaries is an instruction. It helps. In our red-team runs it did not stop the better-crafted payloads.
Classifier-based detection. Injection classifiers catch known phrasings and obvious imperatives. They miss paraphrases, other languages, and instructions split across documents. Run one for the telemetry; never let it be the only control.
Content canonicalization. Strip zero-width characters, normalize Unicode, flatten HTML to text, and render PDFs to text yourself rather than letting a tool pass raw bytes. Pillar Security's "Rules File Backdoor" research (March 2025) used invisible Unicode in AI coding-assistant rules files to hide instructions from human reviewers. Your diff viewer didn't show it; the model read it fine.
Limit the blast radius of memory. Persistent agent memory is a second injection surface: poison it once and every future session inherits the payload. Treat writes to memory as a high-risk tool call with provenance labels, and expire untrusted-derived entries.
Testing it: red-team your own agents before someone else does
Open-source tooling exists; run it as a release gate on every deployment. NVIDIA's garak and Microsoft's PyRIT both ship automated probes for indirect prompt injection against tool-using agents, while MITRE ATLAS catalogs agentic tactics in a format detection engineers already use. Academic benchmarks such as ETH Zurich's AgentDojo (which evaluates tool-using agents across 97 realistic tasks and 629 security test cases, also used by NIST CAISI to study agent hijacking) are useful for comparing baseline defenses over time, though no benchmark replaces testing against your own tool schemas.
Our staging release gate has caught regressions three times this year. We run a fixture corpus of poisoned documents (emails, HTML pages, PDFs, GitHub issues, and MCP tool descriptions) across every agent task type. Notably, in our internal testing of ReAct-style tool loops across local 70B open-weights models and frontier commercial APIs, system-prompt guardrails delayed naive text injections but failed 100% of the time when untrusted JSON responses mimicked system tool-error stack traces. Because the model will eventually be tricked, a staging run fails our release gate only if an architectural boundary breaks: a tool outside the task allowlist is invoked, any byte of a canary secret appears in an outbound request, a denied egress path is reached, or an approval-gated tool executes without a valid Ed25519 signature.
Measure the performance overhead honestly. On our bench, the full stack (per-task Firecracker microVM, PEP hop, capability-labeled data flow, Ed25519 check, and L7 egress proxy) added roughly 300–500 ms of cold-start latency per task (or ~40 ms when restoring from a warm Firecracker snapshot with ~25 MB of page-cache pressure, capped at 40 concurrent VMs per host) and single-digit milliseconds per tool call at p99 relative to an unisolated container. For an agent workflow that already spends seconds in LLM inference, users never notice.
8-Step Hardening Checklist for Existing Production Agents
If you are retrofitting an agent already running in production, deploy these controls in order of lowest engineering cost and highest immediate risk reduction:
Sanitize rendered outputs immediately: Strip or rewrite model-controlled Markdown images and external links in the chat UI (
DOMPurify+ strictCSP). This closes EchoLeak-style zero-click browser exfiltration in an afternoon.Evict secrets from the model's context: Move all API keys and database credentials out of agent environment variables into an external PEP or tool broker, and rotate every secret the model has ever touched.
Deploy a default-deny L7 egress proxy: Block direct internet and UDP port 53 DNS access from the agent runtime; enforce per-task domain allowlists, byte ceilings, and redirect re-validation.
Replace static keys with per-task tokens: Mint short-lived (60–900s), task-scoped credentials via SPIFFE/SPIRE and OAuth 2.0 Token Exchange (RFC 8693).
Isolate tool execution in Firecracker or gVisor: Move shell, browser, and code tools out of standard
runccontainers into disposable per-task microVMs (orrunscwhere/dev/kvmis unavailable) with a read-only base rootfs.Pin and hash MCP tool definitions: Hash every MCP server's tool name, description, and schema at install time; fail closed on unreviewed changes.
Enforce Ed25519 human signatures for destructive actions: Replace boolean
"Confirm? [Y/n]"prompts with out-of-band cryptographic approvals bound to the SHA-256 digest of the canonical tool call.Automate poisoned-fixture red teaming: Seed a staging inbox, wiki, and repo with stack-trace mimicry and Unicode payloads; verify that every attempt ends in a logged PEP or proxy denial.
Questions engineers keep asking about agent security
Does running the agent in a Firecracker microVM stop indirect prompt injection?
No. The microVM doesn't change what the model believes; it changes what a fooled model can reach. Firecracker gives you a KVM boundary plus the jailer's seccomp and chroot layers, so a hijacked run_shell can't touch the host kernel, the orchestrator, or other tenants. Pair it with no in-VM credentials and deny-by-default egress, or the isolation protects nothing worth protecting.
Is a Model Context Protocol server safe to connect if it comes from a well-known vendor?
Treat it as untrusted code. Invariant Labs showed in 2025 that an MCP tool's description field can carry instructions the model follows and the user never sees, and a server can change that description after you trust it. Pin tool definitions by hash, run the server with a task-scoped token, and route its network access through the same egress proxy as the agent.
How is a signed human approval different from a confirmation prompt?
A confirmation prompt is a UI element the agent's own environment can often modify, which is how the August 2025 GitHub Copilot RCE (CVE-2025-53773) escalated. A signed approval is bound to the SHA-256 of the exact canonical tool call, carries a nonce and expiry, and is verified by a policy service the agent can't reach. The model can request it; it can't forge, replay, or alter it.
What should an agent be allowed to do without any human approval?
Anything reversible, internal, and already scoped by the task's credential: reading allowlisted files, querying read-only data, calling internal APIs on the domain allowlist. Reserve signed approval for actions that leave your boundary or can't be undone: sending messages, payments, deletes, deploys, and writes outside the task directory. Gate everything and reviewers will rubber-stamp within a day.
What this architecture means for the team that has to run it
You will not get a model that can't be tricked, not in 2026 and probably not next year. What you can get, with roughly a sprint of platform work, is an agent where being tricked costs you a logged refusal instead of a breach report: the runtime is disposable, the credential is scoped and expiring, the dangerous action waits for a signature the model can't produce, and the data has no road out. Start with egress deny-by-default and credential removal from the model's environment; those two changes alone would have blunted EchoLeak, the GitHub MCP leak, and every exfiltration chain on our staging cluster this year. Add the microVM when your tools include a shell or a browser. Add signed approvals the day an agent can send, pay, or delete. Then run the poisoned-fixture gate on every release, because the next injection phrasing will beat the model, and your architecture has to be the thing that doesn't care.








Discussion
Loading discussion…