Forensic security policy card bypassed by a capability wire leading to a brass execution control.

The Sandbox Was Only a Dataclass: PraisonAI’s Policy Gap

The log line looked reassuring: a strict security policy had been selected before the agent executed a command. Seconds later, the child process returned output that the policy explicitly claimed to forbid.

This is a composite laboratory scenario, not a production incident. The important detail is real: in PraisonAI versions through 4.6.77, the default subprocess sandbox accepted a SecurityPolicy object but did not enforce most of its command, path, import, subprocess, or file-write restrictions. The project advisory for CVE-2026-60085 describes a documented strict policy whose controls existed as configuration while the execution backend consulted only a narrow subset.[1]

That is more dangerous than a missing checkbox. A missing control is visible. A control-shaped object can survive design review, configuration review, and even a screenshot of the running service. The operator sees policy. The process sees execve.

The gap was between declaration and enforcement

PraisonAI’s policy surface contained the controls security engineers would expect: blocked and allowed paths, blocked and allowed commands, import restrictions, allow_subprocess, and allow_file_write. The vulnerable SubprocessSandbox still built an argument vector and launched a child process without evaluating those fields. According to the advisory, tests against the same strict-policy instance could start a subprocess, read a blocked file, and execute a blocked destructive command.[1]

The failure is architectural. A policy dataclass is inert until the component immediately before the dangerous operation turns every relevant field into a fail-closed decision. If policy construction happens in one package and process creation in another, reviewers must trace the value to the sink. Finding a well-named strict() method is not evidence that the sink is guarded.

This distinction also matters when threat-modeling agent systems. The model does not need to “break out” of anything if its tool reaches an ordinary host subprocess. As discussed in our earlier analysis of agent trust boundaries, the security boundary is the capability adapter, not the prompt. Prompt filtering can alter model behavior; it cannot constrain kernel-visible authority.

Three weaknesses formed one attack path

The October disclosure set is useful because it shows how individually reviewable choices compose.

First, AgentOS in the TypeScript package defaulted to all-interface binding, an empty API key, and permissive CORS. The documented quick-start path could expose agent metadata, including instructions, and permit unauthenticated chat invocation. The affected npm versions were below 1.7.3.[4]

Second, the MCP HTTP-stream server authenticated only when an API key had been configured. It bound to loopback by default, which limited remote exposure, but a non-browser client with local access could initialize a session and enumerate tools without authorization. If an operator deliberately changed the bind address to 0.0.0.0 without adding a key, that local condition became a network condition. The advisory explicitly limits the result: it confirmed unauthenticated tool enumeration and schema enforcement gaps, not unauthenticated RCE through the broken workflow adapters it tested.[3]

Third, AICoder exposed file-write and command-execution tools to model-generated calls. Before 4.6.78, an absolute path supplied to os.path.join(cwd, path) replaced the intended working directory rather than remaining beneath it. The command path similarly lacked a capability boundary. In a root-running container, the advisory rated this chain at CVSS 9.9 because a model-influenced tool call could write outside the workspace or start an arbitrary command.[2]

The realistic chain is therefore not “prompt injection equals shell.” It is more specific:

  1. An operator exposes an agent control surface with no authentication, or a legitimate user supplies attacker-controlled content to an agent.
  2. The attacker learns available tools and instructions, then shapes input toward a privileged tool call.
  3. A file or command adapter treats model output as an authorization decision.
  4. The configured sandbox either fails to enforce policy or relies on token-level filtering.
  5. The child process inherits the container’s filesystem, identity, environment, and network authority.

That chain explains why the same defensive mistake can appear as prompt disclosure, cost abuse, arbitrary file write, or host command execution. The model is the routing layer. The blast radius comes from the tool and runtime identity.

AI agent capability flow from untrusted input through policy enforcement to isolated execution.
The green path crosses an explicit policy gate. The red path shows the failure mode: a capability reaches execution without an enforceable boundary.

A safe mechanism lab

I reproduced two underlying mechanics in an isolated temporary directory. The lab did not import PraisonAI, contact a network service, start a listener, or touch files outside its own temporary root.

The first check demonstrates path semantics:

workspace = "/tmp/lab/workspace"
requested = "/tmp/lab/outside-canary.txt"
target = os.path.join(workspace, requested)

# target is /tmp/lab/outside-canary.txt, not a child of workspace

An absolute second operand discards the first operand. The correct invariant is not “we called join.” It is “after expansion and canonicalization, the resolved target remains beneath the allowed root.” That check must account for symlinks and must happen close enough to the write to avoid a time-of-check/time-of-use substitution.

The second check used only find and /usr/bin/printf inside the lab:

find /tmp/lab/workspace -maxdepth 0 \
  -exec /usr/bin/printf LAB_EXEC_CANARY {} +

A first-token allowlist accepts this because the executable is find. No shell metacharacter is required. find interprets -exec itself and starts the nested program. PraisonAI tracked this separately as CVE-2026-61434: the prior hardening blocked shell chaining but left an interpreter-like utility in the allowlist.[5]

The observed lab result was concise: the legacy first-token check accepted the command, printf executed with exit code 0, and a validator that rejected find execution actions blocked it. The path test wrote only to the temporary lab sibling, while a canonical containment check rejected the target.

This is the useful lesson from the PoC. A command allowlist is not a list of binary names. It is a list of grammars and transitive capabilities. find, git, package managers, language runtimes, build tools, and document converters can invoke programs or load code without shell punctuation. Removing bash does not remove execution.

What changed in 4.6.78

The 4.6.77-to-4.6.78 comparison adds validation at several relevant sinks. SubprocessSandbox gained checks for blocked command patterns, command allowlists, shell binaries when subprocess execution is disabled, and canonical path restrictions. AICoder gained workspace containment plus command and metacharacter filtering. The MCP HTTP-stream transport now rejects non-loopback binding without an API key, and the sandbox command validator rejects find -exec.[6]

Upgrading to PraisonAI 4.6.78 and npm package 1.7.3 is the immediate action supported by the advisories.[1][4] It is not a substitute for containment. String filters remain weaker than an OS boundary, and a model-facing code tool should be assumed adversarial even when the model is well aligned.

For high-risk agents, move execution into a disposable identity and filesystem namespace. Mount the workspace read-only by default, expose a separate narrow write directory, remove host credentials, disable ambient cloud identity, constrain egress, set resource limits, and require explicit authorization for capability expansion. If a task genuinely needs code execution, a short-lived microVM or hardened container is easier to reason about than a long-lived application process with a denylist.

The broader pattern mirrors the capability analysis in MCP tools and permissions and the attack-chain model in prompt injection and tool abuse. The relevant unit for authorization is each tool invocation, including arguments and resolved resources, not the conversation that preceded it.

Detection should follow capability transitions

Prompt telemetry is useful for investigation, but it is a weak primary detector. An attacker can phrase the same intent many ways, and a benign-looking document can supply the instruction indirectly. Detect the transition from text to authority instead.

At the agent layer, record the requesting principal, session, model, tool name, normalized arguments, policy decision, and approval decision. Keep the raw model reasoning out of security logs unless there is a defined privacy and retention basis. What defenders need is an immutable account of requested capabilities and enforcement results.

At the host or container layer, alert on agent workers spawning interpreters, network clients, privilege tools, or process-launching utilities. Monitor writes outside the declared workspace, especially SSH material, scheduled-task locations, shell startup files, package hooks, and service configuration. Couple those events to agent request IDs so a process tree can be traced back to a tool call.

At the network layer, restrict and log outbound DNS and HTTP from execution workers. A command that cannot reach an attacker-controlled service has less exfiltration and staging value. Do not depend on egress controls alone: destructive local actions and credential theft remain possible.

Finally, test negative guarantees. A security regression suite should prove that blocked paths stay blocked through absolute paths, .., symlinks, hard links where relevant, alternate encodings, and race conditions. Command tests should include utilities with embedded execution features, not just semicolon and pipe characters. Our analysis of symlink-mediated approval failures shows why a displayed path and an accessed object can diverge after approval.

Strategic takeaway

The PraisonAI disclosures are not primarily a story about one agent framework. They expose a recurring review failure: treating policy declaration, model instruction, and execution isolation as if they were interchangeable.

They are not. The model proposes. The policy engine decides. The operating system constrains. When one component silently performs two of those roles, a small default or parsing mistake can collapse the entire chain.

Audit agent platforms from the sink backward. Start at file writes, process creation, network connections, credential access, and tool dispatch. Then prove which control makes the final decision, what identity enforces it, and what happens when the model supplies an argument the developer did not expect. If the answer ends at a dataclass, an allowlist of command names, or a prompt that says “never,” the boundary is still missing.

Sources

  1. PraisonAI advisory: unenforced SecurityPolicy in SubprocessSandbox (published or reviewed October 8, 2026)
  2. PraisonAI advisory: AICoder arbitrary file write and command execution (published or reviewed October 8, 2026)
  3. PraisonAI advisory: unauthenticated MCP HTTP-stream surface (published or reviewed October 8, 2026)
  4. PraisonAI advisory: AgentOS insecure defaults (published or reviewed October 8, 2026)
  5. PraisonAI advisory: find -exec allowlist bypass (published or reviewed October 8, 2026)
  6. PraisonAI v4.6.77 to v4.6.78 source comparison (release comparison accessed October 12, 2026)

💜 Enjoyed this content? Support the blog with USDT (TRC20):

TX7obcjHQbDUXb4mGqoASEu1QFTKT2CFGG

View support page

Paulo Rigonato

Security Engineer | Red Team | Pentest

Offensive security specialist with experience in assessments, pentesting, and Red Team operations. He works in enterprise cybersecurity and continues to share knowledge through this blog.

Certifications: OSCP | eWPTXv2 | ITILv4

💻 GitHub 🔗 LinkedIn

Similar Posts