feat(composer-update): harden auto-fix (verify fixes, validate lock, concurrency, quieter alerts)#39
Merged
Merged
Conversation
… concurrency, quieter alerts Five hardening changes to the vuln auto-fix path: A. Final 'is it actually fixed?' guard (update.sh). is_still_vulnerable previously ran only on the loose-retry path; the tight/bulk and widen paths trusted the derived constraint. A wrong min-safe constraint (cf. the <= hotfix bug) could land composer on a still-affected version and we'd claim a fix. Now every flagged package's FINAL locked version is re-checked; still-affected ones are surfaced in the PR body under a 'Still vulnerable after update' section instead of being presented as fixed. No-op when no vulns_json (plain dependency updates). E. Reject a lockfile that fails 'composer validate' — revert and open no PR rather than ship something that breaks 'composer install' on deploy. B. Extracted the create/refresh orchestration from action.yml into scripts/open-or-refresh-pr.sh and added an integration test driving it with a fake gh + local bare origin (create / no-fix / refresh-with-push / refresh-without-push / still-vuln surfacing). Previously untested. C. concurrency: group on the workflow so a manual dispatch can't race the nightly cron into duplicate PRs / competing force-pushes. D. Suppress the Google Chat alert when composer-update reports 'skipped' (an open PR already covers exactly these findings) — stops the nightly re-ping for a vuln already awaiting review. New 'outcome' action output (created|refreshed|skipped|none) drives it; genuinely new/unfixable findings still alert. Also fixes a latent test-harness bug: new_project set GITHUB_ACTION_PATH to scripts/ rather than the action root, so update.sh's PHP helpers resolved to scripts/scripts/ — dormant until a test drove them with a non-empty VULNS_JSON. Full suite: 6 files, 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Five hardening changes to the vuln auto-fix path (the A–E follow-ups). One PR so it's a single
v2move.A — Final "is it actually fixed?" guard
update.shis_still_vulnerable()previously ran only on the loose-retry path; the tight/bulk and widen paths trusted the derived constraint. A wrong min-safe constraint (cf. the<=4-segment-hotfix bug) could land composer on a still-affected version and we'd open a PR claiming a fix that isn't one. Now every flagged package's final locked version is re-checked; still-affected packages are surfaced in the PR body under a "vulns_json(plain dependency-update runs).E — Validate the lockfile before opening a PR
update.shRun
composer validateafter the update; if it fails, revert and open no PR instead of shipping something that breakscomposer installon deploy.B — Extract + test the create/refresh orchestration
action.yml→scripts/open-or-refresh-pr.shMoved the create/refresh logic out of the inline YAML into a script, and added an integration test driving it with a fake
gh+ a local bareorigin: create, create-with-no-fix, refresh-with-force-push, refresh-without-push, and still-vulnerable surfacing. This is the code #37 rewrote with only unit-level coverage of the decision; now the flow itself is tested. (action.yml shrinks ~90 lines.)C — Concurrency guard
vulnerability-scan.ymlconcurrency: { group: vuln-scan-<repo>, cancel-in-progress: false }so a manualworkflow_dispatchcan't race the nightly cron into duplicate PRs or competing force-pushes.D — Quieter alerts
action.yml+vulnerability-scan.ymlNew
outcomeoutput (created | refreshed | skipped | none). The Google Chat step is suppressed whenoutcome == skipped— i.e. an open PR already covers exactly these findings and nothing changed — so the nightly run stops re-pinging for a vuln that's already awaiting review. Genuinely new (created/refreshed) or unfixable (none, or auto-update disabled) findings still alert. Job status is unchanged (out of scope per request).Also: a latent test-harness bug
new_projectsetGITHUB_ACTION_PATHtoscripts/instead of the action root, soupdate.sh's PHP helpers resolved toscripts/scripts/…. Dormant because no prior test drove the PHP helpers throughupdate.sh(all used emptyVULNS_JSON); the new A test exposed it. Fixed to point at the action root, matching how GitHub sets it.Full suite: 6 files, 0 failures. Takes effect downstream once
v2is moved to the merge commit.🤖 Generated with Claude Code