{"record":{"id":"159535c0b25eebc4","repo":"ruvnet/ruflo","slug":"sha256-mismatch-for-input-assetfilename-expect","errorCode":null,"errorMessage":"sha256 mismatch for ${input.assetFilename}: expected ${expected.slice(0, 12)}…, got ${actual.slice(0, 12)}…","messagePattern":"sha256 mismatch for (.+?): expected (.+?)…, got (.+?)…","errorType":"exception","errorClass":"ReleaseVerificationError","httpStatus":null,"severity":"critical","filePath":"v3/@claude-flow/cli/src/proxy/verify.ts","lineNumber":90,"sourceCode":" * Full verification: signature over SHA256SUMS, then the asset's own hash\n * against the matching line. Throws `ReleaseVerificationError` on ANY\n * failure — there is no partial-trust outcome, matching ADR-307's \"refuses\n * on any mismatch\" requirement.\n */\nexport function verifyRelease(input: VerifyReleaseInput): VerifyReleaseResult {\n  if (!verifySha256SumsSignature(input.sumsBytes, input.sigBase64, input.pubkeyPem)) {\n    throw new ReleaseVerificationError('SHA256SUMS.sig failed Ed25519 verification — refusing to install');\n  }\n\n  const sums = parseSha256Sums(input.sumsBytes.toString('utf-8'));\n  const expected = sums[input.assetFilename];\n  if (!expected) {\n    throw new ReleaseVerificationError(`SHA256SUMS has no entry for ${input.assetFilename}`);\n  }\n\n  const actual = sha256Hex(input.assetBytes);\n  if (actual !== expected) {\n    throw new ReleaseVerificationError(\n      `sha256 mismatch for ${input.assetFilename}: expected ${expected.slice(0, 12)}…, got ${actual.slice(0, 12)}…`,\n    );\n  }\n\n  return { sha256: actual };\n}\n","sourceCodeStart":72,"sourceCodeEnd":97,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/proxy/verify.ts#L72-L97","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import { createHash } from 'node:crypto';\n// Independent pre-check: hash a manual download, compare with the signed manifest\nconst expected = parseSha256Sums(sumsText)[assetName];\nconst actual = createHash('sha256').update(archiveBytes).digest('hex');\nif (actual !== expected) throw new Error('bytes differ from signed manifest — redownload');","typeGuard":"const isReleaseVerification = (e: unknown): e is Error & { name: 'ReleaseVerificationError' } =>\n  e instanceof Error && e.name === 'ReleaseVerificationError';","tryCatchPattern":"let lastErr: unknown;\nfor (let i = 0; i < 2; i++) {\n  try {\n    return verifyRelease(input);\n  } catch (e) {\n    lastErr = e;\n    if (!(isReleaseVerification(e) && /sha256 mismatch/.test(e.message))) throw e;\n    input = await refetchAssets(); // corruption in transit → exactly one redownload\n  }\n}\nthrow lastErr; // persistent mismatch = possible tampering: stop and report","preventionTips":["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"],"tags":["security","sha256","integrity","download-corruption","supply-chain"],"backgroundTag":"checksum-mismatch","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}