Reading the report

This page walks through the text report section by section and defines the terms that appear in every format (text, HTML, JSON, SARIF, CycloneDX, GitLab).

What every format carries: whether the run completed, the findings, the recommendation and the warnings — anything that changes how a result should be read. What they do not all carry is the same level of detail, because each machine-readable format is shaped by its own specification. Where a format omits something, it is said below.

Header and warnings

Composer Remediate — 2 findings in /srv/app
Advisories: advisories from packagist.org
Solver: in-process Composer 2.10.3 dry-run (fallback: `composer update --no-install` in a scratch directory)
Warning: ...

The header names the advisory source and the solver route, so a report can be reproduced. Warnings are conditions that make the result weaker than it looks and deserve a reader's attention before the findings do:

Warning starts with Meaning
Advisory source "…" only knows advisories for the current lock The source was a composer audit dump; candidate locks could not be checked for other advisories.
N advisory matches ignored per configuration --ignore, config.audit.ignore or config.policy suppressed findings.
Ignore hygiene: … Ignore entries that match nothing in this lock; stale configuration.
Coverage gap: … An advisory database record about a locked package (or one it replaces or provides) could not be interpreted when the database was built, or a record could not be attributed to any package at all; the package is treated as unaffected by that record. A lock with gaps and no findings exits 4, not 0, unless --accept-coverage-gaps is given, and a fix that would add a package with such records is rejected unless they are accepted, in which case the recommendation lists them. See Advisory database.
Advisory source chosen by the analysed project's composer.json … The project, not the operator, decided where advisories come from (extra.remediate.database); a gate should treat that as the project's claim.
The analysed project's composer.json suppresses advisories … The repository's own config.audit.ignore or config.policy.advisories left findings out. Intended for a project you own; for one you are scanning, the entries are named so you can review them, and --no-project-ignores drops them.
The analysed project's composer.json changes how TLS certificates are verified … The repository supplied config.cafile, capath or disable-tls. They did not apply: the advisory database was fetched with your own TLS configuration.
The analysed project's composer.json sets config.cache-dir … The database is kept under the operator's cache directory instead, so a repository cannot pre-fill the path the scan reads.
The advisory database at … is a local build with private advisories … is kept A newer public database exists but the local build with --include files was not replaced, so those advisories stay in force; public advisories published since are unknown until it is rebuilt.
Could not confirm that the advisory database at … is current / Offline: using the advisory database at … The published database could not be asked (or --offline); the local copy is used and its build age is given. --database-max-age turns this into exit 4 past a chosen age.
Advisory database unavailable; the configured repositories were asked instead No copy at the database path and nothing could be fetched; the run used the repository API as composer audit does, without exploit data or coverage gaps.
… is not a readable advisory database …; it will be replaced The file at the database path was not a database (corrupt, foreign); it was replaced by a download.
… was downloaded without a published sha256 … The database in use was never verified against a checksum.
<package>: the recommended command moves … to a version that still carries another advisory Blocking risk, see below.

A gate that wants to be strict about any of these can read the warnings array of the JSON report.

A Coverage gap: warning that names no package is the one to read closely: the record could not be attributed to anything, so it may have been about any package in the lock and it gates every scan until it is accepted. The warning quotes the record's source and identifier, which is enough to look it up and judge it. See coverage gaps, which also names the one the reference database carries today and the upstream fix for it.

One finding

CVE-2024-45411
────────────────────────────────────────────────────────────
Affected
  twig/twig v3.10.3
  CVE-2024-45411 (PKSA-6319-ffpf-gx66): Twig has a possible sandbox bypass
    https://github.com/advisories/GHSA-6j75-5wfj-gh66
    affected versions: >=1.0.0,<1.44.7|>=2.0.0,<2.16.0|>=3.0.0,<3.11.0|>=3.12.0,<3.14.0
Introduced by
  root
  └── drupal/core-recommended 10.3.1
      └── twig/twig v3.10.3  (requires ~v3.10.2)
Current state
  Transitive dependency.
Recommended remediation
  drupal/core-recommended 10.3.1 -> 10.3.4
  twig/twig v3.10.3 -> v3.14.0
Composer validation
  PASS  3 packages changed, 0 added, 0 removed, 0 root constraints changed
Expected changes
  drupal/core 10.3.1 -> 10.3.4
  drupal/core-recommended 10.3.1 -> 10.3.4
  twig/twig v3.10.3 -> v3.14.0
Recommended command
  composer update drupal/core-recommended:10.3.4 -W -m
Other candidates
  rejected: composer update twig/twig
      resolves, but twig/twig ends at v3.10.3 which is still affected by CVE-2024-45411
  • Affected: the locked package and every advisory on it. Several advisories on one package are always handled together; the command must escape all of them. (replaces x/y) after the package means the advisory is about a package this one replaces or provides. With an advisory database carrying exploit data, an advisory line is followed by EPSS 0.93 (97th percentile) and, when CISA lists the CVE as exploited in the wild, listed in CISA KEV since 2026-02-01; findings are ordered by that urgency, so the first finding in the report is the one to fix today.
  • Introduced by: the dependency paths from the root package down to the vulnerable one, with the constraint each link places on the next. Up to five paths are printed; the JSON report has up to ten and the total count.
  • Current state: direct or transitive, and whether it is a development requirement only. An Abandoned line follows when Packagist marks the vulnerable package or a package on its dependency path abandoned, with the replacement Packagist names: an abandoned parent will not ship the release that lifts its pin, and an abandoned vulnerable package will not ship a fix.
  • Recommended remediation: what moves. For a transitive package this names the parent that is updated and the version the vulnerable package lands on.
  • Composer validation: PASS means Composer's own solver resolved the command in a dry run and the resulting lock no longer contains the advisory. This line also counts changed, added and removed packages and any root-constraint change, and flags a major version change.
  • Expected changes: the full lock diff the command produces (first thirty entries).
  • Recommended command: the exact command to run. It is the request that was verified, including any --with constraint, -m, and platform flags the analysis was run with.
  • Keep the fix: shown whenever the recommendation rests on a --with constraint. A --with constraint lives for exactly one command, and nothing in the lock file records why the version went up, so a later composer update may resolve straight back into the affected range. The conflict entry printed here says it once and for good, and makes the resolver name it when something tries. Composer has no subcommand that writes one, so it is an edit to composer.json rather than part of the verified command above.
  • Other candidates: what else was tried and why it lost or was rejected, with the solver's own explanation. Candidates listed as "not tried" were skipped because a better one already existed or the solve budget ran out.

Notes that may appear between remediation and validation:

  • Constraint drag: the only fix requires widening a constraint in composer.json; the note names the root requirement that blocks every fix within the current constraints and the constraint the recommendation widens it to. The command then starts with composer require --no-update ….
  • No fix on this branch: follows a constraint drag whose fix also leaves the locked major behind. Advisory ranges are per branch, so a 2.x project is offered the 2.x fix before any major bump is considered; this note means no such release was published. The alternative the planner cannot see is a fix carried by a maintained fork or a backport under a different package name — it searches by the name in your lock file, so a rename is invisible to it.
  • Capability changes: what the update changes about what a package is allowed to do, as opposed to how much it changes: a package type becoming composer-plugin, autoload.files entries (which run on every request) or binaries appearing where there were none, and the source or dist host a package is fetched from changing. All of it is read from metadata the lock file and the solve already carry, so nothing is downloaded or unpacked to produce it. It is shown rather than folded into the ranking, because the count of changed packages measures review burden and this measures reach.
  • Blocking risk: the command moves a package to a version that still carries another, already present, advisory. Composer 2.10 and newer refuse such updates by default (advisory blocking); the command may need that advisory ignored in config.policy or blocking disabled to run.
  • Note: installs pre-release versions: no stable release satisfies the constraints yet.

When there is no fix

Recommended remediation
  No verified remediation found (none).

The word in parentheses is the outcome, and it matters for gating:

Outcome Meaning Exit code
none Every candidate was solved and none removes the advisory within the current metadata. Usually a platform or constraint bound; the rejected candidates say which. 2, unless the combined command fixes it (below)
none found within the search budget --max-candidates or --solve-budget cut the search short. A fix may exist outside the bounded search; raise the limits to look further. 2, unless the combined command fixes it
unknown: solver error At least one solve failed inside Composer. The absence of a fix is not established. 3
unknown: network failure while solving Package metadata could not be fetched. Retry, or warm the cache. 5

A finding without a fix is never silently equal to a finding with no vulnerability: exit code 2 is only returned when the search completed.

These outcomes describe the finding's standalone remediation: the command found for that package on its own. A finding with outcome none can still be fixed by the combined command in the summary, as when updating a vulnerable parent removes its vulnerable child, which has no fix of its own. The exit code counts that: a gated finding whose advisories the verified combined command removes does not cause exit 2, whatever --fail-on or the baseline left out of the gate. In the JSON report such a finding keeps remediation.status: none and appears in unsolved_findings; the summary's combined_command, combined_fixes and combined_fixes_all say what the combined command covers.

Summary

Summary
  Findings: 2 advisories on 2 packages, 2 with a verified fix
  You can fix all 2 findings with:
    composer update symfony/http-foundation symfony/process
    (2 packages changed, verified by Composer)
  Gate: --fail-on high, 1 of 2 packages count towards the exit code (1).

The summary merges the per-package winners into one command and verifies it with a further solve; when that does not fix everything it tries other combinations and smaller ones (see global planning). It reads "You can fix all N findings with …", or "… fixes k of N findings" when some have no reachable fix, or lists per-package commands when no combination resolves. When more than one combination was tried, a "Combined command search" block lists each attempt with its verdict, so the choice is explained rather than asserted. [baselined] marks packages accepted through --baseline; the Gate line shows how --fail-on and the baseline shaped the exit code. [declared exposed] marks packages named with --exposed, and a "Declared exposed" line says that they are listed first because of that declaration rather than because of their advisories.

Exit codes

Exit Meaning
0 No known vulnerabilities in the lock (after --fail-on and baseline), and no coverage gap about a locked package unless --accept-coverage-gaps was given
1 Vulnerabilities found and every gated one has a verified fix
2 At least one gated vulnerability has no verified fix (not even through the combined command), and every solve completed
3 Tool error: no lock file, bad option, or a solver error left an outcome unknown
4 Advisory data unavailable: the source could not be read or fetched, an incomplete source cannot verify candidates, or records about locked packages could not be read (coverage gaps) and were not accepted
5 Package metadata could not be fetched while solving

The same content in other formats

  • JSON (--format=json, --output=x.json): one object per finding with remediation.status (verified or none), remediation.outcome, remediation.command, remediation.blocking_risk, constraint_drag, the full candidates list, solver_runs and search_exhausted; the summary block carries the combined command and the gate counts; warnings is the list above. Since 0.10.1 (schema_version 2) a verified remediation also carries no_fix_within_locked_major, conflict_entries and capability_changes, the three notes described above, and summary carries packages_running_new_code. Since schema_version 3 each finding carries declared_exposed and summary carries packages_declared_exposed. The schema is published at docs/schema/report.schema.json, with report-v3.schema.json as the copy to pin.
  • HTML (--output=x.html): a self-contained page with the same sections, a summary table and collapsible candidate lists; advisory links are anchors only for http(s) URLs.
  • SARIF (--output=x.sarif): one rule per advisory, one result per vulnerable package located at its composer.lock line, the recommended command in the message. For GitHub Code Scanning.
  • CycloneDX (--output=x.cdx.json): the lock as an SBOM with vulnerabilities and their recommendation attached.
  • GitLab (--output=gl-dependency-scanning-report.json): the dependency-scanning report format for GitLab's security dashboard, solution field holding the command.

--format selects what goes to standard output; --output writes files and can be repeated. See CI integration for how to gate on these.