mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #71] [MERGED] feat(groom): extract CI-privileged patch path policy into tested patch_policy.py (BE-4404) #82
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#82
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/71
Author: @mattmillerai
Created: 7/24/2026
Status: ✅ Merged
Merged: 8/4/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-4404-groom-patch-policy📝 Commits (3)
5278fbcfeat(groom): extract CI-privileged patch path policy into tested patch_policy.py + close lockfile/.husky/action.yml gaps (BE-4404)ef119a8fix(groom): close patch_policy under-blocks + fail-open flagged by review panel (BE-4404)c52567aMerge remote-tracking branch 'origin/main' into matt/be-4404-groom-patch-policy📊 Changes
4 files changed (+616 additions, -36 deletions)
View changed files
📝
.github/groom/README.md(+40 -0)➕
.github/groom/patch_policy.py(+226 -0)➕
.github/groom/tests/test_patch_policy.py(+307 -0)📝
.github/workflows/groom.yml(+43 -36)📄 Description
ELI-5
The groom auto-builder lets a (prompt-injectable, credential-free) AI agent write a code patch, and a later job opens a review-gated PR from it. But review only gates the merge — pushing the branch already runs the repo's CI, with secrets, before any human looks. So if that patch edits a file CI executes, it's arbitrary code execution in credentialed CI. There was already a deny-list that downgrades such a patch from an auto-PR to a plain filed issue — but it was a one-line
grepnobody could test, and it missed the scariest case: dependency lockfiles. Rewrite a lockfile'sresolvedURL +integrityhash and the nextnpm cidownloads and runs an attacker's install script. This PR moves the deny-list into a small, unit-tested Python file and plugs that gap (plus.husky/,action.yml, and build files for many ecosystems).What changed
.github/groom/patch_policy.py(new) —denied_paths(paths)returns the CI-privileged subset of changed paths;main()reads NUL-delimited paths from stdin (matchinggit diff --cached --name-only -z) and prints matches, exit 0 always (the caller tests non-emptiness). Ports the pre-existing patterns verbatim, then adds: lockfiles (package-lock.json,npm-shrinkwrap.json,yarn.lock,pnpm-lock.yaml,bun.lock/bun.lockb,poetry.lock,uv.lock,Pipfile.lock,Cargo.lock,Gemfile.lock),.husky/(any segment),action.yml/action.yaml,.gitmodules,requirements*.txt,Pipfile,Cargo.toml,build.rs,Package.swift,*.pbxproj,*.gradle/*.gradle.kts/settings.gradle,gradle/wrapper/,Gemfile,Rakefile,*.gemspec,CMakeLists.txt/*.cmake, BazelWORKSPACE(.bazel)/BUILD(.bazel)/*.bzl,Jenkinsfile,Taskfile.yml,just(J)file..github/groom/tests/test_patch_policy.py(new) — 19 tests: every ported + added pattern family at root and nested depth, anchored negatives (src/foo.py,README.md,packages.json,Dockerfile.md, … must NOT match), and the git raw-byte regressions (embedded"and embedded newline still caught from-zbytes; both newline halves tested).groom.ymlCapture patchstep — the inlinegrep -Ebecomesgit -C repo diff --cached --name-only -z | python3 "$GROOM_ASSETS/patch_policy.py".GROOM_ASSETSis the existing workflow-env var pointing into the_groom_assetscheckout thebuildjob already does (Load groom assets (builder brief)runs before this step). The stale comment block is trimmed to a pointer keeping the two load-bearing notes (conservative default; NUL-delimited comparison mandatory), and the bail reason string now names the policy file.README.md+groom.ymlheader — mention the policy file and its structural limit: the deny-list guards privileged-config surfaces, but any patch's source still executes when caller CI runs its tests — callers should avoid exposing secrets to test steps and considernpm ci --ignore-scriptswhere viable.Verification
actionlint .github/workflows/groom.yml— clean (includes shellcheck on therun:block).python3 -m unittest discover -s .github/groom/tests -p 'test_*.py'— 78 tests OK (ledger 59 + patch_policy 19);test_patch_policyandtest_ledgerboth auto-discovered by the existingtest-groom-scripts.ymlpattern, no workflow change needed.printf '.github/workflows/ci.yml\0package-lock.json\0src/app.py\0.husky/pre-commit\0tools/action.yml\0README.md\0' | python3 patch_policy.pyprints exactly the four privileged paths; clean-only input prints nothing; empty stdin → exit 0.Acceptance criteria
PortedPatternsTestpins the full ported set at root + nested).package-lock.json,.husky/pre-commit,tools/action.ymleach bail.bails → lands in the ledger and files agroomissue; nothing dropped.builder: falsedefault; no caller touched.Judgment calls
|| truekept on the assignment. The original wrapped thegrepin|| true(grep exits 1 on no-match, the common case).patch_policy.pyexits 0 always, so|| truenow only masks a genuinegit/python3failure — preserving the original posture that this line never aborts the step underset -euo pipefail. Dropping it would instead abort the matrix leg on any error, which (per the step's own secret-scan comment) drops the finding entirely rather than filing it. The script is unit-tested and SHA-pinned viaworkflows_ref, so a broken script is caught bytest-groom-scripts.ymlbefore merge. Flagged rather than silently changed..github/(workflows|actions)/stays root-anchored (verbatim port), while newly-added.husky/andgradle/wrapper/match at any depth per the ticket — documented inline. Over-blocking is the safe direction, but the ported pattern is kept byte-for-byte to guarantee no regression on the previously-covered set.Negative-claim falsification
Not applicable in the #581 sense: this diff does not deny a product capability or add a "not supported"/
STOPdead-end. "Denied" here means a builder patch is downgraded from an auto-PR to a filed issue — the finding is still delivered and the human authors the privileged change. Nothing is dropped. The deny behavior is empirically validated by the end-to-end pipeline simulation above, not merely asserted by self-authored tests.BE-4404
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.