Skip to content

0.4.0

Choose a tag to compare

@hexblot hexblot released this 10 Sep 00:44
· 34 commits to main since this release
v0.4.0
c548c29

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 COMPOSER to the scratch manifest. Before, the child inherited a
    COMPOSER=/path/alternate.json from 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 with COMPOSER set to another manifest and asserts that file is unchanged.
  • composer-remediate never includes any project's vendor/autoload.php (Composer's autoloader
    executes autoload.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
    a gap table with source, id, package and reason; composer remediate warns for every gap that
    names a package in the lock ("treated as unaffected by that record"); remediate:db-status lists
    them; databases built before gap tracking are flagged. A private --include file 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, introduced opens an interval,
    the next fixed/last_affected closes it, and limit caps 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 composer on
    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-remediate binary: 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) or unknown: network failure while solving (exit 5). A tool
    failure can no longer produce the actionable-policy exit 2.
  • --ignore-platform-req / --ignore-platform-reqs are 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
    .sha256 sidecar; a missing sidecar and a stale cache reused after a failed refresh are reported
    as warnings with the cache age.
  • IgnorePolicy: config.audit.ignore entries with apply: block and config.policy.advisories
    entries with on-audit: false no 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-dev baseline, all-advisory fixed range, platform flags, blocking
    risk, budget), parser strictness, OSV versions/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=json output passed as --advisories-file is recognised as covering the
    current lock only.
  • OSV normalisation honours explicit versions in addition to ranges and the limit event.
  • 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-age cooldown; 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-dev an 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() follow COMPOSER=alternate.json.
  • --minimal-changes is 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
    SecurityAdvisory class has no severity property; the adapter no longer reads it unconditionally.
  • HTML reports turn only http(s) advisory links into anchors.
  • --min-release-age rejects non-numeric input instead of coercing it to zero.
  • The advisory-database workflow publishes sha256sum output (digest and filename) so
    sha256sum -c works as documented.
  • README no longer claims the smallest fix; the search is bounded and ranked.
  • JSON report (schema still version 1, additive): solver_runs and search_exhausted per finding,
    outcome on findings without a fix, blocking_risk on verified ones, solver_runs in 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 numeric security-severity, one result per vulnerable package located at its
    composer.lock line, 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.