142 Commits

Author SHA1 Message Date
nightcityblade fadc53fb50 fix(docker): tolerate unavailable IPv6 loopback 2026-07-15 23:36:38 +08:00
ntohidi 055e2ecdc7 release: Crawl4AI v0.9.2
Bump version to 0.9.2 across all references.
Add release notes, demo verification script, and doc updates.

Key highlights:
- Dispatcher: clean up leaked tasks/pages on streaming crawl close (#2071)
- Docker: fix Playground Advanced Config 400 (#2059) and Monitor WebSocket 500 (#2060)
- Docker: include Playwright headless shell (#2067)
- GPU: fix ENABLE_GPU=true builds via CUDA toolkit non-free apt source (#2020)
2026-07-14 11:08:22 +02:00
ntohidi 9ee84cc277 fix(test): update monitor auth test for WS-safe router config
The router-level token_dep was intentionally removed (PR #2063) because
it broke WebSocket upgrades. Update the structural test to assert the
dependency is absent — auth is enforced by AuthGateMiddleware.
2026-07-11 10:24:38 +02:00
ntohidi a94041c339 fix(test): correct regex group name in headless shell test
The test used group("commands") but the regex defines group("artifacts").
2026-07-11 09:58:53 +02:00
Nasrin 6357f8530e Merge pull request #2067 from reallav0/agent/include-playwright-headless-shell
fix(docker): include Playwright headless shell
2026-07-11 09:57:44 +02:00
NhatNguyen b4e155152e test(docker): target Playwright cache copy command 2026-07-10 20:51:08 +07:00
NhatNguyen 1ca47f19de fix(docker): include Playwright headless shell 2026-07-10 16:19:56 +07:00
Pitchfork-and-Torch 1a28980713 fix(docker): playground config type + monitor websocket auth dep
Playground Advanced Config always 400'd because /config/dump requires
type but pyConfigToJson only sent code. Include #cfg-type, align stream
fallback with dump shape, and teach shouldUseStream both nestings.

Monitor dashboard WS /monitor/ws 500'd because router-level token_dep is
HTTP Request-only and cannot inject into WebSocket scopes. Auth remains
on AuthGateMiddleware; admin routes keep require_admin.

Fixes #2059
Fixes #2060
2026-07-09 20:54:07 -04:00
Nasrin 4144bc7e02 Merge pull request #2028 from nightcityblade/fix/issue-2025
fix(docker): cap FastAPI below 0.137
2026-07-07 17:46:26 +02:00
Nasrin f05b84e02c Merge pull request #2039 from unclecode/fix/docker-ui-auth-gate
fix(docker): let dashboard/playground load when auth gate is active
2026-07-07 17:40:22 +02:00
ntohidi b8511616bb test(posture): move dashboard/playground to public UI assertions
The auth gate fix (cbf5e5c) intentionally makes UI shell pages load
without credentials. Update the posture-gate tests to match: move
/dashboard/ and /playground/ from PROTECTED_ENDPOINTS to a new
PUBLIC_UI_PATHS list with an explicit 200 assertion.
2026-07-07 17:35:35 +02:00
Tobias Wallura 9b57c5697f fix(docker): use writable directories for supervisord pid and redis data
- supervisord: write pidfile to /tmp (tmpfs) instead of read-only /app
- redis: set working dir to /var/lib/redis (tmpfs) for RDB snapshots

Fixes "could not write pidfile" and "Failed opening temp RDB file" errors
in read-only root filesystem deployment (docker-compose.yml security hardening).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-01 17:50:06 +02:00
Harsh Mathur 979e8e6e31 fix(docker): authenticate rate limit redis storage 2026-06-29 17:05:40 +00:00
ntohidi cbf5e5c926 fix(docker): let dashboard/playground load when auth gate is active (#2037)
The AuthGateMiddleware blocked UI static pages with 401 because browsers
cannot attach Authorization headers to top-level navigation. The UI shell
serves no data, so it is safe to load without credentials.

- Add public_prefixes to AuthGateMiddleware for prefix-based path bypass
- Register /dashboard, /playground, /static as public prefixes
- Add token input bar to both playground and dashboard UIs
- Replace all bare fetch() calls with authFetch() that attaches Bearer token
- Append ?token= to monitor WebSocket URL (gate already accepts it for WS)

All API/data routes remain fail-closed behind the auth gate.

Closes #2037
2026-06-27 18:32:48 +02:00
nightcityblade c8aee8cd67 fix(docker): make read-only tmpfs writable 2026-06-22 23:13:57 +08:00
nightcityblade 9018f60fbb fix(docker): cap FastAPI below 0.137 2026-06-21 11:12:58 +08:00
unclecode 60886d1a0c release: merge 0.9.0 secure-by-default Docker server hardening into develop
Squash of security/docker-hardening-2026-06 (the full R1-R7 redesign) plus the
three new 0.9.0 security fixes (download path traversal, streaming-path SSRF,
extra_args RCE) and uniform egress pinning across all browser-fetch endpoints.

Version 0.9.0. Supersedes the 0.8.8/0.8.9 non-breaking patches with their
breaking-by-design variants; the 0.8.8/0.8.9 CHANGELOG and credit ledger
entries are preserved. The old exec-based hook_manager.py is removed in favor
of declarative hook_registry.py. Security suite: 309 passed, 1 xfailed.

See deploy/docker/MIGRATION.md for the breaking-change migration guide.
2026-06-18 09:20:07 +00:00
unclecode cdf2ead7ed security: patch proxy SSRF in Docker server (0.8.9)
Docker Release / docker (push) Has been cancelled
Release Pipeline / release (push) Has been cancelled
0.8.8's SSRF check validated the crawl target URL but not the proxy address, so
an unauthenticated /crawl, /crawl/stream, or /crawl/job could route the browser
through a proxy pointing at an internal IP and reach internal services / cloud
metadata. Reported by Geo (geo-chen).

Fix (backward compatible): validate every proxy destination with the same
not-is_global check used for crawl URLs, before the browser is built -
browser_config.proxy, browser_config.proxy_config.server,
crawler_config.proxy_config.server - and strip proxy/DNS-redirecting flags
(--proxy-server / --proxy-pac-url / --proxy-bypass-list / --host-resolver-rules)
from extra_args. A legitimate public proxy still works; configure proxies via
proxy_config (validated), not raw extra_args flags. _enforce_proxy_safety is
called in both crawl handlers (and covers /crawl/job transitively); HTTPException
passthrough added so the 400 is not masked as a 500.

Bump 0.8.8 -> 0.8.9 (__version__ + Dockerfile). 20 new tests; full security
suite 161 pass. Changelog, release blog, README, SECURITY-CREDITS updated.

This vector was already fixed in the upcoming secure-by-default release; 0.8.9
brings it forward because it is an unauthenticated SSRF.
2026-06-04 06:17:41 +00:00
unclecode aa81e8fe7d security: non-breaking hardening patch (0.8.8)
Backward-compatible fixes for the Docker server - features keep working, only
the unsafe behavior is closed. (The secure-by-default redesign is the later
major.)

- SSRF: replace the explicit blocklist with the one rule (reject any resolved
  IP where not ip.is_global) evaluated on embedded IPv4 transition forms too,
  closing the gaps - IPv6 unspecified ::, NAT64 64:ff9b::/96, 6to4 2002::/16,
  v4-mapped. Error messages are now opaque (no resolved-IP leak).
- output_path arbitrary write: harden validate_output_path with realpath
  containment (defeats a symlinked path component) and write via O_NOFOLLOW
  (write_output_file). output_path stays supported.
- LLM base_url key exfil: ignore a request-supplied base_url in /md, /llm,
  /llm/job; the endpoint is always server-derived. Field still accepted (no
  4xx) for compatibility.
- env:SECRET_KEY exfil gadget: LLMConfig refuses env: resolution of protected
  names (SECRET/PASSWORD/PRIVATE substrings, CRAWL4AI*/AWS_SECRET* prefixes,
  SECRET_KEY/REDIS_PASSWORD/TOKEN). Normal provider keys (OPENAI_API_KEY, ...)
  unaffected.
- CRLF log injection: CRLFSafeFilter strips CR/LF/control from log records.
- Webhook header injection: sanitize_webhook_headers (name pattern, no control
  chars, deny hop-by-hop/sensitive) at send time + a WebhookConfig validator
  for early 422.

Bump 0.8.7 -> 0.8.8 (__version__ + Dockerfile C4AI_VER). 30 new behavioral
tests; existing 111 security tests + 112 library config tests still pass.

NOT included (breaking -> deferred to the major): auth-by-default, trust
boundary, declarative hooks, output_path removal, base_url/provider removal,
loopback bind, redis password, TLS-verify-on, CORS, bounded queue. The
exec-hook RCE and unauth-by-default criticals have no non-breaking fix and are
closed only in the major (hooks are already off by default).
2026-06-02 12:39:04 +00:00
unclecode fcaf08b3b3 merge: slot April 2026 security batch (Docker API vulns, SSRF, JWT, file-write, XSS, execute_js) into develop for 0.8.7 2026-06-01 12:40:37 +00:00
Soham Kukreti 76f56af2dd fix: use ensure_ascii=False in MCP bridge json.dumps to preserve CJK characters 2026-05-13 14:10:19 +05:30
Nasrin 244fbf7b58 Merge pull request #1929 from atomic-carpenter/listen-on-all-addressess
docker: listen on all addresses
2026-04-24 17:59:48 +02:00
Atomic Carpenter e06b19ca09 docker: listen on all addresses
Fixes: https://github.com/unclecode/crawl4ai/issues/1928
2026-04-22 00:47:08 +02:00
unclecode 1e25edcb5c fix(security): block IPv6-mapped IPv4 SSRF bypass
Caught during internal review. `http://[::ffff:127.0.0.1]/` bypassed
validate_webhook_url because getaddrinfo returns ::ffff:7f00:1, which
is not in any IPv4 blocklist (127.0.0.0/8) nor IPv6 blocklist (::1/128).

Fix: added _expand_ip_candidates() helper that unwraps IPv4 from
IPv4-mapped (::ffff:X.Y.Z.W, via .ipv4_mapped) and IPv4-compatible
(::X.Y.Z.W, via low-32-bits) IPv6 addresses. Blocklist now checks
both the original IP and the unwrapped IPv4 form.

Added 6 new TestIPv6MappedBypass tests covering:
- Loopback, RFC 1918, link-local (cloud metadata) via ::ffff: mapping
- IPv4-compatible variant (::127.0.0.1)
- Regression test that plain ::1 still blocked

Also updated stale test assertion in test_eval_security_adversarial:
hasattr, type, __build_class__ were removed from hook builtins in
batch 2 but the test still expected hasattr to remain.

DO NOT PUSH until release day.
2026-04-20 10:10:59 +00:00
unclecode f77c0a856f fix(security): SSRF protection on all crawl/md/llm URL entry points
Reported by secsys_codex (2026-04-18): /md, /crawl, /llm endpoints
pass user URLs to crawler.arun() with no private IP validation.

- Add validate_url_destination() to utils.py with opt-out via
  CRAWL4AI_ALLOW_INTERNAL_URLS=true env var for users who need
  to crawl internal services.
- Integrate into validate_url_scheme() (covers all server.py endpoints).
- Add validation at all 4 URL entry points in api.py (handle_llm_qa,
  handle_markdown_request, create_new_task, handle_crawl_request).
- raw: URLs bypass check (inline HTML, no network fetch).
- 16 adversarial + source coverage tests added.
- secsys_codex added to SECURITY-CREDITS.md.

DO NOT PUSH until release day.
2026-04-20 09:42:43 +00:00
Gab c8c2dc319f fix: add LLMTableExtraction to Docker API deserialization allowlist 2026-04-17 15:43:56 -04:00
unclecode 0f20f8bb83 fix(security): batch 2 - JWT secret, eval removal, execute_js, hook sandbox
Fixes for 4 vulnerabilities reported by by111/August829 (2026-04-14):

1. Hardcoded JWT secret (CVSS 9.8): Removed "mysecret" default from
   auth.py. Added weak secret validation (blocklist + min 32 chars).
   Auto-generates ephemeral key when none set.

2. eval() in /config/dump (CVSS 9.1): Replaced eval-based config
   parsing with JSON input {type, params} validated by Pydantic.
   Added authentication. Deleted _safe_eval_config and all AST
   allowlist code.

3. /execute_js endpoint (CVSS 8.1): Disabled by default via
   CRAWL4AI_EXECUTE_JS_ENABLED env var. Added SSRF blocklist on
   destination URL. Removed --disable-web-security from default
   browser args.

4. Hook sandbox escape (CVSS 9.8): Strip __builtins__, __loader__,
   __spec__ from injected module proxies. Removed type, hasattr,
   __build_class__ from allowed builtins.

Also added SECURITY-CREDITS.md tracking all reporters.
30 adversarial tests added.

DO NOT PUSH until release day.
2026-04-15 05:42:14 +00:00
unclecode 7976b45817 fix(security): patch 4 vulns - file write, SSRF, monitor auth, XSS
Fixes for 4 vulnerabilities reported by Jeongbean Jeon (2026-04-13):

1. Arbitrary File Write (CVSS 9.1): /screenshot and /pdf output_path
   now validated via validate_output_path() restricting writes to
   CRAWL4AI_OUTPUT_DIR. Pydantic validator rejects '..' at schema level.

2. SSRF via Webhook (CVSS 8.6): validate_webhook_url() blocks private
   IPs (RFC 1918, loopback, link-local, cloud metadata), dangerous
   hostnames (localhost, metadata.google.internal, host.docker.internal).
   Validated at job submission + send time. follow_redirects=False set.

3. Monitor Auth Bypass (CVSS 6.5): monitor_router now mounted with
   dependencies=[Depends(token_dep)]. WebSocket /ws endpoint checks
   CRAWL4AI_API_TOKEN from query params.

4. Stored XSS (CVSS 6.1): Server-side html.escape() on URLs and errors
   in monitor.py. Client-side escapeHtml() wrapping all innerHTML
   template injections in index.html (active/completed/error lists +
   WebSocket updates).

33 adversarial security tests added.

DO NOT PUSH until release day. Merge to develop + tag + advisory together.
2026-04-13 11:29:54 +00:00
Nasrin 3d02d75edb Merge pull request #1852 from hafezparast/feat/maysam-arun-many-config-list-1837
feat: expose arun_many config-list support in Docker API (#1837)
2026-04-06 10:26:44 +02:00
unclecode e326da9166 fix(security): complete AST sandbox escape remediation (CVSS 9.8)
Addresses the gi_frame.f_back chain exploit reported by Song Binglin (q1uf3ng).

- Delete _safe_eval_expression() and _SAFE_EVAL_BUILTINS entirely from
  extraction_strategy.py. Dead security-sensitive code is a liability.
  The eval path was already disabled; this removes the function itself.
- Fix hook_manager.py module injection: replace broken exec("import X", ns)
  pattern (silently failed due to missing __import__) with direct module
  injection. Sanitize asyncio to strip subprocess access (RCE vector).
- Add startup warning when CRAWL4AI_API_TOKEN is unset (all endpoints
  unauthenticated).
- Expand adversarial test suite to 87 tests: hook sandbox escapes,
  asyncio.subprocess RCE verification, end-to-end exploit payload from
  vuln report, dead code deletion checks, codebase eval/exec audit.
2026-03-31 13:01:57 +00:00
unclecode 2fc39cbe89 fix(security): remove eval() from computed fields, harden config deserializer
- Disable eval() in _compute_field expression path (RCE vector via untrusted input).
  Expression key now logs warning and returns default; function key still works.
- Harden _safe_eval_config in server.py with name/attribute allowlists,
  block lambdas, generators, comprehensions in constructor args.
- Remove getattr/setattr from hook_manager allowed builtins (sandbox escape vectors).
- Add 67 adversarial security tests covering all eval/exec attack surfaces.

Closes #1886, closes #1855
2026-03-31 12:02:43 +00:00
ntohidi bcbccbea2f docs: update version references to 0.8.6 in README and Docker docs 2026-03-30 10:57:13 +08:00
Nasrin fb24ee592e Merge pull request #1851 from hafezparast/fix/maysam-mcp-sse-asgi-1850
fix: MCP SSE endpoint crash on Starlette >=0.50 (#1850)
2026-03-24 11:17:35 +01:00
hafezparast 8995c1bbd6 feat: expose arun_many config-list support in Docker API (#1837)
The /crawl endpoint now accepts an optional crawler_configs field
(list of CrawlerRunConfig dicts) alongside the existing crawler_config.
When provided with multiple URLs, each config is deserialized and passed
as a list to arun_many(), enabling per-URL configuration with url_matcher
patterns. Single-URL requests and requests without crawler_configs are
unchanged (backward compatible).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 09:56:53 +08:00
hafezparast 219416e49d fix: MCP SSE endpoint crash on Starlette >=0.50 (#1850)
Starlette's Route wraps async functions in request_response(), calling
handler(request) instead of handler(scope, receive, send). This broke
the MCP SSE endpoint which needs raw ASGI access. Fix: use a callable
class instead of an async function — Route passes class instances
through as raw ASGI apps without wrapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 08:55:41 +08:00
ntohidi bb6406a2d0 release: Crawl4AI v0.8.5
Bump version to 0.8.5 across all references (Dockerfile, README,
Docker README, blog index, __version__.py).

Add release notes, blog post, demo verification script (13 real-crawl
tests), and releases directory entry.

Key highlights:
- Anti-bot detection with 3-tier proxy escalation
- Shadow DOM flattening
- Deep crawl cancellation
- Config defaults API
- 60+ bug fixes and critical security patches
2026-03-16 18:46:05 +08:00
ntohidi f6ab207e25 fix: remove shared LOCK contention in monitor to prevent pod deadlock (#1754)
The monitor's update_timeline(), get_health_summary(), and
get_browser_list() all acquired the crawler pool's global LOCK to read
pool stats. That same lock is held during slow browser start/close
operations (get_crawler, janitor, close_all), causing the monitor to
block indefinitely and the pod to become unresponsive after sustained
crawling.

Replaced all three lock acquisitions in monitor.py with a lock-free
get_pool_snapshot() in crawler_pool.py that returns shallow dict copies.
Under CPython's GIL, dict.copy() and len() are atomic — safe for
read-only monitoring with at most slightly stale counts.
2026-03-13 12:17:52 +08:00
Nasrin 648f36b622 Merge pull request #1827 from hafezparast/fix/maysam-llm-provider-redis-config-1611-1817
fix: /llm per-request provider override, Redis config from host/port/password (#1611, #1817)
2026-03-13 03:59:28 +01:00
unclecode a73bc1c076 fix: MCP SSE endpoint crash — mount via raw ASGI Route (#1594)
Replace @app.get() with starlette.routing.Route() for the SSE handler.
The MCP SDK's SseServerTransport calls raw ASGI (scope, receive, send)
internally, which conflicts with Starlette's middleware wrapping.

Also update CONTRIBUTORS.md for PR #1829.
2026-03-12 11:22:48 +00:00
hafezparast 480d938f67 fix: /llm per-request provider override, Redis config from host/port/password (#1611, #1817)
- #1611: /llm GET endpoint hardcoded server's LLM_PROVIDER. Added optional
  provider, temperature, base_url query params with fallback to server config.
  Consistent with /md and /llm/job endpoints.
- #1817: Redis connection used non-existent config["redis"]["uri"]. Now builds
  URL from host/port/password/db/ssl config fields with REDIS_HOST, REDIS_PORT,
  REDIS_PASSWORD environment variable overrides.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:53:04 +08:00
unclecode 3a75dd3f4c fix: batch fix for 10 open issues (#1520, #1489, #1374, #1424, #1183, #1354, #880, #1031, #1251, #1758)
- #1520: Preserve trailing slashes in URL normalization (RFC 3986 compliance)
- #1489: Preserve query parameter key casing in normalize_url
- #1374: Close NamedTemporaryFile handle before reopening (Windows fix)
- #1424: Fix CosineStrategy returning empty results (delimiter fallback + at_least_k >= 1)
- #1183: Fix extract_xml_data regex matching tag names in prose text
- #1354: Make import_knowledge_base async (fix asyncio.run in running loop)
- #880: Fix 404 sample_ecommerce.html gist URL in docs (6 occurrences)
- #1031: Make Docker playground code editor resizable with overflow-auto
- #1251: Add DEFAULT_CONFIG with deep-merge in load_config to prevent KeyError crashes
- #1758: Change screenshot stitching format from BMP to PNG
2026-03-07 09:47:38 +00:00
unclecode 7c0cc3ed88 fix: batch merge of community PRs (#1622, #1786, #1796, #1795, #1798, #1734, #1290, #1668)
Bug fixes:
- Verify redirect targets are alive before returning from URL seeder (#1622)
- Wire mean_delay/max_range from CrawlerRunConfig into dispatcher rate limiter (#1786)
- Use DOMParser instead of innerHTML in process_iframes to prevent XSS (#1796)

Security/Docker:
- Require api_token for /token endpoint when configured (#1795)
- Deep-crawl streaming now mirrors Python library behavior via arun() (#1798)

CI:
- Bump GitHub Actions to latest versions - checkout v6, setup-python v6,
  build-push-action v6, setup-buildx v4, login v4 (#1734)

Features:
- Support type-list pipeline in JsonCssExtractionStrategy for chained
  extraction like ["attribute", "regex"] (#1290)
- Add --json-ensure-ascii CLI flag and JSON_ENSURE_ASCII config setting
  for Unicode preservation in JSON output (#1668)
2026-03-07 08:45:11 +00:00
unclecode 761664d29e fix: add TTL expiry for Redis task data to prevent memory growth (#1730)
From PR #1730 by @hoi
2026-03-07 06:17:58 +00:00
unclecode d229beeaf8 fix: add wait_for_images option to screenshot endpoint (#1792)
From PR #1792 by @Br1an67
2026-03-07 06:15:54 +00:00
unclecode c0912f7234 feat: add avoid_ads/avoid_css resource filtering and pool release lifecycle
Add opt-in BrowserConfig flags (avoid_ads, avoid_css) for blocking ad/tracker
domains and CSS resources at the browser context level. Refactor crawler pool
with release_crawler() and active_requests tracking to prevent janitor from
closing browsers with in-flight requests. Add proper finally blocks to all
Docker API/server handlers. Update docs for new config options.

Inspired by #1689.
2026-02-25 07:12:28 +00:00
Nasrin 8d35d17d01 Merge pull request #1722 from YuriNachos/fix/issue-1652-md-docstring
fix: Add docstring to MCP tool 'md' endpoint
2026-02-25 06:00:09 +01:00
Nasrin 7435a1654c Merge pull request #1771 from hafezparast/claude/check-fork-sync-S9SSz
Claude/check fork sync s9 s sz
2026-02-23 06:28:10 +01:00
Claude 0e9b677870 Fix MCP bridge httpx timeout: add configurable timeout parameter
The httpx.AsyncClient() default 5s timeout causes TimeoutException on
slow LLM-backed endpoints. The exception bypasses the HTTPStatusError
handler, propagating as an unhandled error to the MCP framework.

- Add `timeout` parameter to `attach_mcp()` (default None = no limit)
- Pass timeout through to `_make_http_proxy()` and `httpx.AsyncClient()`
- Catch `httpx.TimeoutException` and surface it as HTTP 504

Fixes #1769

https://claude.ai/code/session_01LpranMwFBtQU7kFrV5EHAB
2026-02-23 02:10:04 +00:00
unclecode 0aacafed0a Merge PR #1463: Add configurable device_scale_factor for screenshot quality 2026-02-06 09:19:42 +00:00
unclecode 0104db6de2 Fix critical RCE via deserialization and eval() in /crawl endpoint
- Replace raw eval() in _compute_field() with AST-validated
  _safe_eval_expression() that blocks __import__, dunder attribute
  access, and import statements while preserving safe transforms
- Add ALLOWED_DESERIALIZE_TYPES allowlist to from_serializable_dict()
  preventing arbitrary class instantiation from API input
- Update security contact email and add v0.8.1 security fixes to
  SECURITY.md with researcher acknowledgment
- Add 17 security tests covering both fixes
2026-01-30 08:46:32 +00:00