1. Executive summary
Eleven vulnerabilities across five GitHub Security Advisories affect the Crawl4AI Docker API server, an open-source crawling and LLM-extraction service that ships unauthenticated by default. The highest-scored issue is a hardcoded JWT signing key default of "mysecret" in the public source (CVSS 9.8 as published by the advisory), which lets anyone forge valid authentication tokens, followed by an arbitrary file write through the /screenshot and /pdf output_path parameter (CVSS 9.1) that reaches code execution where the container runtime user can write to executable or cron locations. Four separate SSRF paths reach cloud instance metadata at 169.254.169.254, including a proxy-configuration bypass that passes crawl-URL validation and IPv6 transition-form encodings that defeat a naive blocklist. No CISA KEV entry and no EPSS score were available for this item, and every CVSS value below is as published by the GitHub advisory rather than independently re-scored. Any client running the Crawl4AI Docker image on a network-reachable host should treat this as an emergency upgrade to 0.8.9.
2. Regulatory framing
No specific DORA/NIS2 article is directly engaged by this item.
This is a pre-exploitation vulnerability disclosure. There is no confirmed incident, no confirmed data loss, and no evidence in the source material that any of these flaws has been exploited in the wild. The DORA incident articles (Art. 17, Art. 18, Art. 19) and NIS2 Art. 23 are triggered by an actual ICT-related incident, not by the availability of a patch. DORA Art. 28 to Art. 30 address contractual arrangements with ICT third-party service providers; Crawl4AI is an open-source component distributed without a support contract, so those provisions do not attach on the facts here. If a client establishes that the metadata SSRF or the file write was exercised against its own deployment, that finding changes the position and the incident-reporting clock starts from the moment of detection.
3. Technical analysis & attack chain
The Crawl4AI Docker API server is unauthenticated by default (jwt_enabled: false). Unless CRAWL4AI_API_TOKEN is set, every endpoint below is reachable without credentials. The patched version is 0.8.9: 0.8.8 closed the LLM credential-exfiltration issues and 0.8.9 closed the proxy SSRF bypass.
Triage table
Ordered by published CVSS, since no KEV entry and no EPSS score were available for any entry. Where the source gives no CVSS, the cell reads n/a.
| CVE / advisory | component | CVSS | KEV / EPSS | why it matters |
|---|---|---|---|---|
| CVE-2026-56266 (GHSA-365w-hqf6-vxfg) | JWT signing key | 9.8 | no KEV entry; no EPSS available | default key "mysecret" is in the public source; any token can be forged |
| CVE-2026-56266 (GHSA-365w-hqf6-vxfg) | /screenshot, /pdf output_path |
9.1 | no KEV entry; no EPSS available | arbitrary filesystem write; overwrites server files or writes anywhere the appuser can write |
| GHSA-7cx2-g3h9-382p | /screenshot, /pdf output_path (symlink / TOCTOU) |
n/a | no KEV entry; no EPSS available | the 0.8.7 containment check was string-only and did not resolve symlinks; a symlinked path component escapes the output directory |
| CVE-2026-56266 (GHSA-365w-hqf6-vxfg) | /crawl/job, /llm/job webhook URL |
8.6 | no KEV entry; no EPSS available | webhook URLs accept RFC 1918, loopback, link-local and metadata addresses |
| CVE-2026-56266 (GHSA-365w-hqf6-vxfg) | /crawl, /crawl/stream, /md, /llm |
8.6 | no KEV entry; no EPSS available | primary crawl entry points fetch arbitrary URLs; IPv6-mapped IPv4 evades a literal-hostname check |
| GHSA-6qhc-x826-342c | browser_config.proxy_config.server and related proxy fields |
n/a | no KEV entry; no EPSS available | a benign crawl URL passes validation while the proxy routes Chromium to an internal address |
| GHSA-4qqr-vv2q-cmr5 | validate_webhook_url / validate_url_destination |
n/a | no KEV entry; no EPSS available | NAT64, 6to4, IPv4-mapped, IPv4-compatible and the IPv6 unspecified address all bypassed the CIDR blocklist |
| CVE-2026-56266 (GHSA-365w-hqf6-vxfg) | /execute_js |
8.1 | no KEV entry; no EPSS available | arbitrary JavaScript in the server browser with --disable-web-security enabled |
| GHSA-f989-c77f-r2cq, GHSA-mw9f-q68p-364x | /md, /llm, /llm/job base_url and env: token resolution |
n/a | no KEV entry; no EPSS available | exfiltrates LLM provider keys and server secrets including SECRET_KEY |
| CVE-2026-56266 (GHSA-365w-hqf6-vxfg) | /monitor/* |
6.5 | no KEV entry; no EPSS available | monitor router mounted without token_dep; destructive endpoints such as /monitor/actions/cleanup are open |
| CVE-2026-56266 (GHSA-365w-hqf6-vxfg) | monitor dashboard | 6.1 | no KEV entry; no EPSS available | stored XSS via crafted crawl URLs rendered through innerHTML |
| GHSA-7cx2-g3h9-382p | logging, outbound webhook headers | n/a | no KEV entry; no EPSS available | CRLF log injection (CWE-117) and webhook request-header injection (CWE-93/CWE-113) |
Confirmed exploitation path
- Reach the API. The Docker API server is unauthenticated by default. If
CRAWL4AI_API_TOKENis unset, no credential is required for any step that follows. If JWT is enabled butSECRET_KEYwas left at the default, the attacker signs a token with"mysecret"and proceeds authenticated. - Retrieve cloud credentials by SSRF.
POST /crawlwith a URL pointing at169.254.169.254makes the server fetch instance metadata. The advisory names AWS IMDSv1 as the target for IAM credential tokens. The same applies to/crawl/stream,/mdand/llm, and to webhook URLs supplied to/crawl/joband/llm/job. - Defeat the blocklist where one is present. Encode the internal address in an IPv6 transition form. The advisory lists
::,64:ff9b::a9fe:a9fe(NAT64),2002:a9fe:a9fe::(6to4),::ffff:169.254.169.254(IPv4-mapped) and::a9fe:a9fe(IPv4-compatible) as forms that resolved to blocked ranges but were not caught. The pre-fix error message also echoed the resolved internal IP, giving the attacker a DNS oracle. - Bypass the crawl-URL check entirely with a proxy. Send
/crawlwith a benign, validation-passing URL and aproxy_config.serverpointing at an internal address. Chromium routes all requests through that proxy. For plain-HTTP targets the proxy receives the full request and returns any content, which the API returns verbatim inresults[0].html,cleaned_htmlormarkdown. The same unchecked surface coversbrowser_config.proxy(deprecated),crawler_config.proxy_config.server, and the--proxy-server,--proxy-pac-url,--proxy-bypass-listand--host-resolver-rulesflags inbrowser_config.extra_args. - Exfiltrate LLM provider keys and server secrets.
POST /md,/llmor/llm/jobwithbase_urlset to an attacker-controlled host. The server attaches its configured provider API key to the outbound request, so the key arrives at the attacker endpoint. Separately,LLMConfig(api_token="env:NAME")resolvesNAMEfrom the server environment viaos.getenv; because request bodies deserialise intoLLMConfig, an attacker setsapi_token="env:SECRET_KEY"orenv:REDIS_PASSWORDand, paired with the attackerbase_url, reads that secret out. ReadingSECRET_KEYyields token forgery. - Write files, and potentially execute code.
POST /screenshotorPOST /pdfwith anoutput_pathoutside the intended directory. The pre-fix code accepted arbitrary filesystem paths with no validation, so the attacker overwrites server files (denial of service) or writes anywhere the appuser can write. The symlink/TOCTOU variant is the sharper edge: the 0.8.7validate_output_pathcheck was string-only and did not resolve symlinks, so a symlinked path component inside the output directory redirected the write outside it, and the finalopen()followed the symlink. On a deployment where the runtime user can write executable or cron locations, that is an arbitrary-write to code-execution primitive. - Execute JavaScript in the server browser.
POST /execute_jsruns arbitrary JavaScript with--disable-web-securityset in the default browser arguments, combining script execution with the SSRF capability above. - Reach the monitor plane without credentials. The monitor router was mounted without the
token_depdependency, so every monitoring endpoint, including destructive ones such as/monitor/actions/cleanup, was reachable unauthenticated. The WebSocket endpoint/monitor/wshad no token check either. Crafted crawl URLs stored and rendered throughinnerHTMLin the monitor dashboard give stored XSS against an operator viewing that dashboard.
Fix detail and discrepancies
The primary advisory describes the file-write fix as adding validate_output_path() to restrict writes to CRAWL4AI_OUTPUT_DIR (/tmp/crawl4ai-outputs by default) plus a Pydantic field_validator rejecting .. traversal sequences. GHSA-7cx2-g3h9-382p describes the same control differently: it states the 0.8.7 check was string-only and did not resolve symlinks, and that the fix now resolves the real path of the parent, re-checks containment, and writes with O_NOFOLLOW via write_output_file. Both descriptions cover the same control at different fix stages; the symlink-resolving behaviour is the one that matters for containment. A second discrepancy sits in the SSRF fix: the primary advisory says IPv6-mapped IPv4 addresses are normalised to IPv4 form before the blocklist check, while GHSA-4qqr-vv2q-cmr5 says the CIDR blocklist was replaced by a single not ip.is_global rule evaluated on the address and every embedded IPv4 transition form. Treat the is_global rule as the current behaviour.
The SSRF fixes apply at request entry, not only at fetch time, and raw:// URLs are skipped. Webhook validation runs at both job submission and send time with follow_redirects=False. /execute_js is disabled by default behind CRAWL4AI_EXECUTE_JS_ENABLED, and --disable-web-security was removed from the default browser arguments. The LLM endpoints now ignore a request-supplied base_url and derive the endpoint server-side from the provider name; the field is still accepted so no 4xx is returned. LLMConfig refuses env: resolution of protected names (names containing SECRET, PASSWORD or PRIVATE, prefixes CRAWL4AI* and AWS_SECRET*, and SECRET_KEY, REDIS_PASSWORD, TOKEN). Normal provider keys such as OPENAI_API_KEY are unaffected.
Confidence caveats
All of the above rests on the five GitHub Security Advisories listed in section 7. There is no independent vendor research, no telemetry and no exploitation reporting in the source material. The CVSS values are the advisory's own and have not been corroborated against NVD. The researcher credits in the primary advisory (Jeongbean Jeon, wulonchia, by111/August829, secsys_codex, Velayutham Selvaraj, IcySun and Yashon) are individual reporters, not tracked threat actors, and no MITRE ATT&CK group profile applies. Nothing in the source material indicates ransomware, extortion or data theft.
4. Mitigation & containment
P1, within 24 hours
- Upgrade the Crawl4AI Docker image to 0.8.9. This is the floor: 0.8.8 closes the LLM credential-exfiltration issues and 0.8.9 closes the proxy SSRF bypass. Pin the image by digest rather than tag.
- If an upgrade cannot be completed today, set
CRAWL4AI_API_TOKENto a long random value so the API is no longer unauthenticated, and setSECRET_KEYto a random value of at least 32 characters. Do not leave JWT enabled with the default key. - Cut the container's route to link-local address space. Block
169.254.0.0/16at the egress firewall or the container network policy, and enforce IMDSv2 on any AWS instance hosting the service so a metadata fetch without a session token fails. - Stop exposing the Docker API beyond the host. Bind it to loopback or a dedicated internal network instead of
0.0.0.0, and remove any public ingress rule pointing at the API port. - Hunt for prior exploitation in API access logs: unauthenticated requests to
/monitor/*or/monitor/ws,output_pathvalues containing..or absolute paths outsideCRAWL4AI_OUTPUT_DIR, and crawl or webhook URLs resolving to169.254.169.254, RFC 1918, loopback or link-local addresses.
P2, within 72 hours
- Set
CRAWL4AI_EXECUTE_JS_ENABLED=falseexplicitly rather than relying on the patched default. - Run the container with a read-only root filesystem and mount a dedicated, non-executable volume for
CRAWL4AI_OUTPUT_DIR. This removes the code-execution edge from the file-write primitive even on an unpatched build. - Apply an egress allowlist to the container so it can reach only the sites and LLM providers it needs. This is the control that survives an unpatched SSRF.
- Rotate every secret that was present in the container environment: LLM provider API keys,
SECRET_KEY,REDIS_PASSWORD, and anyAWS_SECRET*value. Rotate them after the upgrade, not before, or the new values are exposed by the same flaw. - Review the log pipeline for forged entries. The CRLF log injection issue let user-controlled URLs and errors embed CR/LF and fabricate additional log lines, so any log-based alerting that ran during the exposure window needs re-checking against raw source records.
P3, within 7 days
- Inventory every Crawl4AI deployment, including development, CI and internal tooling instances. The unauthenticated default means a forgotten instance is as exposed as a production one.
- Remove sensitive secrets from the container environment entirely. The
env:resolution path exists because secrets sit alongside provider keys in the same environment. - Deploy the detection rules in section 6 and confirm they fire against a controlled test request before relying on them.
5. Indicators of compromise
No atomic indicators of compromise are available in the source material. There are no hashes, no domains, no IP addresses and no file paths belonging to an attacker. The sources describe observable behaviours only, and those are listed below.
Behavioural indicators
| behaviour | where to observe | confidence |
|---|---|---|
Requests to /monitor/*, including /monitor/actions/cleanup, with no authentication token |
API access logs, reverse proxy logs | high, documented in GHSA-365w-hqf6-vxfg |
WebSocket connection to /monitor/ws with no token check |
reverse proxy logs | high, documented in GHSA-365w-hqf6-vxfg |
output_path values containing .. or resolving outside CRAWL4AI_OUTPUT_DIR |
API access logs | high, documented in GHSA-365w-hqf6-vxfg and GHSA-7cx2-g3h9-382p |
Crawl or webhook URLs resolving to 169.254.169.254, RFC 1918, loopback or link-local addresses |
API access logs, egress firewall logs | high, documented in GHSA-365w-hqf6-vxfg |
URL fields containing IPv6 transition forms: ::ffff:, 64:ff9b::, 2002:a9fe, ::a9fe:a9fe, or the bare unspecified address :: |
API access logs | high, documented in GHSA-4qqr-vv2q-cmr5 |
browser_config.proxy_config.server, browser_config.proxy or crawler_config.proxy_config.server set to an internal address, or --proxy-server, --proxy-pac-url, --proxy-bypass-list, --host-resolver-rules present in browser_config.extra_args |
API access logs | high, documented in GHSA-6qhc-x826-342c |
base_url in a /md, /llm or /llm/job request pointing at a host that is not the configured LLM provider |
API access logs | high, documented in GHSA-f989-c77f-r2cq |
api_token values of the form env:NAME, particularly env:SECRET_KEY or env:REDIS_PASSWORD |
API access logs | high, documented in GHSA-f989-c77f-r2cq |
Outbound HTTP from the container to an unexpected host carrying an Authorization header |
egress proxy logs | medium, inferred from the documented base_url behaviour |
| CR or LF characters in logged URL or error fields producing additional log lines | log ingestion pipeline | medium, documented in GHSA-7cx2-g3h9-382p |
Outbound webhook requests carrying attacker-supplied Host, Content-Length, Transfer-Encoding, Authorization or Cookie headers |
egress logs | medium, documented in GHSA-7cx2-g3h9-382p |
6. Detection
The YARA rule below hunts deployed Crawl
Published via PulseTrace — Adverse Trace threat intelligence.