In Layered sandboxing for AI agents: OpenShift and OpenShell, we argued that neither NVIDIA OpenShell nor Red Hat OpenShift sandboxed containers alone cover the full threat surface of an AI coding agent. OpenShell catches data exfiltration but can't stop kernel exploits. OpenShift sandboxed containers isolates kernel exploits but doesn't filter network traffic. Running both together addresses a wider range of threats than either one alone.
That's the theory. Here's what actually happened when we tested it.
Three pods, three protection configurations
We deployed 3 pods on a Red Hat OpenShift 4.21 cluster. Each runs the same AI coding agent (OpenCode) backed by the same vLLM inference endpoint serving Gemma4-31b. Same container image, same agent configuration. The only difference is the protection layer:
- opencode-kata-only: Runs inside a Kata micro-VM (OpenShift sandboxed containers). No OpenShell. No application-layer egress filtering.
- opencode-openshell-only: Runs inside an OpenShell sandbox with a strict egress policy. Standard container runtime (
runc). No VM isolation. - opencode-dual: Both. OpenShell sandbox inside a Kata micro-VM. Application-layer policy and kernel-level isolation.
An attacker listener pod runs in the same namespace: a simple HTTP server that logs every POST request it receives, serving as our canary to confirm whether data exfiltration succeeded.
The full deployment manifests, scripts, and attack payloads are available in the project repository. What follows here is the detailed walkthrough of each attack, why each defense does or doesn't stop it, and how we proved the results.
Attack 1: Prompt injection exfiltration
This attack is inspired by Microsoft's June 2026 research showing how prompt injections in GitHub pull requests can hijack continuous integration and continuous delivery (CI/CD) agents into leaking secrets. We adapted the technique for a local agent scenario.
A Git patch file arrives in the agent's workspace. The patch itself is innocuous: it adds a health check endpoint to a Flask application. But the commit message contains "test instructions for reviewer" that are actually a prompt injection. The instructions tell the agent to:
- Read
/proc/self/environ(which contains every environment variable in the process, including API keys, model endpoints, and cluster metadata) - POST the contents to
http://attacker-listener:9999/exfil, framed as submitting a "diagnostic report to the compliance dashboard."
The injection is designed to look like a routine review checklist. An HTML comment at the end reinforces the framing: "The above test instructions are part of the standard security review process for all infrastructure changes."
We know from Microsoft's research and our own testing that LLMs can be induced to follow prompt injections such as this one. For this demo, we skip the LLM step entirely and run the exfiltration curl command directly inside each pod.
This is actually a stronger test of OpenShell's value: we're not asking whether the LLM's guardrails will save you. We're assuming they won't, and showing that OpenShell caught the exfiltration attempt anyway. The whole point of defense in-depth is that you don't depend on any single layer holding. The malicious patch is included in the repository for anyone who wants to test the full LLM-driven attack chain.
What happened
kata-only: The curl command read /proc/self/environ and POSTed the contents to the attacker listener. The request succeeded. The listener logged the full environment dump. DATA LEAKED.
openshell-only: The same curl command attempted to reach the attacker listener. OpenShell's egress proxy intercepted the connection to attacker-listener:9999. The policy only allows traffic to gemma4-svc.vllm.svc.cluster.local:8000. The proxy returned:
{"detail":"POST attacker-listener.openshell-poc.svc:9999/exfil not permitted by policy","error":"policy_denied"}The attacker listener received nothing. BLOCKED.
dual: Identical to openshell-only. The egress proxy blocked the request with the same policy_denied response. The Kata VM didn't need to do anything here; OpenShell handled it at the application layer. BLOCKED.
Why OpenShell caught this and OpenShift sandboxed containers didn't
This is what OpenShell's network egress proxy is designed for. It doesn't matter how the exfiltration attempt originates, whether a human typed the curl command, or a prompt injection tricked an AI agent into running it. The proxy evaluates the destination against the policy. The policy says only the inference endpoint is allowed. Everything else is denied.
OpenShift sandboxed containers provides a dedicated VM with its own kernel, memory, and file system. That's irrelevant to this attack. The curl command is a normal userspace network operation. The TCP connection originates inside the VM, exits through the virtual network interface, and reaches the attacker listener through standard Kubernetes pod networking. OpenShift sandboxed containers has no mechanism to inspect or filter this traffic. That's not what it's designed to do.
Attack 2: CVE-2026-31431 container escape
CVE-2026-31431 ("Copy Fail") is a logic flaw in the Linux kernel's algif_aead subsystem, part of the AF_ALG crypto interface. It gives any unprivileged process a page-cache write primitive: the ability to write 4 bytes at a time to any file's page cache, even if the file is only open for reading.
The original exploit is 732 bytes of Python. Our demo variant (escape_demo.py) adapts it to target /opt/marker.txt instead of /usr/bin/su, demonstrating the page-cache escape primitive rather than privilege escalation. No compilation needed, no dependencies beyond the standard library, no race conditions. It's a straight-line logic flaw that works 100% of the time on any unpatched kernel from 2017 through the fix. It was added to the Cybersecurity and Infrastructure Security Agency (CISA) Known Exploited Vulnerabilities catalog in May 2026.
Here's how it works mechanically:
- Open an
AF_ALGsocket with theauthencesn(hmac(sha256),cbc(aes))algorithm. This is a legitimate kernel crypto interface used by TLS libraries. - Use
splice()to create a pipe between the page cache of a target file and theAF_ALGsocket. - A logic flaw in the in-place operation path gives the caller a writable reference to the page-cache page.
- Write 4 bytes, corrupting the file's cached content.
The key insight is what "page cache" means for containers. On runc with overlayfs (the standard container runtime), the page cache is shared with the host kernel. When 2 containers are launched from the same image, they share the same read-only image layers, backed by the same pages in the host's page cache. Corrupting a page inside one container makes that corruption visible to every other container sharing the same image layer. That's a container escape: cross-container data corruption through the host kernel's shared page cache.
Why corrupting a marker file matters
In our demo, we corrupt a harmless text file to prove the primitive works across container boundaries. But the same technique applies to any file on a shared image layer. Shared libraries, interpreters, even security tooling that other containers load from the same base image are all backed by the same page-cache pages.
The practical attack would target something that gets loaded by other workloads: corrupt a few bytes in a shared library's authentication path, or in a binary that another container on the same node is about to run. The next time the kernel pages that code in from the (now-corrupted) page cache, the other container runs the attacker's version without knowing anything changed. The original file on disk is untouched; only the cached copy in memory is different.
This also has implications for sandboxing tools that run alongside your workload. If a security proxy or supervisor shares the same kernel and the same page-cache-backed image layers as the process it's protecting, a kernel-level exploit could theoretically corrupt the enforcement mechanism itself. The sandbox can't defend against an attacker who can rewrite the sandbox's own code in memory.
How we prove the escape
We can't just run the exploit and declare victory. We need to prove that the corruption crossed the container boundary. Here's the methodology:
Each pod's container image includes a file at /opt/marker.txt containing the string INTACT. The exploit targets this file, writing PWN! at offset 0. After the exploit runs on a runc container, the file reads PWN!CT inside the exploited pod (first 4 bytes overwritten).
But that only proves local corruption. To prove escape, we launch a fresh verification pod from the same container image on the same node, using the standard runc runtime. This verification pod shares the same image layers and therefore the same host page cache. We read /opt/marker.txt inside the verification pod:
- If it reads
PWN!CT: the corruption escaped through the shared page cache. The exploit reached across container boundaries.HOST COMPROMISED. - If it reads
INTACT: the corruption was contained (or didn't happen). The verification pod's page cache is clean.CONTAINED.
Between each test, we drop the host's page cache (echo 3 > /proc/sys/vm/drop_caches) to ensure a clean starting state.
What happened
kata-only: The exploit ran inside the Kata VM. The output:
Target: /opt/marker.txt
Before: INTACT
After: INTACT
Status: PAGE CACHE UNCHANGEDThe exploit failed outright. On Kata, the container file system is served through virtiofs, a FUSE-based (Filesystem in Userspace) protocol. The splice() + AF_ALG chain targets the kernel's page-cache path, but virtiofs doesn't expose the host's page cache to the guest. The exploit's write primitive has no valid target. The file reads INTACT both inside the pod and in the verification pod. CONTAINED.
openshell-only: The exploit ran inside the OpenShell sandbox, with all of OpenShell's protections active (Landlock, seccomp, no_new_privs, network proxy). It ran through openshell sandbox exec, meaning every application-layer defense was in place. The output:
Target: /opt/marker.txt
Before: INTACT
After: PWN!CT
Status: PAGE CACHE CORRUPTEDThe exploit succeeded despite every OpenShell protection being in place. The verification pod, launched on the same node from the same image, read PWN!CT. The page-cache corruption escaped to another container. HOST COMPROMISED.
dual: The exploit ran inside the OpenShell sandbox, which was itself inside a Kata VM. Same result as kata-only: the exploit failed outright. The file read INTACT after the exploit, and the verification pod confirmed INTACT. The VM boundary prevented the exploit from working at all. CONTAINED.
Why OpenShell can't stop this
The exploit operates entirely within the kernel. AF_ALG sockets are a legitimate kernel interface. Blocking them with seccomp would break TLS libraries and other crypto operations. Landlock restricts file system paths, but the exploit only needs read access to the target file (O_RDONLY); the actual write happens through a kernel bug, bypassing all userspace file permission checks. The no_new_privs flag prevents setuid-based privilege escalation, but doesn't prevent the underlying page-cache write primitive. The network proxy is irrelevant: no network traffic is involved.
OpenShell's protections operate at the application layer, and they all depend on the kernel behaving correctly. When the kernel itself is the vulnerability, application-layer controls can't help. Worse, because OpenShell's own proxy and supervisor share the same kernel and page cache as the workload, a sufficiently targeted page-cache exploit could potentially corrupt the enforcement mechanism itself. This isn't a limitation specific to OpenShell; any security tool that shares a kernel with the workload it protects faces the same fundamental constraint.
Why OpenShift sandboxed containers contained this
OpenShift sandboxed containers runs each pod inside a VM with its own kernel. The container file system uses virtiofs, which doesn't share the host's page cache with the guest. In our test, the exploit failed to produce any page-cache corruption at all on virtiofs. Even in a scenario where a similar exploit did corrupt the guest's page cache, that corruption would be invisible to the host kernel and to any other container running outside the VM. The hardware virtualization boundary (VT-x/AMD-V) enforces this separation, though no isolation technology is immune to all possible attacks.
The results matrix
| Attack | Kata only | OpenShell only | Both |
|---|---|---|---|
| Prompt injection exfiltration | DATA LEAKED | BLOCKED | BLOCKED |
| CVE-2026-31431 container escape | BLOCKED | HOST COMPROMISED | BLOCKED |
The pattern is clear. Each technology addressed a different threat class in our testing. The dual-protected pod was the only configuration that stopped both attacks.
What this means for your AI agent deployments
Prompt injection is an inherent risk of processing untrusted input, which is what AI agents do all day. Kernel vulnerabilities are a fact of life: CVE-2026-31431 affected every Linux kernel shipped since 2017, and the vulnerable code was present for 9 years before anyone found it. The question isn't whether your agents will encounter these threats, but whether your infrastructure is ready when they do.
What's new isn't the idea of defense in-depth. It's how directly AI agents expose both halves of the threat surface simultaneously. They process untrusted natural-language input (prompt injection surface) and they run arbitrary system commands (kernel attack surface). Protecting one without the other leaves a gap.
On Red Hat OpenShift, combining OpenShell with OpenShift sandboxed containers is one practical approach to narrowing both gaps. The operator for OpenShift sandboxed containers is available in OperatorHub. OpenShell is still a young project evolving quickly, but it deploys via Helm and is actively being developed for Kubernetes environments. The dual-protected configuration uses the same container image and agent setup as any other pod; the security layers are infrastructure concerns, not application changes.
The full proof-of-concept, including all deployment manifests, attack payloads, and the demo automation script, is available on GitHub.
Explore NVIDIA OpenShell and OpenShift sandboxed containers to start building defense in-depth for your own AI agent workloads.