fix: contain registry path resolution against traversal (F-05-01)#11
Merged
Conversation
load_single() built Path(registry_dir)/secid_type/<reversed-dns>/<subpath>.json from the untrusted secid query with no containment; pathlib's '/' join does not collapse '..', so ?secid=advisory/x/../../../../etc/passwd escaped the registry tree (arbitrary .json read + existence oracle), remote-unauthenticated on the shipped 0.0.0.0/no-auth defaults. Two layers, fail closed: - _reject_unsafe_segment(): refuse '..'/absolute/backslash/NUL segments at the resolver boundary (_match_namespace) and in load_single/load_type_info. - _contained_path(): after building the path, resolve() both it and the registry root and require containment; applied at all three filesystem sinks (load_single, build_type_index, load_type_info). Legitimate dotted namespaces (redhat.com) are unaffected — only '..' path segments are rejected. Adds 4 regression tests; full suite 32 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| """ | ||
| try: | ||
| base = Path(registry_dir).resolve() | ||
| target = full_path.resolve() |
| # Defense in depth: confirm the joined path stays inside the registry | ||
| # tree even if a hostile segment slipped past the check above. | ||
| full_path = _contained_path(registry_dir, Path(registry_dir) / secid_type / fs_path) | ||
| if full_path and full_path.exists(): |
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.
Finding
F-05-01 (HIGH) from the 2026-06 security audit.
registry_loader.load_single()builds a filesystem path from the untrustedsecidquery (Path(registry_dir)/<type>/<reversed-dns>/<subpath>.json) with no containment.pathlib's/join does not collapse.., so:escapes the registry tree — an arbitrary-
.jsonread (e.g. other tenants' private overlays) plus a filesystem existence/readability oracle. Remote-unauthenticated on the shipped--host 0.0.0.0/ no-auth defaults, on the default lazy-load path.Fix (two layers, fail closed)
_reject_unsafe_segment()— rejects..path segments, absolute paths, backslashes, and NUL at the resolver boundary (_match_namespace) and inload_single/load_type_info. Returns(None, None)/None, which flows into the existing not-found path (no error oracle)._contained_path()— after building the path,resolve()s both it and the registry root and requires containment; applied at all three filesystem sinks (load_single,build_type_index,load_type_info). Fails closed on escape/resolution error.Legitimate dotted namespaces (
redhat.com,legislation.gov.uk) are unaffected — only..segments are rejected, not dots within a label.Tests
4 regression tests added (
_reject_unsafe_segment,_contained_path,load_singletraversal block, end-to-endresolvenot-found + secret-never-read). Full suite: 32 passed.Notes
build_type_index/load_type_infojoins) via the same_contained_pathguard.Generated from the audit patch
PATCHES/01-path-traversal-containment.patch.md.🤖 Generated with Claude Code