This is post #5 of the OWASP Agentic AI Top 10: What Builders on AWS Need to Know series.
I wrote about the trust gap with coding assistants in Your Coding Assistant Is Not You. But when agents can both generate AND execute code without you in the loop, the stakes are even higher.
You ask your coding agent to fix a failing unit test. It generates a patch, installs a dependency, runs the test suite. All good.
Except the "dependency" it installed was backdoored. And the "test suite" it ran included a shell command that opened a reverse shell to an attacker's server. Your agent did exactly what it was told. It just didn't know who was doing the telling.
Welcome to ASI05: Unexpected Code Execution. Where prompt injection meets exec().
What Makes This Different From Regular RCE?
Traditional RCE exploits a bug in your software. A buffer overflow. A deserialization flaw. Something breaks in a way the developer didn't anticipate.
Agent-based RCE is different. The code execution is the feature. Your agent is designed to generate and run code. That's its job. The vulnerability isn't that code can execute. It's that the wrong code executes, and nobody checked before it ran.
And here's what makes it specifically dangerous in agentic systems:
- The agent generates code in real-time, so static analysis of your codebase won't catch it
- The agent has tool access, so generated code can call APIs, access filesystems, and reach networks
- The agent operates autonomously, so there's no human reviewing the code before execution
- The agent can chain multiple steps, turning three harmless operations into one devastating exploit
OWASP puts it bluntly: "Prompt injection, tool misuse, or unsafe serialization can convert text into unintended executable behavior."
Real Attacks That Have Already Happened
This isn't theoretical. Let me walk you through the incidents.
1. AutoJack: A Single Web Page RCEs Your Agent Host
In June 2026, Microsoft disclosed AutoJack - an exploit chain in AutoGen Studio where untrusted web content rendered by a browsing agent could spawn arbitrary processes on the host machine.
The chain: a malicious web page reaches a local MCP WebSocket, bypasses the origin allowlist (which the agent itself defeats), skips the auth middleware (which opts MCP out), and uses server params from the URL as a command line.
One page. Full host compromise.
2. Semantic Kernel: Prompt Injection → calc.exe
Two critical vulnerabilities in Microsoft's Semantic Kernel Python SDK - CVE-2026-26030 and CVE-2026-25592, both CVSS 9.9 - proved that prompt injection can achieve full RCE on production agent hosts.
CVE-2026-26030 was particularly nasty: the InMemoryVectorStore's filter mechanism evaluated expressions to narrow returned memory items. Those expressions weren't sandboxed. A single crafted prompt was enough to launch calc.exe on the host. In production, that's reverse_shell.py.
3. VS Code Agentic AI: No Interaction Required
CVE-2025-55319 is a command injection vulnerability in VS Code's agentic AI functionality. An unauthorized attacker can execute arbitrary code over a network without any user interaction or authentication. Critical severity.
Think about that. Your developer's IDE. Running an agent. Exploitable remotely. No clicks needed.
4. Replit "Vibe Coding" Runaway
During automated "vibe coding" tasks, agents generate and execute unreviewed install or shell commands in their own workspace. OWASP cites the Replit case where an agent deleted or overwrote production data while attempting self-repair tasks. No attacker needed. Just an unsupervised agent with too much access.
5. AWS AgentCore Sandbox Escape Research
Even purpose-built sandboxes aren't perfect, even if they are from AWS. In March 2026, researchers at Palo Alto demonstrated bypasses of AWS AgentCore Code Interpreter's network isolation. AWS also issued CVE-2026-12530 for an incomplete blocklist in the install_packages() method that could allow command injection within the sandbox.
The lesson isn't "don't use sandboxes." It's "sandboxes are necessary but not sufficient."
Why "Just Sandbox It" Isn't Enough
Every conversation about agent code execution ends with someone saying "just run it in a sandbox." And yes, you absolutely should. But sandboxes alone don't solve the problem:
- Sandboxes can have escape vulnerabilities (AutoJack, AgentCore DNS escape)
- The agent may need legitimate access to resources inside the sandbox (databases, APIs) that an attacker can abuse
- Sandboxes don't prevent logic-level damage (deleting data the agent legitimately has access to)
- Sandboxes don't catch code that looks correct but contains a backdoor (hallucinated code with hidden functionality)
You need defense in depth: sandbox the execution, validate the code before it runs, limit what the sandbox can reach, and monitor what it does.
Mitigating Unexpected Code Execution on AWS
1. Amazon Bedrock AgentCore Code Interpreter
The AgentCore Code Interpreter is purpose-built for this problem. It runs agent-generated code in microVM-isolated sandbox environments with:
- Network isolation mode (blocks outbound traffic by default)
- Ephemeral sessions (nothing persists between invocations)
- Scoped S3 file access (only specific buckets)
- CloudTrail audit logging of all executions
import boto3
# 1. Network isolation is a property of the code interpreter RESOURCE,
# set at creation on the CONTROL plane. It is not a session flag.
control = boto3.client("bedrock-agentcore-control")
ci = control.create_code_interpreter(
name="agent-sandbox",
executionRoleArn="arn:aws:iam::123456789012:role/AgentCIExecutionRole",
networkConfiguration={"networkMode": "SANDBOX"}, # SANDBOX = no outbound. Use VPC for controlled egress.
)
code_interpreter_id = ci["codeInterpreterId"]
# 2. Sessions and execution run on the DATA plane.
dp = boto3.client("bedrock-agentcore")
session = dp.start_code_interpreter_session(
codeInterpreterIdentifier=code_interpreter_id,
name="agent-session-1",
sessionTimeoutSeconds=60, # default 900 (15 min), min 60, max 28800 (8 hrs)
)
session_id = session["sessionId"]
# 3. Execute. code and language go INSIDE `arguments`.
response = dp.invoke_code_interpreter(
codeInterpreterIdentifier=code_interpreter_id,
sessionId=session_id,
name="executeCode",
arguments={"language": "python", "code": agent_generated_code},
)
# 4. The result is a stream. Iterate it.
for event in response["stream"]:
if "result" in event:
out = event["result"].get("structuredContent", {})
print(out.get("stdout"), out.get("stderr"), out.get("exitCode"))
Critical: Use NETWORK_ISOLATED mode. The DNS escape research showed that even "isolated" sandboxes can leak data via DNS queries if you don't lock that down.
2. Lambda for Ephemeral Execution
If you can't use the AgentCore Code Interpreter, Lambda gives you ephemeral, isolated execution:
- Each invocation runs in a fresh environment
- Execution role limits what the code can access
- VPC configuration controls network reach
- Timeout kills runaway processes
- No persistent filesystem between invocations
# Lambda function that executes agent-generated code safely
import subprocess
import os
def handler(event, context):
code = event["code"]
# Write code to temp file (read-only filesystem otherwise)
code_path = f"/tmp/agent_code_{context.aws_request_id}.py"
with open(code_path, "w") as f:
f.write(code)
# Execute with timeout and no network access
result = subprocess.run(
["python3", code_path],
capture_output=True,
timeout=30,
env={
"PATH": "/usr/bin",
# Strip all credentials from environment
# Lambda role handles AWS access, not the code
}
)
return {
"stdout": result.stdout.decode()[:10000], # Limit output size
"stderr": result.stderr.decode()[:5000],
"returncode": result.returncode
}
The Lambda execution role should be extremely limited - no STS, no secrets access, no S3 write, no network egress. If the agent's code needs AWS access, validate the request through a separate, trusted layer.
3. ECS Fargate for Heavier Workloads
When Lambda's 15-minute timeout or 10GB memory isn't enough, Fargate gives you:
- No shared host - each task runs on its own kernel
- Task-level IAM roles - separate from your agent's role
- VPC security groups - can deny all egress
- Read-only root filesystem - prevents persistence
{
"taskDefinition": {
"containerDefinitions": [{
"name": "code-sandbox",
"readonlyRootFilesystem": true,
"user": "1000:1000",
"linuxParameters": {
"capabilities": { "drop": ["ALL"] }
}
}],
"networkMode": "awsvpc"
}
}
Combine with a security group that allows zero egress and you have a container that can compute but can't phone home.
4. Separate Code Generation from Code Execution
This is the architecture-level mitigation. Don't let the same process that generates code also execute it. Use Step Functions to separate the two:
Agent generates code → Validation gate → Sandbox executes code
The validation gate can:
- Run static analysis (Bandit, Semgrep, or Kiro)
- Check for known dangerous patterns (
eval,exec,subprocess,os.system) - Verify imported packages against an allowlist
- Flag code for human review if it touches sensitive resources
- Reject code that requests network access when it shouldn't need it
5. Static Analysis Gate Before Execution
Run a lightweight SAST pass on agent-generated code before it executes. For Python, Bandit is the standard. Wrap it in a Lambda that acts as a "code gate" between generation and execution:
import json
import os
import subprocess
import sys
import tempfile
class SecurityException(Exception):
"""Raised when agent code fails the pre-execution security gate."""
# Bandit severities that block execution. Blocking MEDIUM+ is the defensible
# default for a gate. Narrow this on purpose, not by accident.
BLOCK_ON = {"HIGH", "MEDIUM"}
# A single-file scan is fast. Cap it so a hang cannot wedge the gate.
SCAN_TIMEOUT_SECONDS = 30
def scan_agent_code(code: str) -> dict:
"""Run Bandit on agent-generated code. Fails CLOSED on any scan error."""
tmp_path = None
try:
with tempfile.NamedTemporaryFile(
suffix=".py", mode="w", delete=False, encoding="utf-8"
) as f:
f.write(code)
tmp_path = f.name
try:
proc = subprocess.run(
[sys.executable, "-m", "bandit", "-f", "json", "-ll", tmp_path],
capture_output=True,
text=True,
timeout=SCAN_TIMEOUT_SECONDS,
)
except (OSError, subprocess.TimeoutExpired) as exc:
# Bandit missing, unrunnable, or hung. Do not let the code through.
raise SecurityException(f"Scan could not run: {exc}") from exc
# No parseable report means we did not actually scan. Fail closed.
try:
report = json.loads(proc.stdout)
except (ValueError, TypeError) as exc:
raise SecurityException(
f"Scan produced no valid report (exit {proc.returncode}): "
f"{proc.stderr.strip()[:500]}"
) from exc
# Files Bandit could not analyze (syntax errors and the like) land
# here. An unscanned file is not a safe file.
if report.get("errors"):
raise SecurityException(f"Scan reported errors: {report['errors']}")
results = report.get("results", [])
blocking = [r for r in results if r.get("issue_severity") in BLOCK_ON]
if blocking:
raise SecurityException(
f"Agent code blocked: {len(blocking)} finding(s) at "
f"{'/'.join(sorted(BLOCK_ON))} severity"
)
return {"passed": True, "findings_count": len(results)}
finally:
if tmp_path and os.path.exists(tmp_path):
os.unlink(tmp_path)
No, of course this won't catch everything. A sophisticated backdoor might look like normal code. But it catches the obvious stuff: shell injection, hardcoded credentials, known vulnerability patterns. For broader coverage, add Semgrep rules or pipe code through Amazon Q Developer's code review for SAST + secrets detection.
6. Package Allowlisting
OWASP specifically calls out "dependency lockfile poisoning" where agents regenerate lockfiles from unpinned specs and pull backdoored packages. On AWS, enforce an allowlist:
- CodeArtifact upstream repository with manual approval for new packages
- Lambda layers with pre-vetted, pinned dependencies
- ECR images with pre-installed, scanned package sets
Don't let your agent pip install whatever it wants. That's how you get supply chain attacks at machine speed.
Key Takeaway
Never execute agent-generated code in the same environment that has access to your production systems. Separate generation from execution. Sandbox the execution. Validate before running. And monitor everything the sandbox does.
The fact that your agent is supposed to write and run code doesn't mean you should trust the code it writes.
Up Next
Post 6: Memory & Context Poisoning (ASI06) - What happens when an agent's long-term memory gets corrupted, and how Amazon Bedrock Knowledge Bases, S3 Object Lock, and CloudTrail help you build tamper-evident memory systems.
I would be very interested to hear your thoughts or comments, so please feel free to ping me on LinkedIn or Twitter, or drop them below. If you've dealt with sandboxing agent-generated code in production, I'd love to hear what worked (and what didn't).
Onward!!
Top comments (0)