GHSA-2jq4-q6vv-4cp3: Crawl4AI Remote Code Execution Vulnerability
CRITICALGHSA-2jq4-q6vv-4cp3 is a CRITICAL (CVSS 9.6) remote code execution vulnerability (CWE-59: Improper Link Resolution Before File Access ('Link Following')) affecting Crawl4AI libraries in the PyPI ecosystem. The issue involves arbitrary file write (path traversal) in crawler downloads can lead to rce. Remediation is available by upgrading affected packages to version 0.9.0.
GHSA-2jq4-q6vv-4cp3 at a Glance
Security Impact
CVSS: 9.6
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H).
How to Fix GHSA-2jq4-q6vv-4cp3
To remediate this vulnerability, upgrade all affected Crawl4AI dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install crawl4ai>=0.9.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show crawl4ai
How to Check if You Are Affected
To determine whether your Python environment is affected by GHSA-2jq4-q6vv-4cp3, inspect your installed packages using pip:
pip show crawl4ai
Analyze the terminal output to locate any version of Crawl4AI in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.9.0), your application is affected.
Note: The pip show crawl4ai command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-59: Improper Link Resolution Before File Access ('Link Following'), where the product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource. In Crawl4AI, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-2jq4-q6vv-4cp3 Affected Crawl4AI Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
crawl4ai
|
PyPI |
< 0.9.0
|
0.9.0
|
References
Frequently Asked Questions
What is GHSA-2jq4-q6vv-4cp3?
Which Crawl4AI versions are affected?
What version fixes GHSA-2jq4-q6vv-4cp3?
Is crawl4ai affected?
What is the CVSS score of GHSA-2jq4-q6vv-4cp3?
What CWE is associated with GHSA-2jq4-q6vv-4cp3?
How can I check PyPI dependencies for GHSA-2jq4-q6vv-4cp3?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-2jq4-q6vv-4cp3",
"modified" : "2026-06-18T17:25:13Z",
"published" : "2026-06-18T17:25:13Z",
"aliases" : [ ],
"summary" : "Crawl4AI: Arbitrary file write (path traversal) in crawler downloads can lead to RCE",
"details" : "### Summary\n\nWhen the crawler saves a downloaded file, the destination filename was taken from attacker-influenced input and joined to the downloads directory with no confinement. A filename containing an absolute path (e.g. `/etc/cron.d/evil`) or `../` traversal escaped the downloads directory, giving an arbitrary file write with attacker-controlled contents. Because the written bytes are attacker-controlled, this escalates to remote code execution (overwriting a shell rc-file, `~/.ssh/authorized_keys`, a cron entry, or a Python module on the import path).\n\n### Affected paths\n\nTwo download sinks in `crawl4ai/async_crawler_strategy.py`:\n- HTTP crawler (`AsyncHTTPCrawlerStrategy`): the filename is parsed from the response `Content-Disposition` header by `_extract_filename()` and written via `aiofiles.open(filepath, 'wb')`. Reachable directly via the SDK, and via the unauthenticated Docker `/crawl` endpoint when an `HTTPCrawlerConfig` is supplied.\n- Browser crawler (`AsyncPlaywrightCrawlerStrategy`): the download's `suggested_filename` (controllable by the visited page) is joined to `downloads_path` and written via `download.save_as()`.\n\nThe HTTP-strategy sink is reachable pre-auth on the default Docker deployment; both are reachable for SDK users simply by crawling an attacker-controlled URL. The default Playwright crawl path that does not trigger a download is unaffected.\n\n### Impact\n\nArbitrary file write with attacker-controlled content as the user running the crawler, escalating to remote code execution.\n\n### Fix\n\nBoth sinks now resolve the destination through a single hardened helper (`_safe_download_filepath`) that reduces the attacker-influenced name to a bare basename (dropping absolute paths and `..` components) and re-checks, via `realpath`, that the resolved path stays inside the downloads root (defeating symlink/TOCTOU escapes). A traversal attempt is rejected; normal downloads are unchanged.\n\n### Workarounds\n\n- Upgrade to the patched version (0.9.0).\n- Run the crawler as an unprivileged user with a dedicated, isolated downloads directory on a volume with no sensitive paths writable.\n- Enable authentication (`CRAWL4AI_API_TOKEN`) on the Docker server.\n\n### Credits\n\nY4tacker - reported the Content-Disposition path traversal in the HTTP crawler with a clear PoC and a basename + realpath-containment fix recommendation.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "crawl4ai"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.9.0"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 0.8.9"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/unclecode/crawl4ai/security/advisories/GHSA-2jq4-q6vv-4cp3"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/unclecode/crawl4ai"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-22", "CWE-59" ],
"severity" : "CRITICAL",
"github_reviewed" : true,
"github_reviewed_at" : "2026-06-18T17:25:13Z",
"nvd_published_at" : null
}
}