composer/composer · critical · SecurityException

Invalid package found during dependency resolution, aborting

Error message

Invalid package found during dependency resolution, aborting: {error}

What it means

Thrown by ValidatingArrayLoader::validatePackage() (a static, security-sensitive re-check) as a SecurityException when a resolved package's name fails hasPackageNamingError() — i.e. it violates the vendor/name regex, uses reserved names (con/nul/aux/etc.), ends in .json, or contains uppercase. This is a defense-in-depth guard re-applied after dependency resolution, before install/write, to stop malicious names that slipped past earlier validation.

Solutions

  1. Run 'composer clear-cache' and re-run update to refetch clean metadata from trusted sources.
  2. Inspect composer.lock for packages with abnormal names; remove offending entries and re-resolve.
  3. If you control the offending package, rename it to a valid lowercase vendor/name with no reserved words.
  4. Verify you are resolving against the official packagist.org or a trusted private repository, not a mirrored/poisoned one.
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check a resolved package name with the public static guard before install
$err = \Composer\Package\Loader\ValidatingArrayLoader::hasPackageNamingError($package->getName());
if ($err !== null) {
    throw new \RuntimeException('Refusing to install: '.$err);
}

Type guard

function isSafePackageName(string $name): bool {
    return \Composer\Package\Loader\ValidatingArrayLoader::hasPackageNamingError($name) === null;
}

Try / catch

try {
    \Composer\Package\Loader\ValidatingArrayLoader::validatePackage($package);
} catch (\Composer\Util\SecurityException $e) {
    // Do NOT auto-resolve; treat as a security incident and abort the operation.
    throw $e;
}

Prevention

When it happens

Trigger: Calling ValidatingArrayLoader::validatePackage($package) where $package is not a RootPackageInterface and getName() returns a value for which hasPackageNamingError() is non-null (structurally invalid name, reserved name, .json suffix, or uppercase letters). Triggered internally during dependency resolution and lock-file writing.

Common situations: A malicious or corrupted provider/lock file injects a package whose name would be interpreted dangerously (e.g. contains shell metacharacters that evade the regex, or a reserved Windows device name). Encountered after a 'composer update' that pulled bad metadata or when a tampered composer.lock is present.

Related errors


AI-assisted analysis of composer/composer@c435d285c9 (2026-08-07). Data as JSON: /api/errors/8be1b642f78c6b67. Report an issue: GitHub.

Appendix: source

Thrown at src/Composer/Package/Loader/ValidatingArrayLoader.php:686

     * installed from the lock file. This guards against malicious package names and source/dist
     * URLs or references that could be interpreted as command-line options (argument injection)
     * by the VCS/download tooling.
     *
     * @throws SecurityException
     */
    public static function validatePackage(PackageInterface $package): void
    {
        // The root package's name/metadata is locally controlled and already validated by
        // RootPackageLoader (and its "__root__" placeholder name would be a false positive here).
        // RootPackageInterface covers both RootPackage and RootAliasPackage.
        if ($package instanceof RootPackageInterface) {
            return;
        }

        // getName() is already lowercased, so the uppercase style branch never fires and only
        // structural/security failures throw. Platform packages return null here.
        if (null !== ($err = self::hasPackageNamingError($package->getName()))) {
            throw new SecurityException('Invalid package found during dependency resolution, aborting: '.$err);
        }

        // A url or reference starting with a "-" may be misinterpreted as a command-line option
        // by the VCS/download tooling, same protection as the source/dist checks done in load().
        $sourceDist = [
            'source.url' => $package->getSourceUrl(),
            'source.reference' => $package->getSourceReference(),
            'dist.url' => $package->getDistUrl(),
            'dist.reference' => $package->getDistReference(),
        ];
        foreach ($sourceDist as $field => $value) {
            if ($value !== null && Preg::isMatch('{^\s*-}', $value)) {
                throw new SecurityException($package->getName().' has an invalid '.$field.', it must not start with a "-": '.$value);
            }
        }

        // Bin paths are resolved relative to the package install dir and then chmod'd (and
        // proxied) by BinaryInstaller. A ".." segment escapes that directory and lets a

View on GitHub (pinned to c435d285c9)