composer/composer · error · RuntimeException
Failed loading the phar signature from {sigSource}, got {sig
Error message
Failed loading the phar signature from {sigSource}, got {signature} What it means
Thrown by SelfUpdateCommand::verifyPhar() when the downloaded signature body cannot be decoded: the JSON is parsed into $signatureData, then base64_decode($signatureData['sha384']) returns false, meaning the signature payload is malformed (missing 'sha384' key, not valid base64, or empty). Composer refuses to proceed because it cannot obtain the raw signature bytes needed for openssl_verify.
Source
Thrown at src/Composer/Command/SelfUpdateCommand.php:580
TzCFWGk/HM6a4f0IzBWbJ5ot0PIi4amk07IotBXDWwqDiQTwyuGCym5EqWQ2BD95
RGv89BPD+2DLnJysngsvVaUCAwEAAQ==
-----END PUBLIC KEY-----
TAGSPUBKEY
);
}
$pubkeyid = openssl_pkey_get_public($sigFile);
if (false === $pubkeyid) {
throw new \RuntimeException('Failed loading the public key from '.$sigFile);
}
$algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384';
if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()), true)) {
throw new \RuntimeException('SHA384 is not supported by your openssl extension, could not verify the phar file integrity');
}
$signatureData = json_decode($signature, true);
$signatureSha384 = base64_decode($signatureData['sha384'], true);
if (false === $signatureSha384) {
throw new \RuntimeException('Failed loading the phar signature from '.$sigSource.', got '.$signature);
}
$verified = 1 === openssl_verify((string) file_get_contents($pharPath), $signatureSha384, $pubkeyid, $algo);
// PHP 8 automatically frees the key instance and deprecates the function
if (\PHP_VERSION_ID < 80000) {
// @phpstan-ignore function.deprecated
openssl_free_key($pubkeyid);
}
if (!$verified) {
throw new \RuntimeException('The phar signature did not match the file you downloaded, this means your public keys are outdated or that the phar file is corrupt/has been modified');
}
}
/**
* Warns when a path Composer trusts is owned by another user or writable by group/other users.
*
* Such a location could let another user tamper with files that Composer later trusts, e.g. aView on GitHub (pinned to 6ffc117740)
Solutions
- Re-run `composer self-update` to re-fetch the signature cleanly.
- Check network/proxy: if behind a corporate proxy, ensure it allows raw downloads of .phar and .sig from getcomposer.org/download; inspect what the proxy returns.
- Clear the local cache / COMPOSER_HOME of any partial files and retry, or set COMPOSER_CAFILE to a valid CA bundle if TLS interception is mangling responses.
Example fix
// before: signature fetch returned a non-JSON body
// $signature = '<html>503 Service Unavailable</html>'
// base64_decode($signatureData['sha384'], true) === false -> throws
// after: verify the sig body is JSON with a sha384 field before passing to verifyPhar
$data = json_decode($signature, true);
if (!is_array($data) || !isset($data['sha384']) || false === base64_decode($data['sha384'], true)) {
throw new \RuntimeException('Signature endpoint returned an unusable body, retrying later.');
} Defensive patterns
Strategy: retry
Validate before calling
// Pre-validate a fetched signature body before handing it to verifyPhar
$signature = @file_get_contents($sigUrl);
$data = json_decode((string) $signature, true);
if (!is_array($data) || !array_key_exists('sha384', $data) || false === base64_decode($data['sha384'], true)) {
throw new \RuntimeException('Signature body unusable; re-download from '.$sigUrl);
} Type guard
/** @param mixed $sig raw signature response body */
function isValidComposerSignature($sig): bool {
$data = is_string($sig) ? json_decode($sig, true) : null;
return is_array($data)
&& isset($data['sha384'])
&& is_string($data['sha384'])
&& false !== base64_decode($data['sha384'], true);
} Try / catch
for ($attempt = 1; $attempt <= 3; $attempt++) {
try {
verifyPhar($pharPath, $signature, $verifyAsTag, $home, $sigSource);
break;
} catch (\RuntimeException $e) {
if (str_contains($e->getMessage(), 'Failed loading the phar signature') && $attempt < 3) {
$signature = reFetchSignature($sigSource);
continue;
}
throw $e;
}
} Prevention
- Configure a valid CA bundle (COMPOSER_CAFILE) so TLS responses are not mangled by interception.
- Whitelist getcomposer.org/download on corporate proxies for raw .sig files.
- On intermittent failures, retry the self-update a few times before escalating.
When it happens
Trigger: A self-update fetch where the .sig endpoint returned an HTML error page, a partial/truncated body, a proxy-injected payload, or a non-JSON string. The `$signature` string passed to verifyPhar() either is not valid JSON or its 'sha384' field is not valid base64, so base64_decode(..., true) returns false at src/Composer/Command/SelfUpdateCommand.php:580.
Common situations: Corporate MITM proxy returning an auth/blank page instead of the .sig; transient network interruption truncating the download; a wrong endpoint/mirror serving stale or wrong content; COMPOSER_HOME or a manually-supplied sig being corrupted.
Related errors
- The phar signature did not match the file you downloaded, th
- Composer update failed: the "{localFilename}" is not accessi
- The contents of {filename} do not match its signature. This
- SHA384 is not supported by your openssl extension, could not
- 0
AI-assisted analysis of composer/composer@6ffc117740 (2026-08-07).
Data as JSON: /api/errors/78e8b676c1fd3e63.
Report an issue: GitHub.