JuliusBrussee/caveman · error · Error
signed checksum manifest is malformed
Error message
signed checksum manifest is malformed
What it means
`expectedDigest(checksums, artifact)` parses the signed checksums.txt manifest line by line, expecting each non-empty line to match `^([a-f0-9]{64}) ([A-Za-z0-9._-]+)$` (sha256 hex digest, two spaces, filename). If any line deviates from that strict shape, the manifest cannot be trusted, so the installer throws before attempting any digest comparison. This is a supply-chain guard: a malformed manifest is treated as hostile or corrupt.
Solutions
- curl the checksums.txt URL and inspect it: confirm every non-empty line is `<64 hex chars> <filename>`.
- Check for CRLF line endings (`file` or `cat -A`); regenerate or normalize the manifest to LF.
- Verify you are not getting an HTML error/login page through a proxy — bypass or authenticate the proxy.
- If artifact names legitimately contain characters like `+` outside `[A-Za-z0-9._-]`, rename the artifact or extend the regex in expectedDigest.
- Fall back to installing the binary manually and pointing the envVar at it.
Example fix
// before: manifest line with one space "abc123... artifact.tgz" -> "abc123... artifact.tgz" (malformed) // after: regenerate with correct two-space format (sha256sum style) "abc123... artifact.tgz"
Defensive patterns
Strategy: validation
Validate before calling
function manifestLooksValid(text) {
return text.split("\n").filter(Boolean).every(l => /^[a-f0-9]{64} [A-Za-z0-9._-]+$/.test(l));
}
// fetch checksums.txt yourself and assert manifestLooksValid before ensureBinary Try / catch
try {
await ensureBinary({ name, envVar });
} catch (e) {
if (String(e.message).includes("manifest is malformed")) {
const raw = await fetch(`${release}/checksums.txt`).then(r => r.text());
console.error("Manifest preview:", raw.slice(0, 500));
}
throw e;
} Prevention
- Check for proxies/captive portals that substitute HTML for plain-text files.
- Normalize line endings to LF when regenerating manifests.
- Keep artifact names within [A-Za-z0-9._-] to match the parser.
- Diff the fetched manifest against the published one on a good network.
When it happens
Trigger: Calling `ensureBinary()` whose `signedDigest`/`expectedDigest` path receives a checksums.txt that contains any line failing the regex — e.g. MD5-length digests, blank-but-whitespace lines, filenames with spaces or `+`/`~` characters, CRLF line endings leaving a trailing `\r`, or HTML error text saved instead of the manifest.
Common situations: Proxy or captive portal returning an HTML page for checksums.txt; release tooling changed manifest format (different hash size, single space separator); Windows CRLF artifacts when the manifest was regenerated; artifact names with `+` (common in versions) that the regex rejects.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- cave_harness_artifact_digest_invalid
- signed checksum manifest does not contain
- adapter-specific build_sha256 values must differ
- apiKey, baseURL, and agent are required
- arm must be or
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/92a2c96f8a16e30c.
Report an issue: GitHub.
Appendix: source
Thrown at packages/shared/binary-installer/installer.mjs:121
const digest = createHash("sha256").update(checksums).digest();
const bundled = Buffer.from(bundle.messageSignature.messageDigest.digest, "base64");
if (digest.length !== bundled.length || !digest.equals(bundled)) return false;
return verify(
"sha256",
Buffer.from(checksums),
createPublicKey(BINARY_SIGNING_PUBKEY),
Buffer.from(bundle.messageSignature.signature, "base64"),
);
} catch {
return false;
}
}
function expectedDigest(checksums, artifact) {
for (const line of checksums.split("\n")) {
if (!line) continue;
const match = line.match(/^([a-f0-9]{64}) ([A-Za-z0-9._-]+)$/);
if (!match) throw new Error("signed checksum manifest is malformed");
if (match[2] === artifact) return match[1];
}
throw new Error(`signed checksum manifest does not contain ${artifact}`);
}
function cleanup(path) {
try {
unlinkSync(path);
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
}
async function download(url, part, timeout) {
const response = await asset(url, timeout);
if (!response.body) throw new Error("binary download failed: response body missing");
const hash = createHash("sha256");
const file = await open(part, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);View on GitHub (pinned to 3ee70a1026)