0.6.0
A feature release with changed defaults. Global planning (Phase 4) searches for the smallest command
that fixes every finding and explains the search; the answers to an Aikido code scan make incomplete
advisory sources, unverified database downloads and unread coverage gaps fail closed, so exit codes can
differ from 0.5.0 in CI; three CLI options, a combined_search block in the JSON summary and Deptrac
layer rules arrive with it. The Plan::SEVERITIES constant is gone, replaced by the
Engine\Advisory\Severity enum.
Added
- Global planning (Phase 4). The planner now searches for one command that fixes every finding
with as little change as possible. The per-package winners are merged as before; when that merge
does not resolve, or fixes only some findings, the search swaps in the next-ranked candidate of a
finding that is in the way and tries again, within a budget of ten further solves, keeping the
combination that fixes the most findings, then the smallest diff. A combination that fixes
everything is then shrunk by dropping, in turn, each contribution whose package a sibling's fix
already moves, and keeping the smaller command when it still fixes everything: in the BookStack
socialite fixturerobrichards/xmlseclibsleaves the command because theonelogin/php-saml
update carries it, and in the Open Social fixturetwig/twigleaves because thedrupal/core
update does. Every attempt is listed in the text and HTML summaries and
insummary.combined_searchof the JSON report, with the solver's reason, so the recommended
command is explained rather than asserted. Tests: a merge that does not resolve until a
lower-ranked candidate is swapped in, a merge that undoes one finding's fix, a shrink to a parent
update that covers its sibling, and an exhausted search that keeps the best partial result.
Changed
- Structure. The longest methods were split without behaviour change, after a reader pointed at
them:RemediateCommand::executenow delegates to methods for report targets, runtime checks, the
advisory source, the planner, gating and output; the global search's bookkeeping moved from the
planner intoEngine\Plan\CombinedSearch, withrepair()andshrink()as separate steps;
TextRendererrenders one report section per method;CandidateGeneratorbuilds each candidate
family in its own method;RangeNormalizer::fromOsvandDatabaseWriter::writeare split by stage.
Severity labels are an enum (Engine\Advisory\Severity) instead of a lookup table onPlan. - Architecture rules.
deptrac.yamlstates the layers (Plugin → Command → Output → Engine →
Advisory) and CI checks them on the PHP 8.4 job;composer deptracruns them locally (Deptrac is
installed undertools/deptrac, since it needs PHP 8.2 or newer). The first run found one
violation, the advisory model reading the severity table from the plan class, which the enum fixes. - Fixture reports under
tests/Fixture/third-party/*/reportsrecord the sha256 of the committed
composer.fixture.jsonrather than of the scratch copy, whose injected repository path differed on
every run; regenerating a report now yields the same bytes.
Security
Answers to an Aikido code scan (nine findings), each with a regression test.
- Incomplete advisory sources fail closed. A source that only knows the current lock's
advisories (composer auditoutput through--advisories-file, the audit fallback) cannot check a
candidate lock, so the planner no longer verifies candidates against it: findings are reported with
a blocker and no remediation, and the run exits 2 instead of recommending fixes verified against
nothing. Before, the run warned and still reported the fixes as verified. - Coverage gaps gate the exit code. A lock with no findings but with advisory records about
locked (or replaced/provided) packages that the source could not read exits 4, not 0;
--accept-coverage-gapsrestores 0 once the gaps have been read. The JSON summary carries
coverage_gapsandcoverage_gaps_accepted. Gaps about packages a locked package replaces or
provides are now reported at all; before, only the locked names were checked. - Advisory database downloads must be verified. A URL without a published
<url>.sha256and
without an expected digest is refused;--allow-unverified-databaserestores the old warn-and-
accept behaviour.--database-sha256=<hex>pins the digest the operator trusts, which is checked
on the download and on every later use of the cached copy, and is the trust anchor for a database
someone else publishes (the sidecar comes from the same host). Onlyhttps://locations are
downloaded, wherever they are configured (option, environment orcomposer.json). Credentials
embedded in a URL are redacted from every message and from the cache metadata. Symbolic links at
the cache paths are refused and the cache directory is created private; the status file is written
atomically. - A same-version lock change that moves the commit (a re-tagged release, not only a moved dev
branch) is now a change, so the release-age guard refuses it when the date is unknown or too young
and reports it as one change of unclassifiable size. - One advisory id on two replaced components (a CVE spanning several Symfony components under
symfony/symfony) produced one finding; the finding key now includes the replaced target
(advisory@package/target), so both are reported. Baseline entries for such findings use the new
key. - Console output sanitised. Advisory titles, links, upstream record ids, solver output and file
names are stripped of control characters and escape sequences before they reach a terminal, and
console formatting tags in them are escaped in decorated output (Remediate\Output\ConsoleText),
so a crafted advisory cannot restyle the report or rewrite the line above it.remediate:db-build
andremediate:db-statussanitise the upstream text they print. - The GitLab pipeline verifies the Composer installer against its published signature instead of
piping the download into PHP. - The advisory-database release job runs in the
advisory-dbGitHub environment, whose
deployment-branch policy admits onlymain. Aworkflow_dispatchfrom another branch executes
that branch's copy of the workflow file, so no check inside the file (a pinned checkout ref, a
validated input) can stop an actor with write access from running modified code with the
release-capable token; the environment policy is enforced by GitHub before the job starts. A
github.refguard gives a clearer error for an accidental dispatch from a branch.