ruvnet/ruflo · critical · ReleaseVerificationError
sha256 mismatch for : expected …, got …
Error message
sha256 mismatch for ${input.assetFilename}: expected ${expected.slice(0, 12)}…, got ${actual.slice(0, 12)}… What it means
The final gate of verifyRelease(): the sha256 of the downloaded archive must equal the signature-verified SHA256SUMS entry for that asset. A mismatch means the bytes received are not the bytes that were signed — truncated transfer, corrupted cache, or deliberate replacement. Only the first 12 hex characters of each hash are shown for comparison; the install aborts.
Solutions
- Re-run `ruflo proxy install` after clearing caches — truncation/corruption in transit is the most common cause and a fresh download usually verifies
- Manually cross-check: download the archive and SHA256SUMS yourself and run sha256sum to see the full hashes
- If the mismatch reproduces from clean networks, stop and report it — a persistent mismatch against a validly-signed manifest signals real tampering and must be escalated, not retried around
Defensive patterns
Strategy: retry
Validate before calling
import { createHash } from 'node:crypto';
// Independent pre-check: hash a manual download, compare with the signed manifest
const expected = parseSha256Sums(sumsText)[assetName];
const actual = createHash('sha256').update(archiveBytes).digest('hex');
if (actual !== expected) throw new Error('bytes differ from signed manifest — redownload'); Type guard
const isReleaseVerification = (e: unknown): e is Error & { name: 'ReleaseVerificationError' } =>
e instanceof Error && e.name === 'ReleaseVerificationError'; Try / catch
let lastErr: unknown;
for (let i = 0; i < 2; i++) {
try {
return verifyRelease(input);
} catch (e) {
lastErr = e;
if (!(isReleaseVerification(e) && /sha256 mismatch/.test(e.message))) throw e;
input = await refetchAssets(); // corruption in transit → exactly one redownload
}
}
throw lastErr; // persistent mismatch = possible tampering: stop and report Prevention
- Disable content-mutating caches for the dist domain
- Retry at most once — a persistent mismatch must be escalated, not retried around
- Cross-check with a manual sha256sum before reporting upstream
When it happens
Trigger: Truncated downloads (dropped connections, partial proxy responses); a caching layer serving stale or corrupt bytes for the asset URL; genuine tampering where the archive differs from the signed manifest.
Common situations: Flaky networks behind TLS-terminating proxies; CI caches keyed on URL but not content; mirrors serving different bytes; disk issues corrupting temp files before hashing.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- KV cache integrity check failed: hash mismatch
- SHA256SUMS.sig failed Ed25519 verification — refusing to…
- AI budget file is a symlink (refusing)
- AI job registry is a symlink (refusing)
- AIDefence failed to load
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/159535c0b25eebc4.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/proxy/verify.ts:90
* Full verification: signature over SHA256SUMS, then the asset's own hash
* against the matching line. Throws `ReleaseVerificationError` on ANY
* failure — there is no partial-trust outcome, matching ADR-307's "refuses
* on any mismatch" requirement.
*/
export function verifyRelease(input: VerifyReleaseInput): VerifyReleaseResult {
if (!verifySha256SumsSignature(input.sumsBytes, input.sigBase64, input.pubkeyPem)) {
throw new ReleaseVerificationError('SHA256SUMS.sig failed Ed25519 verification — refusing to install');
}
const sums = parseSha256Sums(input.sumsBytes.toString('utf-8'));
const expected = sums[input.assetFilename];
if (!expected) {
throw new ReleaseVerificationError(`SHA256SUMS has no entry for ${input.assetFilename}`);
}
const actual = sha256Hex(input.assetBytes);
if (actual !== expected) {
throw new ReleaseVerificationError(
`sha256 mismatch for ${input.assetFilename}: expected ${expected.slice(0, 12)}…, got ${actual.slice(0, 12)}…`,
);
}
return { sha256: actual };
}
View on GitHub (pinned to fa13ee4ad6)