0.4.0
This release responds to an adversarial adoption review of 0.3.0 (twenty findings, nine rated as
able to undermine a security decision) and to the reviewer's recheck of the first response (six
remaining findings). Each item below names its change; the tests that establish it are listed in the
"Tests" bullets, and where a boundary is documented rather than removed the text says so.
Recheck (second round)
- The subprocess solver pins
COMPOSERto the scratch manifest. Before, the child inherited a
COMPOSER=/path/alternate.jsonfrom the parent and updated the analysed project's real lock while
the planner read the untouched scratch lock. Covered by an integration test that runs the real
Composer binary withCOMPOSERset to another manifest and asserts that file is unchanged. composer-remediatenever includes any project'svendor/autoload.php(Composer's autoloader
executesautoload.files, which is project code). It boots Composer from the phar it finds and
registers the plugin's own classes through a PSR-4 mapping. Covered by an entry-point test that
installs the binary in a project whose autoloader plants a probe and asserts the probe never runs.- Database ingestion keeps coverage gaps: an upstream record the build cannot interpret is stored in
agaptable with source, id, package and reason;composer remediatewarns for every gap that
names a package in the lock ("treated as unaffected by that record");remediate:db-statuslists
them; databases built before gap tracking are flagged. A private--includefile with an
unreadable record fails the build, matching--advisories-file. Database reads use the strict range
parser; the lenient one is gone. - OSV events are evaluated per the specification: sorted by version,
introducedopens an interval,
the nextfixed/last_affectedcloses it, andlimitcaps the whole range instead of closing an
interval of its own. "introduced 1.0, fixed 1.1, limit 2.0" no longer marks 1.5 affected. - The locator records whether a cached download was verified (
<cache>.status.json) and repeats the
disclosure on every cache hit, offline included; caches written by earlier versions are flagged as
unverified. - The fixture harness executes the printed command through a shell, verbatim, with a
composeron
PATH that adds--no-install; quoted constraints are no longer split on whitespace. - Documentation regrouped by intent (Use it, Understand it, Integrate it, Evidence, Project) with
three new pages: Reading the report, Comparison with other tools, and Case studies generated from
the fixture corpus (bin/case-studies.php, checked for staleness in CI).
Added
composer-remediatebinary: runs the same commands with the analysed project's plugins and
scripts disabled from the first instruction, reusing the installed Composer for its classes and
the fallback solver.composer remediate(the plugin command) keeps Composer's usual behaviour of
activating the project's other allowed plugins at startup; SECURITY.md and the privacy page now
state both boundaries precisely.--solver=auto|in-process|subprocess: the documented subprocess fallback is now wired. A
candidate whose in-process solve errors (not a conflict, not a network failure) is retried through
composer update --no-install; the report's solver line counts the retries.--solve-budget(default 60): hard ceiling on solver runs per finding across candidates, conflict
expansion, parent descent and simplification. Every solve is counted; the report shows the count
per finding (solver_runs) and in total, and a bounded search that finds nothing reads "none found
within the search budget" instead of "none".- Typed outcomes for findings without a fix:
none,none found within the search budget,
unknown: solver error(exit 3) orunknown: network failure while solving(exit 5). A tool
failure can no longer produce the actionable-policy exit 2. --ignore-platform-req/--ignore-platform-reqsare repeated in every recommended command, so
the printed command is the request that was verified.- Blocking-risk note when a recommended command moves a package to a version that still carries
another advisory (Composer 2.10+ advisory blocking may refuse it). - Client-side sha256 verification of a downloaded advisory database against the publisher's
.sha256sidecar; a missing sidecar and a stale cache reused after a failed refresh are reported
as warnings with the cache age. IgnorePolicy:config.audit.ignoreentries withapply: blockandconfig.policy.advisories
entries withon-audit: falseno longer suppress findings; package rules are matched as packages
(with their constraint), not as advisory ids.- Tests: planner rules with a scripted solver (unknown severity, tool-error and network exits,
combined-command cooldown,--no-devbaseline, all-advisory fixed range, platform flags, blocking
risk, budget), parser strictness, OSVversions/limit/event order, ignore scoping, HTML link
safety, fallback solver composition (with scripted routes), the real subprocess solver, the
standalone entry point, database download/checksum/cache behaviour with a scripted HTTP layer,
coverage gaps from build to report; freshly rendered JSON validated against the schema for every
fixture; the synthetic fixture's recommended command executed verbatim by a shell with the real
Composer binary and the resulting lock re-matched. Not covered by tests: the CLI driven against a
live advisory repository, and the Composer 2.4 advisory adapter beyond the CI matrix job.
Changed
- No advisory-capable repository, a malformed advisory document (an error payload, a malformed
entry, an unparsable range) now stop the run with exit 4 instead of reading as a clean lock.
composer audit --format=jsonoutput passed as--advisories-fileis recognised as covering the
current lock only. - OSV normalisation honours explicit
versionsin addition torangesand thelimitevent. - Unknown and unrecognised severities count towards
--fail-on. - The combined command is held to the same rules as individual candidates: no new advisories and
the--min-release-agecooldown; its simplified spelling is re-matched instead of inheriting the
original's results. Releases without a known date are refused by the cooldown. - Under
--no-devan untouched development finding is no longer counted as "newly introduced" by a
production fix. - The fixed range escapes every advisory known for the package, not only those affecting the locked
version; ignored advisories do not shrink it. - "Identical lock" for simplification now compares source and dist references and the
production/development split, not only versions. composerJsonPath()/lockPath()followCOMPOSER=alternate.json.--minimal-changesis documented as a Composer 2.7.0 feature (2.9 extended it), and the subprocess
solver's threshold follows. Composer older than 2.4 is refused at runtime. The Composer 2.4
SecurityAdvisoryclass has noseverityproperty; the adapter no longer reads it unconditionally.- HTML reports turn only
http(s)advisory links into anchors. --min-release-agerejects non-numeric input instead of coercing it to zero.- The advisory-database workflow publishes
sha256sumoutput (digest and filename) so
sha256sum -cworks as documented. - README no longer claims the smallest fix; the search is bounded and ranked.
- JSON report (schema still version 1, additive):
solver_runsandsearch_exhaustedper finding,
outcomeon findings without a fix,blocking_riskon verified ones,solver_runsin the
metadata.
[0.3.0] - 2026-09-09
Added
- SARIF 2.1.0 output (
--output=results.sarif,--format=sarif) for GitHub Code Scanning: one rule
per advisory with a numericsecurity-severity, one result per vulnerable package located at its
composer.lockline, the verified command in the message. --fail-on <severity>: only findings at or above the threshold affect the exit code; findings of
unknown severity always count. Shown in the summary and in the JSON report.- Published JSON Schema for the report (
docs/schema/report.schema.json), validated against every
stored fixture report. - Seven more historical fixtures (BookStack 2023 and 2024, Pixelfed, USAGov Drupal, Invoice Ninja,
Kimai 1.x, Shopware 6.4.20.2), each with stored console, JSON, HTML and SARIF reports. - Composer version matrix in CI: 2.4, 2.7, 2.8, 2.9 and latest on matching PHP versions.
- Generated CLI reference page, CI integration guide with gate policies.
Changed
- Composer releases before 2.10 have no
Installer::getLockTransaction(); the in-process solver
now performs a lock-only update inside the scratch copy there and reads the lock back. - Solver results are copied into detached package objects and cycles are collected after each
solve; peak memory for 136 solves dropped from 1.5 GB to 87 MB.