In Composer 2.10, composer update is blocked by an advisory you assessed as not affecting you; how do you configure config.policy to proceed without hiding it?
answer
- ignore one ID, not the gate
- ignore-id with a reason
- on-audit false keeps audit reporting
- config.audit is deprecated
- fallback is all-or-nothing
basics
~10 sAdd the advisory's ID under config.policy.advisories.ignore-id with a reason and on-audit set to false. Updates may then select the version, composer audit still reports it, and blocking stays on for every other advisory.
solid answer
~40 sScope the exception as narrowly as possible. `config.policy.advisories.ignore-id` takes advisory IDs (CVE, GHSA or PKSA), each optionally with a `reason` and scoping. A scoping key switches the ignore off for one operation: `{"on-audit": false}` means the advisory no longer blocks updates but is **still reported** by `composer audit`; `{"on-block": false}` is the reverse. If one package rather than one advisory is the issue, `policy.advisories.ignore` takes package names with an optional version `constraint`. Avoid the blunt switches, `--no-blocking`, `COMPOSER_NO_BLOCKING=1` or `"policy": false`, because they drop every policy for every package. Composer 2.10 deprecated the old `config.audit` keys; they are read only when the matching `policy` section is absent, all or nothing per policy.
code
json · 19 lines{
"config": {
"policy": {
"advisories": {
"ignore-id": {
"GHSA-xxxx-xxxx-xxxx": {
"on-audit": false,
"reason": "XML driver only; we use JSON. Review 2026-12."
}
}
},
"abandoned": {
"ignore": {
"acme/legacy-mailer": "Replacement planned next quarter."
}
}
}
}
}go deeper
Know that Composer can refuse a version with a security advisory, and that exceptions are written in composer.json instead of disabling the check.
Explain block versus audit, the defaults of the three built-in policies, and the ID, package and severity forms of ignore.
Scope exceptions to one advisory with a reason, keep them visible in audit, avoid --no-blocking, and migrate config.audit to config.policy in one change.
Define who may accept an advisory, how long an ignore may live, and how accepted risk is reviewed across repositories.
## What config.policy is Composer 2.10 introduced **`config.policy`**, one block that controls how Composer treats flagged packages. It has three built-in **dependency policies**, and you can add custom named ones: | Policy | `block` default | `audit` default | Flags | |---|---|---|---| | `advisories` | `true` | `fail` | versions with known security advisories | | `malware` | `true` (scope `all`, so install too) | `fail` | versions flagged as malware | | `abandoned` | `false` | `fail` | packages marked abandoned | `block` drops matching versions while resolving, during `update`, `require` and `remove`. `audit` sets how `composer audit` treats them: `ignore`, `report` (listed, exit code unaffected) or `fail` (exit 1). `"policy": false` disables everything. ## The narrow exception: ignore-id with scoping When an advisory blocks an update but your team has shown it does not apply (the vulnerable function is never called, or a workaround is in place), record **that advisory** only: ```json "policy": { "advisories": { "ignore-id": { "GHSA-xxxx-xxxx-xxxx": { "on-audit": false, "reason": "Only the XML driver is affected; we use JSON." } } } } ``` An ignore rule applies to both operations unless scoped. Each scoping key switches the ignore **off** for one operation: - `"on-audit": false`: the ignore is not used when auditing. The advisory **stops blocking** updates but `composer audit` **still reports** it. This is the "proceed without hiding it" setting. - `"on-block": false`: the ignore is not used when blocking. The advisory **still blocks** updates and is only dropped from audit reports. The keys are easy to read backwards, and the `ignore-id` prose in Composer's config documentation itself describes them the other way round. Its own example reasons ("Patch applied, still want blocking." on an `on-block: false` entry), its general ignore-format section, and the source (`getIgnoreIdForOperation('block')` keeps a rule only when `onBlock` is true) all agree with the reading above. The other ignore forms: - `ignore-id` as a plain list (`["CVE-2026-1234"]`) ignores the advisory everywhere, with no reason recorded. - `advisories.ignore` targets **package names**, with wildcards and an optional version `constraint`, such as `{"vendor/pkg": {"constraint": "^2.0", "reason": "Only v2 is affected."}}`. - `advisories.ignore-severity` drops whole severity levels (`low`, `medium`, `high`, `critical`), also with optional scoping. It is broad; use it knowingly. ## What not to reach for 1. `composer update --no-blocking`, or `COMPOSER_NO_BLOCKING=1`: turns off **all** policy blocking for that run, so any other vulnerable or malware version can enter the lock too. 2. `"policy": false` or `COMPOSER_POLICY=0`: disables blocking and auditing for every policy. 3. `policy.advisories.block: false`: stops blocking every advisory, not the one you assessed. Each of these turns a documented one-advisory decision into an undocumented blanket one. ## Abandoned packages `policy.abandoned.block` defaults to **`false`**, so an abandoned package can still be installed or updated, while `policy.abandoned.audit` defaults to **`fail`**, so `composer audit` fails on it. Teams that keep a package deliberately while planning a replacement use `policy.abandoned.ignore` with a reason, for example `{"acme/*": "Scheduled for replacement next quarter."}`, or set `abandoned.audit` to `report`. ## Custom dependency policies Besides the three built-in policies, `config.policy` accepts **named custom policies**, for example a company list of package versions that must not be used. Each one has its own `block`, `audit` and `ignore` keys and one or more `sources`, such as `{"type": "url", "url": "https://..."}`. Composer sends that URL a POST request listing candidate package names and expects back entries with a `package` and a version `constraint`. Source URLs must use `https://`. Names cannot collide with `advisories`, `malware` or `abandoned`, cannot start with `ignore`, and a list of further names (such as `license` and `minimum-release-age`) is reserved for future built-in policies. ## Migrating from config.audit Before 2.10 these settings lived under `config.audit` (`block-insecure`, `ignore`, `ignore-severity`, `abandoned`, `block-abandoned`, `ignore-abandoned`). They still work but are **deprecated**, and the fallback is **all or nothing per policy**: - If `policy.advisories` exists at all, **every** advisory-related `audit.*` key is ignored. - If `policy.abandoned` exists, **every** abandoned-related `audit.*` key is ignored. - The two are independent, so you can migrate one at a time. Adding `policy.advisories.block` while keeping the old `audit.ignore` list therefore silently drops the old ignores. Move all advisory keys in one change.
- You added policy.advisories.block and the old audit.ignore entries stopped working. Why?In Composer 2.10 the legacy `config.audit` keys are a fallback read only when the matching `policy` section is absent. Once `policy.advisories` exists, every advisory-related `audit.*` key, including `audit.ignore`, is ignored. Move the ignores to `policy.advisories.ignore-id` or `policy.advisories.ignore` in the same change.
- When is ignoring by package name better than ignoring by advisory ID?When the decision is about the package rather than one advisory, for example a fork you maintain yourself or a range you have patched. `policy.advisories.ignore` with a `constraint` limits it to the affected versions. For a single assessed vulnerability, an ID is narrower: a new advisory on the same package still blocks.
saying these in an interview costs you the question
- on-block false means the advisory no longer blocks updates.
- Running update with --no-blocking is fine for a single known advisory.
- Abandoned packages are blocked from installing by default.
- Old config.audit keys merge with the new policy block.
- ignore-id entries cannot record why the advisory was accepted.