perf(vuln-scan): audit the lock file instead of installing - #54
Merged
Conversation
The scan installed the whole project to audit it, then threw the result away. Measured on suomentyokalu run 30515814093 (90s total): 36s Random delay (removed separately) 23s Post Setup project saving caches 14s Install dependencies composer install 9s Setup project Node + PHP + cache restores 2s Run vulnerability scan The audit is 2s. Everything else exists to populate vendor/, which this job never reads — there is not one reference to vendor/ in the workflow. composer audit --locked reads composer.lock directly: same advisory data, same JSON shape the jq guard already requires, no install. That removes the install, all three caches and Node entirely, leaving checkout, PHP, auth and the audit. composer-update keeps working on the lock alone — its calls gain --no-install, and its post-update check is composer validate, which inspects composer.json and composer.lock rather than vendor/. The PR it opens has always been a lock diff. Expected ~90s -> ~15s per scan. Across 19 repos every night that is the largest remaining item in the org's CI bill. Not using the shared setup action here on purpose: it installs Node and manages three caches this job has no use for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
oxyc
added a commit
that referenced
this pull request
Jul 31, 2026
#54 removed composer install from this job because the audit runs off composer.lock. It does — but composer-update does not: its compute-min-safe-constraints.php loads composer/semver from the consuming project's vendor/autoload.php and bails with 'needs composer install first' when it is missing. That broke the vulns-found path on the first night. generoi/solving run 30598123430: audit correct, PR body assembled, then 'Update packages' died in 70ms. The same path succeeded on the four previous nights. I checked the workflow for vendor/ references before #54 and found none; I did not check the action it calls. Install only on that path, gated on the same condition the update steps already use, with the download cache restored alongside so it does not fetch every package over the network. A clean scan — about half of nightly runs — still finishes in ~12s having installed nothing. Co-authored-by: test <test@example.com> Co-authored-by: Claude Opus 5 (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.
The nightly scan installed the entire project in order to audit it, then threw the installation away. It is the largest single line in the org's CI bill — 19 repos, every night — and almost all of it is scaffolding.
Where the time went
suomentyokalu run 30515814093, 90s total:
The audit is 2 seconds. The other ~50s exists to populate
vendor/— and the workflow contains not one reference tovendor/.The change
composer audit --lockedreadscomposer.lockdirectly. Verified locally: 2s, and the same.advisoriesJSON shape the existingjq -e '.advisories'guard requires. Same advisory sources, since the wpsecadv repository is still configured.That makes the install redundant, and with it all three caches and Node. The job becomes: checkout → PHP → auth → audit. The shared
setupaction is deliberately not used here — it installs Node and manages three caches this job has no use for.Expected ~90s → ~15s per scan, ~19 × 30 runs a month.
The auto-update path still works on the lock alone
composer update -W/composer requirecalls gain--no-install, so they rewritecomposer.jsonandcomposer.lockwithout materialisingvendor/. This is what the flow already wanted: the PR it opens has always been a lock diff, and its post-update gate iscomposer validate --no-check-all --no-check-publish, which inspectscomposer.jsonagainst the lock rather than an installed tree.The
composer-updatetest suite runs on this PR and exercises those call sites.Details worth reviewing
config.platform.phpas the shared action does, rather than hardcoded, so repos on older PHP are unaffected.if:—secrets.*is unavailable in step-levelif:inside a reusable workflow, which this repo already fixed once in 2702182.--lockedresolves nothing, but composer still loads metadata from configured repositories and the private ones 401 without credentials.Verification plan
Timing on a real repo via a temporary pin, with
auto_update: falseso no spurious PRs are opened. The vulns-found path needs a genuine finding to exercise end to end; the composer-update tests cover the script changes in the meantime.🤖 Generated with Claude Code