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

View on GitHub (pinned to 6ffc117740)

Solutions

  1. Re-run `composer self-update` to re-fetch the signature cleanly.
  2. 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.
  3. 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

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


AI-assisted analysis of composer/composer@6ffc117740 (2026-08-07). Data as JSON: /api/errors/78e8b676c1fd3e63. Report an issue: GitHub.