Split ci.yml to close a pull_request_target pwn-request hole - #478
Split ci.yml to close a pull_request_target pwn-request hole#478Loup-Garou911XD wants to merge 3 commits into
Conversation
|
created by claude |
58465c1 to
3572a0d
Compare
d70bedd to
704fe9f
Compare
704fe9f to
dd53b01
Compare
dd53b01 to
9034d1d
Compare
|
Thanks — addressed in Staging scope wider than the validation allowlist (both occurrences): fixed. This was a legitimate gap.
Verified with a scratch repo: with Passing the changed-file list as one CLI argument: not changing, deliberately. Both stated risks are bounded here:
More importantly this is in |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (3)
test/test_checks.py:17
- The comment describes unresolved commits as “absent 40-hex sha raises ValueError”, but this repo stores abbreviated commit SHAs (first 8 chars) in metadata (see test/auto_apply_version_metadata.py). The wording is misleading; make it SHA-length agnostic (rev/hex object name) so future readers don’t assume a 40-hex requirement.
# Raised by Repo.commit() when a sha cannot be resolved: a well-formed but
# absent 40-hex sha raises plain ValueError, while a malformed name raises
# BadName/BadObject (which are NOT ValueError subclasses). Kept narrow so
# lenient mode can't mask unrelated repository errors.
.github/workflows/ci-check.yml:85
python test/auto_apply_plugin_metadata.py "$(cat changed_files.txt)"collapses newlines to spaces in bash command substitution, but the script splits on\n(sys.argv[1].split('\n')). This causes the metadata step to miss most changed files. Invoke the script per file (or adjust the script to read the file), so it receives one path per run.
run: |
set -euo pipefail
python test/auto_apply_plugin_metadata.py "$(cat "${RUNNER_TEMP}/changed_files.txt")"
.github/workflows/ci-check.yml:71
git diff ... -- '*.py'only matches Python files in the repo root; it won’t include changed plugin files under plugins/**, so autopep8 won’t run on most PR changes. Use a recursive pathspec so all .py files are captured.
git diff --name-only -z "$BASE_SHA" HEAD -- '*.py' > "${RUNNER_TEMP}/changed_py.z"
pull_request_target checked out fork PR branches with the repo's
write-scoped GITHUB_TOKEN and ran autopep8/metadata scripts/tests
against that fork content, letting a malicious PR rewrite test/*.py
for arbitrary code execution with push access and secrets. It's also
been failing outright for weeks since actions/checkout now blocks
unsafe fork checkouts here without explicit opt-in.
Split into ci-check.yml (plain pull_request, GitHub's read-only
no-secrets token, safe to run fork code) which uploads a diff
artifact, and ci-apply.yml (workflow_run, privileged) which only
applies that diff via `git apply`, never executing fork content.
ci.yml keeps just the push-to-main job as the strict integrity check.
Because GitHub runs the PR's own copy of ci-check.yml for
pull_request events, that artifact is attacker-authored: ci-apply.yml
therefore resolves PR identity from the workflow_run payload plus the
API rather than the artifact, passes every dynamic value through env:
instead of ${{ }} in run: blocks (which the runner substitutes before
the shell parses, so quotes don't contain it), and validates branch,
repo, sha and PR-number shapes before use. The patch itself stays
untrusted input: allowlist-validated and applied only to the fork's
own branch.
test_checks.py adds an env-gated lenient mode so ci-check.yml's
preview run doesn't fail on a brand-new plugin's not-yet-existing
commit sha, while history and push-to-main stay strict.
9034d1d to
bc5c5aa
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
|
copilot reviewing claude, dead internet theory |
auto_apply_plugin_metadata.py asked "what version is already released?" by reading the category manifest out of the PR's own working tree. Once ci-apply.yml has pushed its "[ci] apply-plugin-metadata-and-formatting" commit back to the PR branch, that tree already lists the version being added - so the check compared the new version against itself and raised "Version cant be lower or equal than the previous version." That fires on every re-run of PR Check: the one ci-apply.yml's own push triggers, and any run caused by a contributor pushing a follow-up commit. Published state now comes from the base branch instead, read with `git show $PLUGMAN_BASE_REF:<manifest>` (ci-check.yml supplies the PR base sha; local runs fall back to origin/main, then to the working tree). The writer is idempotent to match: a version entry that is already stamped and still matches the plugin's md5sum is left alone rather than reset to null. An entry whose md5sum has drifted is still reset, which implements the TODO this replaces - a contributor can keep iterating on an unpublished version during review without bumping it every round. Bump enforcement against published versions is unchanged. Version comparison and ordering switch to the existing get_comparable_version_tuple_from_string; versioning_tools.semantic_to_str ordered 1.0.10 below 1.0.9 and silently truncated 3-digit components.
cb656b9 to
44cffc5
Compare
|
@bombsquad-community/plugman-maintainers ill merge this tomorrow if no one wants to review it |
pull_request_target checked out fork PR branches with the repo's write-scoped GITHUB_TOKEN and ran autopep8/metadata scripts/tests against that fork content, letting a malicious PR rewrite test/*.py for arbitrary code execution with push access and secrets. It's also been failing outright for weeks since actions/checkout now blocks unsafe fork checkouts here without explicit opt-in.
Split into ci-check.yml (plain pull_request, GitHub's read-only no-secrets token, safe to run fork code) which uploads a diff artifact, and ci-apply.yml (workflow_run, privileged) which only applies that diff via
git apply, never executing fork content. ci.yml keeps just the push-to-main job as the strict integrity check.test_checks.py adds an env-gated lenient mode so ci-check.yml's preview run doesn't fail on a brand-new plugin's not-yet-existing commit sha, while history and push-to-main stay strict.