Harden scripts against argument injection and malformed inputs#26
Closed
sagpatil wants to merge 2 commits into
Closed
Harden scripts against argument injection and malformed inputs#26sagpatil wants to merge 2 commits into
sagpatil wants to merge 2 commits into
Conversation
Addresses the findings from the vuln-hunt sweep against 820b478. The dominant class was argument injection: untrusted CLI values (--rev, --image, --registry, --tag) flowing into git/docker as positional args where a leading dash is parsed as an option (e.g. --rev=--upload-pack=<cmd> -> RCE; --image=--privileged -> full container caps). Security fixes: - common.reject_option_like(): central guard refusing values that begin with '-' before they reach a subprocess argv. A valid image ref, git ref, or tag never starts with '-'. - repro_test.clone(): reject option-like repo/rev and pin --end-of-options before the git fetch positionals (the high-severity RCE vector). - docker_inspect: guard image refs, manifest tag, and sources. - git_remote.ls_remote: guard URL/refspecs and pin --end-of-options. - Boundary guards on --image/--tag/--registry in smoke_test_image, repro_test, write_metadata, build_image, publish_manifests, publish_aliases. - release_body: validate every metadata field (digest, arch, tag, rust_base_key) and --registry/--repo against safe charsets before interpolating them into the copy-paste verification shell block; refuse symlinked meta-*.json. - validate_json.iter_json_files: skip *.json that resolve outside the repo tree (symlink-followed arbitrary-file read). Robustness fixes: - builds.load(): require a top-level JSON object (was AttributeError downstream). - builds.split_entry(): require a string pin (was AttributeError on null/int). - refresh: catch CalledProcessError from the docker digest lookup; only re-sort rust_versions when a pin was actually appended (no spurious reorder on no-op). Adds focused tests for each guard; full suite passes (223), ruff clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CodeQL flagged the .startswith("docker.io") assertion as incomplete URL
substring sanitization. The guard returns the value verbatim, so assert
equality on the full string — clearer intent and no substring check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Hardens the repository’s Python build/publish utility scripts against argument-injection and malformed-input vulnerabilities by adding centralized guards for option-like values, tightening subprocess argument boundaries for git, and preventing unsafe filesystem reads (symlink escapes). It also improves robustness of JSON parsing and refresh error handling, with focused unit/integration tests covering the new protections.
Changes:
- Add
common.reject_option_like()and apply it across scripts/libs before passing untrusted values intogit/dockersubprocess argv. - Prevent unsafe metadata / repo file reads by rejecting symlinked
meta-*.jsonand skipping*.jsonthat resolve outside the repo tree. - Improve failure modes and type validation (e.g.,
builds.load()top-level dict requirement,builds.split_entry()string requirement,refreshhandling ofCalledProcessErrorand no-op ordering).
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_write_metadata.py | Adds coverage ensuring write_metadata rejects option-like --image values. |
| tests/unit/test_validate_json.py | Adds coverage ensuring iter_json_files skips symlink escapes out of tree. |
| tests/unit/test_smoke_test_image.py | Adds coverage ensuring smoke_test_image rejects option-like --image before any docker call. |
| tests/unit/test_repro_test.py | Adds coverage for option-like repo/rev rejection and git end-of-options placement. |
| tests/unit/test_release_body.py | Adds coverage for metadata field validation and safe --registry/--repo constraints + symlink rejection. |
| tests/unit/test_git_remote.py | Adds coverage for git ls-remote end-of-options placement and option-like rejection. |
| tests/unit/test_docker_inspect.py | Adds coverage for rejecting option-like refs/tags/sources before docker subprocess. |
| tests/unit/test_common.py | Adds coverage for common.reject_option_like() behavior. |
| tests/unit/test_builds.py | Adds coverage for builds.load() non-dict JSON and split_entry() non-string pins. |
| tests/unit/test_build_image.py | Adds coverage ensuring build_image rejects option-like --tag. |
| tests/integration/test_refresh.py | Adds integration coverage for clean handling of docker failures and no-op ordering preservation. |
| scripts/write_metadata.py | Adds option-like guard for --image at CLI boundary. |
| scripts/validate_json.py | Skips *.json that resolve outside repo root (symlink escape prevention). |
| scripts/smoke_test_image.py | Guards --image to prevent docker flag confusion via positional parsing. |
| scripts/repro_test.py | Guards --image/--repo/--rev, and pins git end-of-options boundaries in clone/fetch. |
| scripts/release_body.py | Validates metadata fields and CLI args before interpolating into runnable shell verification blocks; rejects symlinked metadata files. |
| scripts/refresh.py | Avoids re-sorting on no-op; reports docker digest lookup failures cleanly. |
| scripts/publish_manifests.py | Guards --registry against option-like values before docker calls. |
| scripts/publish_aliases.py | Guards --registry against option-like values before docker calls. |
| scripts/lib/git_remote.py | Adds option-like guards and --end-of-options for git ls-remote. |
| scripts/lib/docker_inspect.py | Adds option-like guards for image refs / manifest tags / source images. |
| scripts/lib/common.py | Introduces reject_option_like() centralized guard. |
| scripts/lib/builds.py | Adds type validation for loaded JSON and rust pin entries for clearer failures. |
| scripts/build_image.py | Guards --tag to prevent docker flag injection via positional/tag handling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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
Addresses findings from a vulnerability sweep of the build/publish scripts. The dominant class was argument injection: untrusted CLI values flowing into
git/dockeras positional arguments where a leading-is parsed as an option.--rev=--upload-pack=<cmd>(with afile://repo) → arbitrary command execution inrepro_test.py--image=--privileged→ full container capabilities insmoke_test_image.py--registry→ docker flag confusion increate_manifestSecurity fixes
common.reject_option_like()— central guard refusing any value beginning with-before it reaches a subprocess argv. A valid image ref, git ref, or tag never starts with-, so no legitimate input is lost.repro_test.clone()— reject option-likerepo/revand pin--end-of-optionsbefore thegit fetchpositionals (the high-severity RCE vector). Verified end-to-end: the PoC now fails as an invalid refspec instead of executing.docker_inspect— guard image refs, manifest tag, and sources.git_remote.ls_remote— guard URL/refspecs and pin--end-of-options.--image/--tag/--registryinsmoke_test_image,repro_test,write_metadata,build_image,publish_manifests,publish_aliases.release_body— validate every metadata field (digest,arch,tag,rust_base_key) and--registry/--repoagainst safe charsets before interpolating them into the copy-paste verification shell block; refuse symlinkedmeta-*.json.validate_json.iter_json_files— skip*.jsonthat resolve outside the repo tree (symlink-followed arbitrary-file read).Robustness fixes
builds.load()— require a top-level JSON object (was an opaqueAttributeErrordownstream).builds.split_entry()— require a string pin (wasAttributeErroron anull/int element).refresh— catchCalledProcessErrorfrom the docker digest lookup; only re-sortrust_versionswhen a pin was actually appended (no spurious reorder/commit on a no-op refresh).Assessed as by-design (not changed)
The append-only "newest pin wins" and
max(n)+1release-tag semantics are intentional (documented in code/schema); changing them would risk the release pipeline.write_metadata --outputaccepting an arbitrary path is inherent to a tool whose job is to write a file you name (andmkstempalready requires an existing parent dir).Testing
Focused tests added for every guard. Full suite passes (226),
ruff checkandruff format --checkclean.🤖 Generated with Claude Code