Skip to content

0.6.0

Choose a tag to compare

@hexblot hexblot released this 10 Sep 16:11
· 11 commits to main since this release
v0.6.0
420b05c

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 fixture robrichards/xmlseclibs leaves the command because the onelogin/php-saml
    update carries it, and in the Open Social fixture twig/twig leaves because the drupal/core
    update does. Every attempt is listed in the text and HTML summaries and
    in summary.combined_search of 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::execute now 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 into Engine\Plan\CombinedSearch, with repair() and shrink() as separate steps;
    TextRenderer renders one report section per method; CandidateGenerator builds each candidate
    family in its own method; RangeNormalizer::fromOsv and DatabaseWriter::write are split by stage.
    Severity labels are an enum (Engine\Advisory\Severity) instead of a lookup table on Plan.
  • Architecture rules. deptrac.yaml states the layers (Plugin → Command → Output → Engine →
    Advisory) and CI checks them on the PHP 8.4 job; composer deptrac runs them locally (Deptrac is
    installed under tools/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/*/reports record the sha256 of the committed
    composer.fixture.json rather 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 audit output 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-gaps restores 0 once the gaps have been read. The JSON summary carries
    coverage_gaps and coverage_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>.sha256 and
    without an expected digest is refused; --allow-unverified-database restores 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). Only https:// locations are
    downloaded, wherever they are configured (option, environment or composer.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
    and remediate:db-status sanitise 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-db GitHub environment, whose
    deployment-branch policy admits only main. A workflow_dispatch from 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.ref guard gives a clearer error for an accidental dispatch from a branch.