Skip to content

Harden scripts against argument injection and malformed inputs#26

Closed
sagpatil wants to merge 2 commits into
mainfrom
security/argument-injection-hardening
Closed

Harden scripts against argument injection and malformed inputs#26
sagpatil wants to merge 2 commits into
mainfrom
security/argument-injection-hardening

Conversation

@sagpatil

Copy link
Copy Markdown
Contributor

Summary

Addresses findings from a vulnerability sweep of the build/publish scripts. The dominant class was argument injection: untrusted CLI values flowing into git/docker as positional arguments where a leading - is parsed as an option.

  • --rev=--upload-pack=<cmd> (with a file:// repo) → arbitrary command execution in repro_test.py
  • --image=--privileged → full container capabilities in smoke_test_image.py
  • --registry → docker flag confusion in create_manifest

Security 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-like repo/rev and pin --end-of-options before the git fetch positionals (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.
  • 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 an opaque AttributeError downstream).
  • builds.split_entry() — require a string pin (was AttributeError on a null/int element).
  • refresh — catch CalledProcessError from the docker digest lookup; only re-sort rust_versions when 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)+1 release-tag semantics are intentional (documented in code/schema); changing them would risk the release pipeline. write_metadata --output accepting an arbitrary path is inherent to a tool whose job is to write a file you name (and mkstemp already requires an existing parent dir).

Testing

Focused tests added for every guard. Full suite passes (226), ruff check and ruff format --check clean.

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings June 10, 2026 02:54
Comment thread tests/unit/test_common.py Fixed
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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 into git/docker subprocess argv.
  • Prevent unsafe metadata / repo file reads by rejecting symlinked meta-*.json and skipping *.json that resolve outside the repo tree.
  • Improve failure modes and type validation (e.g., builds.load() top-level dict requirement, builds.split_entry() string requirement, refresh handling of CalledProcessError and 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.

@sagpatil sagpatil closed this Jun 10, 2026
@sagpatil sagpatil deleted the security/argument-injection-hardening branch June 10, 2026 19:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants