Crawl4AI, changedetection.io, Dagster, and Django file/query boundary batch¶
Source: GitHub Security Advisories REST API, updated 2026-06-05.
This batch is durable because the advisories map to reusable offensive testing patterns: browser automation APIs crossing file:// into host file reads, backup/XML import paths crossing into local file disclosure, workflow gRPC notebook paths crossing directory boundaries, and Django ORM helper kwargs crossing into SQL syntax. Use the workflows only in authorized labs or explicitly scoped assessments.
What changed¶
- Crawl4AI Docker API
file://local file inclusion — GHSA-vx9w-5cx4-9796 / CVE-2026-26217:crawl4aibefore0.8.0acceptedfile://URLs on Docker API endpoints including/execute_js,/screenshot,/pdf, and/html. When those endpoints are reachable without a strong access gate, a caller can make the browser/runtime read files from the server-side container or host filesystem context. - Crawl4AI Docker API hooks RCE — GHSA-5882-5rx9-xgxp / CVE-2026-26216: the Docker API
/crawlendpoint accepted ahooksparameter containing Python code executed withexec(), and allowed__import__, letting unauthenticated callers on exposed API deployments import modules and execute commands. - changedetection.io backup-restore local file read — GHSA-8757-69j2-hx56 / CVE-2026-43891: affected
changedetection.ioversions trusted restored watch snapshot paths from backup ZIP contents. A crafted backup could preserve attacker-controlled watch/history metadata that later resolves to local files readable by the application process. - changedetection.io XML/RSS XPath XXE — GHSA-v7cp-2cx9-x793 / CVE-2026-41895: XML/RSS include-filter parsing used
lxmlXML parsing without explicitly disabling external entity resolution in the affected path. Watches that parse attacker-controlled XML/RSS with XPath include filters may expose local-file or network-backed entity behavior, depending on runtime parser defaults. - Dagster gRPC notebook local file inclusion — GHSA-h7x8-jv97-fvvm / CVE-2025-51481: Dagster before
1.10.16let callers with access to the gRPC server supply traversal sequences inExternalNotebookData.notebook_path, bypassing the intended notebook extension check and reading arbitrary files reachable by the Dagster process. - Django ORM dictionary-expansion SQL injection family — GHSA-6w2r-r2m5-xq5w / CVE-2025-57833, GHSA-hpr9-3m2g-3j9p / CVE-2025-59681, GHSA-frmv-pr5f-9mcr / CVE-2025-64459, GHSA-rqw2-ghq9-44m7 / CVE-2025-13372, GHSA-gvg8-93h5-g6qq / CVE-2026-1287, and GHSA-6426-9fv3-65x8 / CVE-2026-1312: multiple Django releases fixed SQL injection primitives where attacker-influenced dictionaries were expanded into ORM helper kwargs such as
QuerySet.annotate(),QuerySet.alias(),QuerySet.aggregate(),QuerySet.extra(),values(),values_list(),FilteredRelation,Q(),filter(),exclude(), andget()across backend-specific paths. - Django PostGIS
RasterFieldband-index SQL injection — GHSA-mwm9-4648-f68q / CVE-2026-1207: PostGIS raster lookups accepted a user-influenced band index that could cross into SQL syntax when applications expose raster lookup parameters.
Operator triage¶
- Search asset inventories for exposed Crawl4AI Docker/API deployments, AI crawling services, screenshot/PDF/HTML render workers, or agent tools that wrap Crawl4AI endpoints.
- Prioritize Crawl4AI instances where
/crawl,/execute_js,/screenshot,/pdf, or/htmlare reachable from user-controlled jobs, unauthenticated network positions, shared workspaces, or low-trust tenants. - Search changedetection.io targets for administrative backup restore access, watch import workflows, XML/RSS monitors, and XPath include filters applied to attacker-controlled feed responses.
- Search orchestrator inventories for Dagster gRPC servers, user-code deployments, notebooks exposed through Dagster metadata, and network paths where a tester or lower-privileged service can call notebook data APIs.
- Search Django code for patterns that unpack request, JSON, form, GraphQL, or tenant-config dictionaries directly into ORM methods:
annotate(**data),alias(**data),aggregate(**data),extra(**data),filter(**data),exclude(**data),get(**data),Q(**data), orFilteredRelation(..., **data). - For Django, prioritize PostgreSQL, MySQL, and MariaDB apps that expose advanced filtering, reporting, analytics, saved-search, data-grid, or admin-builder features where users influence field aliases, connectors, or annotation names.
Replayable validation boundaries¶
Crawl4AI file:// API proof¶
Use a disposable Crawl4AI lab or an explicitly approved non-production service. Do not request real secrets.
- Create an inert marker file in the Crawl4AI runtime context, for example
/tmp/skillz-crawl4ai-marker.txtwith a unique string. - Send the target API path a server-side URL of
file:///tmp/skillz-crawl4ai-marker.txt. For/execute_js, use a harmless script that returns page/body text; for/html, request the rendered content directly. - Vulnerable result: the API response, screenshot/PDF text extraction, or HTML output contains the marker content from the local filesystem.
- Capture endpoint, auth state, package version, container/runtime identity, exact
file://URL, and marker-only output. Avoid/etc/passwd,/proc/self/environ, SSH keys, cloud credentials, and application config files.
Crawl4AI hooks execution canary¶
- In a lab Docker API instance, enable only the minimum feature set needed to reproduce
/crawlhooks behavior. - Submit a
/crawlrequest whose hook code does not run OS commands; instead have it import a harmless module and write/return a marker value inside the crawler result path. - Vulnerable result: hook code supplied by the API caller executes server-side and can import modules through
__import__. - Capture endpoint exposure, auth state, hook configuration, package version, and marker-only output. Do not include command-execution payloads in public reports.
changedetection.io crafted backup local-read proof¶
Keep this in a lab unless the client explicitly approves backup-restore testing.
- Create a lab changedetection.io instance with a marker file outside the watch data directory.
- Build a backup ZIP that restores a watch directory and a controlled
history.txt/snapshot reference matching the advisory's trust-boundary pattern. - Restore the backup through the normal UI/API path using an authorized admin-equivalent account.
- Trigger the restored watch history/snapshot display path.
- Vulnerable result: the application reads or displays marker content from outside the intended restored watch data.
- Capture backup structure, restored watch UUID, version, request path, and marker-only evidence. Do not use production backup archives or sensitive local paths.
changedetection.io XML/RSS XXE canary¶
Use a canary XML feed and a marker file; do not exfiltrate real files.
- Host a controlled XML/RSS response with a DOCTYPE/entity referencing only the marker file path available in the lab runtime.
- Configure a watch that fetches the canary feed and enables an XPath include filter, forcing the XML helper path.
- Trigger the watch fetch and compare parser output, diff text, logs, and outbound callbacks if using a network entity canary.
- Vulnerable result: the marker entity is expanded or the parser attempts the controlled external lookup.
- Capture parser behavior, dependency versions, watch configuration, and marker-only output.
Dagster gRPC notebook path traversal proof¶
Only test gRPC paths where access is in scope.
- Create a Dagster lab with a legitimate notebook path and a marker file outside the notebook directory.
- Call the same notebook-data gRPC method used by the environment with a traversal value in
notebook_path, adjusted to reach the marker file while preserving any extension constraints the target enforces. - Vulnerable result: the response returns marker file content rather than rejecting the path as outside the notebook root.
- Capture Dagster version, gRPC reachability, caller identity, supplied
notebook_path, and marker-only response.
Django ORM kwargs injection proof¶
Prefer source review plus a tiny canary harness over blind production payloads.
- Identify a route where user-controlled dictionaries flow into ORM kwargs by expansion.
- Reproduce in a lab with the same Django minor version and database backend.
- Use a harmless alias/connector canary that changes generated SQL shape without reading unrelated rows or modifying data. Confirm through query logging, generated SQL inspection, or a controlled fixture table.
- Vulnerable result: attacker-controlled dictionary keys or
_connectorvalues appear as SQL syntax rather than quoted identifiers/validated operators. - Capture code path, Django version, database backend, generated SQL excerpt, and controlled fixture result. Do not run destructive SQL or time-based payloads against production.
Reporting heuristics¶
- Frame Crawl4AI findings as render/crawler API local-file boundary issues. Include whether the vulnerable endpoint is unauthenticated, tenant-accessible, or shielded by an internal gateway.
- Frame changedetection.io backup findings as trusted backup metadata crossing into application file reads. Evidence should use a purpose-built backup and marker file only.
- Frame changedetection.io XXE findings as untrusted feed XML parser configuration issues. Record the exact watch settings and dependency/parser behavior; avoid overstating impact when entity expansion is blocked by runtime defaults.
- Frame Dagster findings as workflow control-plane gRPC path containment issues. Distinguish internet exposure, internal service exposure, and authenticated developer-only reachability.
- Frame Django findings as user-controlled ORM dictionary expansion issues, not generic SQL injection. The decisive evidence is the user-controlled dictionary key/value crossing into generated SQL syntax on an affected Django/database combination.
- Keep all proofs marker-only, non-destructive, and scoped to approved systems.
Sources¶
- GitHub Advisory Database: GHSA-vx9w-5cx4-9796 / CVE-2026-26217
- GitHub Advisory Database: GHSA-5882-5rx9-xgxp / CVE-2026-26216
- Crawl4AI upstream advisory: unclecode/crawl4ai GHSA-vx9w-5cx4-9796
- GitHub Advisory Database: GHSA-8757-69j2-hx56 / CVE-2026-43891
- changedetection.io upstream advisory: dgtlmoon/changedetection.io GHSA-8757-69j2-hx56
- GitHub Advisory Database: GHSA-v7cp-2cx9-x793 / CVE-2026-41895
- changedetection.io upstream advisory: dgtlmoon/changedetection.io GHSA-v7cp-2cx9-x793
- GitHub Advisory Database: GHSA-h7x8-jv97-fvvm / CVE-2025-51481
- Dagster fix reference: dagster-io/dagster pull request 30002
- GitHub Advisory Database: GHSA-6w2r-r2m5-xq5w / CVE-2025-57833
- GitHub Advisory Database: GHSA-hpr9-3m2g-3j9p / CVE-2025-59681
- GitHub Advisory Database: GHSA-frmv-pr5f-9mcr / CVE-2025-64459
- GitHub Advisory Database: GHSA-rqw2-ghq9-44m7 / CVE-2025-13372
- GitHub Advisory Database: GHSA-gvg8-93h5-g6qq / CVE-2026-1287
- GitHub Advisory Database: GHSA-6426-9fv3-65x8 / CVE-2026-1312
- GitHub Advisory Database: GHSA-mwm9-4648-f68q / CVE-2026-1207
- Django security release archive: https://docs.djangoproject.com/en/dev/releases/security/
September 16 follow-up: Crawl4AI Docker API hardcoded default JWT signing key (GHSA-8qrg-7j2f-rf2h / CVE-2026-56265)¶
crawl4ai before 0.8.7 ships a hardcoded default JWT signing key in the Docker API server: anyone who knows the published default can forge valid tokens for any user and reach every protected endpoint. This completes the Crawl4AI Docker-API story on this page — the same exposure class as the file:// LFI and hooks RCE entries, but the auth gate itself was forgeable, so "it's behind the JWT gate" was never a mitigation on affected images.
Operator check: when you find an exposed Crawl4AI API server, test the auth layer before treating it as authenticated-only — attempt one request signed with the documented default key (or run unauthenticated first and compare 401 vs 200 surfaces), and record whether any key-configuration banner differs from the default. Positive: a forged-token request reaches a protected route. Proof stays marker-only (list/read a harmless endpoint); the forged token itself is the finding — never crawl real targets or read host files as evidence of auth bypass. Frame as default-signing-key auth bypass distinct from missing-auth findings; note that pre-0.8.0 images additionally carry the file:///hooks legs above.
- GitHub Advisory Database: GHSA-8qrg-7j2f-rf2h / CVE-2026-56265
September 16 follow-up: changedetection.io browser-step Goto SSRF and browserless file-protocol non-enforcement¶
Two more fetch-worker advisories extend this page's browser-automation boundary axes (published 2026-09-16T21:32Z):
- changedetection.io browser-step Goto SSRF — GHSA-4jg2-g9g4-7fpp / CVE-2026-92815: through 0.60.6, the Goto URL action in browser steps never validates
optional_value, so an attacker who can define/modify watch browser steps supplies arbitrary internal URLs and retrieves responses from restricted network locations. Validation note: the page-watch URL may be filtered while the step-action URL is a separate sink — enumerate every URL parameter a watch config accepts, not just the primary target field. Proof: lab instance, watch step Goto to an owned callback URL, capture the outbound hit; never probe real internal addresses. - browserless
file:navigation despiteALLOW_FILE_PROTOCOL=false— GHSA-w2m9-jxm7-c4jp / CVE-2026-92811: versions 1.44.0–2.56.7 fail to enforce the file-protocol restriction on the Playwright websocket endpoints (the CDP/Playwright wire path), even though the env default is deny. A token holder navigates the remote browser tofile://URLs and reads any file the container process can. This is the protocol-guard parity pattern again: the HTTP navigation path enforced the flag, the websocket control path did not. Replayable check on an owned browserless instance: connect via Playwright over websocket,goto("file:///proc/self/environ")-adjacent lab marker file you created, compare against the HTTP-API verdict. Reading your own marker is the proof; never read app config or secret files.
Operator sweep update: for any hosted browser-automation service (Crawl4AI, browserless, Playwright/CDP grids), the protocol/URL guard must be verified per transport (REST endpoint vs websocket/CDP session) and per URL-bearing parameter (target URL vs step/action URLs). Same reporting discipline as above: marker content only, own callbacks only.