Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
PHPDevelopmentDevOpsTutorial

How to secure PHP dependencies with Composer 2.10 dependency policies

Composer 2.10 added dependency policies that block malware and vulnerable package versions. Learn the defaults, configure config.policy in composer.json, run composer audit in CI and deploy from a committed lock file.

Cloudsoft Zone

Updated 8 min read

Dense racks of servers with blue and green status lights in a data centre
Photograph: Victorgrigas / CC BY-SA 3.0, via Wikimedia Commons
In this article

A typical PHP application installs dozens of Composer packages, and each one runs with the same permissions as your own code. Attacks on package ecosystems have shifted from vulnerable code towards deliberately malicious releases: a maintainer account is taken over, a poisoned version is published, and every project that updates in the next few hours installs it.

Composer 2.10, released on 28 May 2026, responds to this with dependency policies: a single config.policy section that controls how Composer treats packages with security advisories, abandoned packages and package versions flagged as malware. The current stable release is 2.10.3, published on 27 August 2026, which itself fixed several security issues in Composer.

This tutorial shows you how to check your Composer version, understand the default policies, configure them in composer.json, run composer audit in CI, and deploy so that production installs exactly what you tested. All options are taken from the official Composer documentation and the 2.10 release announcement.

Step 1: update Composer itself

Dependency policies require Composer 2.10 or newer. Check your version and update:

Shell
composer --version
composer self-update

Do the same in your CI images and deployment scripts. Official Docker images and CI setup actions often pin a Composer version; make sure yours resolves to 2.10.3 or later. Composer's download page notes that bug fixes are only guaranteed for the latest feature release, so staying on an old minor version also means missing fixes such as those in 2.10.3, which addressed a path traversal issue with package binaries and a command injection issue with malicious Perforce URLs.

Step 2: understand the default policies

Composer 2.10 ships with defaults that the Composer team describes as matching what most projects want:

PolicyDuring update / requireDuring install from a lock filecomposer audit
MalwareBlockedBlockedFails
Security advisoriesBlockedAllowedFails
Abandoned packagesAllowedAllowedReported; the configuration reference lists fail as the default audit value

"Blocked" means the affected versions are removed from the pool Composer resolves from, so it picks another version or reports that it cannot satisfy your requirements. Advisories are not blocked at install time on purpose: the announcement explains that this gives you a chance to judge whether a published vulnerability affects you before you patch, without breaking deployments of an existing lock file. Malware, by contrast, is blocked even when installing from a lock file.

The malware data for Packagist.org comes from a feed of flagged package versions that Aikido provides under a CC BY 4.0 licence. It works without any configuration for packages installed from Packagist.org.

Step 3: configure the policy in composer.json

The defaults are sensible, so start by stating only what you want to change. A stricter configuration for a production application might block abandoned packages too, and document any advisory you have consciously accepted:

JSON
{
    "config": {
        "sort-packages": true,
        "policy": {
            "advisories": {
                "block": true,
                "audit": "fail",
                "ignore-id": {
                    "GHSA-xxxx-xxxx-xxxx": "Placeholder: the affected function is never called; review again in 30 days."
                }
            },
            "abandoned": {
                "block": true,
                "audit": "fail"
            },
            "malware": {
                "block": true,
                "block-scope": "all",
                "audit": "fail"
            }
        }
    }
}

The GHSA-xxxx-xxxx-xxxx entry is a placeholder showing the format; list real advisory IDs only after you have checked that the vulnerable code path is not reachable in your application. Each policy shares the same building blocks:

  • block: whether matching versions are removed from dependency resolution. For malware, block-scope controls whether blocking applies during update, install or both (all, the default).
  • audit: how composer audit treats matches: ignore, report or fail.
  • ignore: per-package exemptions, with optional reasons and version constraints. Advisories also accept ignore-id (CVE, GHSA or other identifiers) and ignore-severity.

Ignores can be scoped. In the Composer documentation, {"on-block": false} means an advisory no longer blocks updates but is still reported by the audit, and {"on-audit": false} means it still blocks updates but disappears from audit reports. Always write a reason: the next person to read composer.json needs to know why an exception exists.

If you already use the older config.audit keys, they keep working as a fallback in 2.10. The announcement says a deprecation warning will follow in Composer 2.11, and that migration is one-to-one; for example audit.block-insecure becomes policy.advisories.block. Migrate each policy in one go: the configuration reference explains that once, say, config.policy.advisories is set, every advisories-related audit.* key is ignored entirely. The advisories and abandoned policies are independent, so you can migrate one before the other.

Blue, grey and yellow network cables plugged into a rack-mounted Ethernet switch
Every Composer package arrives over the network; policies and a committed lock file control what gets installed.

Step 4: run composer audit in CI

composer audit checks installed packages against the configured policies. Since Composer 2.10 it exits with 0 when there are no issues and 1 when anything fails, which makes it simple to use as a CI gate:

Shell
# Fail the build on advisories, malware or (with the config above) abandoned packages
composer audit --locked

# Production dependencies only
composer audit --locked --no-dev

# Machine-readable output for dashboards or tickets
composer audit --locked --format=json

The --locked option audits the lock file rather than whatever happens to be in vendor/, which is what you want in CI. Other useful options are --abandoned=report to override the abandoned policy for one run, and --ignore-severity=low to skip low-severity advisories.

Run the audit on a schedule as well as on every pull request. New advisories are published for versions you already have, so a project that has not changed in a month can still start failing, and that is exactly when you want to know.

Step 5: deploy exactly what you tested

Policies protect you during resolution, but your deployment process decides what reaches production. A safe sequence:

  1. Commit composer.lock to version control. Only composer update run by a developer or an automated update tool should change it.
  2. Check the manifest: composer validate --strict.
  3. Install on the build server with composer install --no-dev --optimize-autoloader --no-interaction. install never resolves new versions; it reproduces the lock file.
  4. Check that the target runtime has the PHP version and extensions the packages need: composer check-platform-reqs.
  5. Run composer audit --locked --no-dev before the artefact is published.

Composer 2.10 also stopped falling back automatically to a source checkout (for example a Git clone) when a package archive fails to download. The announcement explains that an unexpected source checkout can have security implications. A source-fallback option restores the old behaviour temporarily, and the Composer team plans to remove it in 2.11. If a deployment now fails where it previously worked, fix the download problem rather than turning the fallback back on.

Step 6: handle a blocked update

Sooner or later composer update will refuse a version because of a policy. Composer 2.10.3 improved the error output for this case, so read it first: it names the package, the version and the policy that blocked it. Then:

  • For a security advisory, check whether a fixed release exists and widen your version constraint to allow it. That is the normal outcome.
  • If no fixed release exists yet, read the advisory and decide whether the vulnerable code is reachable in your application. Only then add a scoped ignore-id entry with a reason and a review date.
  • For malware, do not add an exception. Remove or pin the package to a known-good version, rotate any credentials that were present on machines where the flagged version was installed, and inspect your lock-file history.
  • For an abandoned package, look at the replacement the maintainer suggests on Packagist and plan the migration.

The --no-blocking flag (or COMPOSER_NO_BLOCKING=1) disables policy-based blocking for one command. Treat it as an emergency tool for diagnosis, not as a setting for CI.

Step 7: control plugins and scripts

Composer plugins run code during installs. Since Composer 2.2, the config.allow-plugins option restricts which plugins may execute code; it defaults to an empty object, which allows none. Composer 2.10 also enforces it in non-interactive mode for very old lock files created before 2.2. Review the list and allow only what you use:

JSON
{
    "config": {
        "allow-plugins": {
            "composer/installers": true,
            "phpstan/extension-installer": true
        }
    }
}

Review package scripts in the same way. Scripts in your root composer.json run on install and update, so treat changes to them like code changes in review.

What about a cooldown period?

Many malicious versions are detected and removed within hours or days of publication. The configuration reference on getcomposer.org now also documents a policy.cooldown option that withholds newly published versions for a period you choose, for example "period": "7 days", during update and require. This option is not mentioned in the 2.10 release announcement or in the 2.10.x changelogs, so check that your installed Composer version supports it before you rely on it. If it does not, you can get a similar effect by updating dependencies on a schedule rather than immediately after every release, and by reviewing lock-file diffs in pull requests.

Blurred lines of colourful source code on a dark computer monitor
Review lock-file changes like code: a new indirect dependency is new code running in your application.

A practical checklist

  • Composer is 2.10.3 or newer on developer machines, CI and build servers.
  • composer.lock is committed, and production uses composer install, never update.
  • config.policy states any deviation from the defaults, with a reason for every ignore.
  • composer audit --locked runs on every pull request and on a daily or weekly schedule.
  • allow-plugins lists only plugins you actually need.
  • Lock-file changes are reviewed like code, including new packages pulled in indirectly.
  • Abandoned packages have a replacement plan and an owner.

Summary

Composer 2.10 gives PHP projects a single place to decide how to treat vulnerable, abandoned and malicious packages, and its defaults already block malware and vulnerable versions during updates. Update Composer everywhere, write down your policy in composer.json, make composer audit a required CI check, and deploy from a committed lock file. Together these steps close the most common routes by which a compromised package reaches production.

Frequently asked questions

  • Which Composer version introduced dependency policies?

    Composer 2.10.0, released on 28 May 2026, introduced the unified config.policy section and the malware policy. The latest stable release in October 2026 is 2.10.3.

  • Does Composer block packages with security advisories during composer install?

    Not by default. Versions with advisories are blocked during update and require and fail the audit, but can still be installed from an existing lock file. Malware is blocked during install as well.

  • Do my old config.audit settings still work?

    Yes, as a fallback in 2.10, and the Composer team says a deprecation warning will follow in 2.11. Once you set a config.policy section for advisories or abandoned packages, the matching legacy keys are ignored.

  • What exit codes does composer audit return?

    Since Composer 2.10, composer audit returns 0 when there are no issues and 1 when packages match a failing policy or required packages are missing.

Advertise on CloudSoftZone

Your next customer could start right here.

Introduce your tool, product or business to readers exploring technology.

Advertise with us

Explore placements and request a private quote

Sources

  1. Composer 2.10 release announcement — Packagist blog, 2026-05-28
  2. Composer documentation: Config — Composer
  3. Composer documentation: Command-line interface / Commands — Composer
  4. Composer releases (2.10.0 to 2.10.3 changelogs) — Composer on GitHub, 2026-08-27
  5. Download Composer — Composer

Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.

Comments

No comments yet. Be the first to join the conversation.

Leave a comment

Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.