security: fix path traversal (CVE-2026-4307) and SSRF (CVE-2026-4308) - #1785
Open
3baprinting wants to merge 6 commits into
Open
security: fix path traversal (CVE-2026-4307) and SSRF (CVE-2026-4308)#17853baprinting wants to merge 6 commits into
3baprinting wants to merge 6 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.