Skip to content

feat(composer-update): harden auto-fix (verify fixes, validate lock, concurrency, quieter alerts)#39

Merged
oxyc merged 1 commit into
masterfrom
feat/harden-auto-fix
May 29, 2026
Merged

feat(composer-update): harden auto-fix (verify fixes, validate lock, concurrency, quieter alerts)#39
oxyc merged 1 commit into
masterfrom
feat/harden-auto-fix

Conversation

@oxyc
Copy link
Copy Markdown
Member

@oxyc oxyc commented May 29, 2026

Five hardening changes to the vuln auto-fix path (the A–E follow-ups). One PR so it's a single v2 move.

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 <= 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 "⚠️ Still vulnerable after update" section rather than presented as fixed. No-op when there's no vulns_json (plain dependency-update runs).

E — Validate the lockfile before opening a PR update.sh

Run composer validate after the update; if it fails, revert and open no PR instead of shipping something that breaks composer install on deploy.

B — Extract + test the create/refresh orchestration action.ymlscripts/open-or-refresh-pr.sh

Moved 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 bare origin: 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.yml

concurrency: { group: vuln-scan-<repo>, cancel-in-progress: false } so a manual workflow_dispatch can't race the nightly cron into duplicate PRs or competing force-pushes.

D — Quieter alerts action.yml + vulnerability-scan.yml

New outcome output (created | refreshed | skipped | none). The Google Chat step is suppressed when outcome == 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_project set GITHUB_ACTION_PATH to scripts/ instead of the action root, so update.sh's PHP helpers resolved to scripts/scripts/…. Dormant because no prior test drove the PHP helpers through update.sh (all used empty VULNS_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 v2 is moved to the merge commit.

🤖 Generated with Claude Code

… 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>
@oxyc oxyc merged commit 2ca166d into master May 29, 2026
1 check passed
@oxyc oxyc deleted the feat/harden-auto-fix branch May 29, 2026 14:46
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.

1 participant