Safeguard
Developer Tools•

Why is a PHP package missing from the scan? Composer replace and provide

Composer replace and provide let one package satisfy requirements for another name. Here is why that can hide or duplicate components in a scan.

Safeguard Security Team
4 min read

Composer lets a package declare that it replaces or provides another package name. When it does, a requirement for the other name is satisfied without that package being installed. An inventory or scan keyed only on the names a project requires can therefore show a package name that was never downloaded, or miss one that is covered.

What the two fields mean

The Composer schema documentation describes both.

replace is a map of packages that are replaced by this package. It lets you fork a package, publish it under a different name with its own version numbers, and have packages that require the original keep working because your fork replaces it. The docs also give the case of a main package that contains sub-packages: if you require the main package, it automatically fulfils any requirement for one of the individual components, since it replaces them. They advise caution here and recommend replacing with self.version so the main package only replaces sub-packages of that exact version.

provide is a map of packages that are provided by this package. The docs say it is mostly useful for implementations of common interfaces. A package could depend on a virtual package such as psr/log-implementation, and any library implementing that interface lists it under provide. Using provide with the name of a real package implies the code of that package is shipped too, in which case replace is generally the better choice.

Why this matters for inventories

SituationWhat the name list showsWhat is really installed
A fork replaces the originalThe original name is required by other packagesOnly the fork's code
A monolith replaces its componentsComponent names appear in requirementsOne package, containing them
A library provides a virtual nameA virtual name is requiredWhichever implementation was chosen

Three problems follow.

A scan that lists the original name may report vulnerabilities for code you do not run, because your fork replaced it. Equally, the fork may have its own version numbers, which do not line up with the advisories for the original, so version matching can mislead in either direction.

A scan that lists only top level installed packages may miss components shipped inside a main package, since their names never appear as separate installs.

Virtual package names, such as an interface implementation name, are not real libraries at all. An inventory that tries to look them up will find nothing, and that is expected.

Steps to check a project

  1. Open composer.lock and search for the strings replace and provide inside the package entries.
  2. For each package with a replace entry, note which names it covers and under what version constraint.
  3. Check how your scanner reports those names. Does the original appear, the replacement, both, or neither?
  4. If a fork replaces an original, decide how you will track advisories for the original, since they may describe code your fork inherited.
  5. Treat virtual names as markers and ignore them for advisory matching.

A practical rule

For any fork that replaces another package, keep a short note recording the upstream name, the fork's source, and the point at which it diverged. When an advisory for the upstream appears, you need to know quickly whether your fork carries the vulnerable code, and no tool will reliably tell you that from the names alone.

Try it yourself

Open the demo and notice that an inventory is built from what a project resolves to. Then think about what an entry would look like if a replace field covered several names.

Click through it at your own pace, or open it full screen.

Safeguard's SBOM and dependency scanning builds an inventory for your projects, and the free scan lets you see how a repository of yours is listed.

Quick check

Does replace remove the original package from the lock file?

The original is not installed when a replacing package satisfies the requirement, though the replacing package's entry records what it replaces. Check composer.lock.

Is a name under provide a real package?

Often not. The docs say provide is mostly used for virtual names that describe an interface implementation.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.