security: fix path traversal (CVE-2026-4307) and SSRF (CVE-2026-4308) - #1785
security: fix path traversal (CVE-2026-4307) and SSRF (CVE-2026-4308)#17853baprinting wants to merge 6 commits into
Conversation
|
Hi @3baprinting, we are finally looking at all the PRs, thanks for the report and for covering both advisories. We reviewed the two separately: CVE-2026-4307 / download paths: the /a0 restriction was inconsistent with the existing authenticated File Browser and editor, which are intentionally rooted at /. We have aligned the single and bulk download APIs with that same filesystem scope while retaining authentication, CSRF protection, and canonical path resolution. We are implementing the fixes through the existing shared owners, so we won’t merge this PR, but thank you for surfacing the paths and again for the review. Under our current security posture, File Browser and editor already expose the container filesystem, while the agent itself runs as root inside that container. The impact of CVE-2026-4307 is therefore narrower than what the advisory describes. CVE-2026-4308 instead suffered a regression after Document Query Tool was extracted into a plugin. Thank you, |
Summary
Fixes the two publicly-disclosed vulnerabilities that are still reachable on
main:Both were verified against current
main(not just the advisory's 0.9.7), and every anchor these patches touch exists there today.CVE-2026-4307 — arbitrary file read
helpers/files.py: _resolve_path()returns a single absolute path verbatim andos.path.joins relative ones without normalising..:api/api_files_get.pyfeeds request-supplied paths in, and its final branch skips resolution entirely before reading the file and returning it base64-encoded:The handler sets
requires_auth = Falseandrequires_csrf = False, so an API key alone yieldsPOST {"paths": ["/etc/passwd"]}→ file contents.api/file_info.pyhas the same root cause with a smaller impact: it stats any absolute path, leaking existence, size, mtime and permission bits for arbitrary host files.Fix. Adds
files.get_abs_path_contained(), whichrealpaths the result (normalising..and resolving symlinks) and refuses anything outside the base directory.api_files_get.pynow accepts only/a0/...paths through that helper, and the arbitrary-absolute-path branch is removed.file_info.pyreturns a "refused" record instead of stat-ing outside the base dir.get_abs_path()itself is deliberately unchanged — it is used throughout the codebase with trusted absolute paths, so tightening it would break internals. The rule this PR establishes is that handlers taking a path from a request use the contained variant.CVE-2026-4308 — SSRF
plugins/_document_query/helpers/fetch.pyperformed:with no destination validation anywhere in the codebase. A document URI — which an agent will readily take from a page it just read — could therefore reach
http://169.254.169.254/latest/meta-data/(cloud instance credentials),http://127.0.0.1:…(co-located services), or any RFC1918 address; and because redirects were delegated to the client, a public-looking URL could bounce into the private network on hop two.Fix. Adds
helpers/net_guard.py: deny-by-default validation requiringhttp/https, resolving the hostname, and refusing if any resolved address is loopback, private, link-local, reserved, multicast or unspecified — including IPv4-mapped IPv6 such as::ffff:127.0.0.1.fetch.pyvalidates before the first byte leaves and follows redirects manually, re-validating each hop (capped at 3). A blocked target raises immediately rather than being retried and masked as a genericDocument fetch error.Tests
tests/test_path_traversal_and_ssrf_guards.py— 26 tests covering absolute-path reads,..traversal, symlink escape, non-HTTP schemes, loopback/link-local/RFC1918/IPv4-mapped targets, and redirect delegation.They also assert the guards do not over-block: legitimate in-base paths still resolve and public addresses still pass. The public-address cases use IP literals rather than hostnames, because some sandboxed/proxied networks resolve every name into
198.18.0.0/15(RFC 2544 benchmark space), which is correctly not public — a hostname assertion would fail there for environmental reasons rather than a real defect.Notes
MAX_REDIRECTSand the scheme allowlist are module-level constants innet_guard.pyif you would prefer them configurable.Happy to adjust naming or split this into two PRs if you would rather review the fixes separately.