{"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/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/proxy/verify.ts#L72-L97","documentation":"Thrown by verifyRelease() when the Ed25519 signature on SHA256SUMS passed AND the asset filename is present in the manifest, but sha256Hex(assetBytes) does not equal the expected hex from the signed manifest. Of the three verification gates this is the strongest tamper signal: a trusted manifest says what the hash should be, and the bytes you have don't match. ADR-307 mandates refuse-on-any-mismatch with no partial-trust outcome, so this must abort the install.","triggerScenarios":"Truncated or partially downloaded asset (network drop, proxy timeout mid-stream); corrupted cache (disk error, interrupted previous write); a genuinely tampered binary (MITM, compromised mirror); verifying a re-compressed/repackaged artifact whose bytes differ from what was signed; reading the file in text mode on Windows so line endings are rewritten.","commonSituations":"CDN served a stale cached copy from a previous release under the new URL; a corporate proxy re-compressed the tarball; the download was resumed with `curl -C -` against a different version; the asset was piped through a tool that strips/converts bytes (git's autocrlf, a Docker layer re-tar); the file was written with a different encoding flag in readFileSync.","solutions":["Re-download the asset from scratch (no resume) and re-verify — most mismatches are incomplete downloads.","Confirm you are reading the asset with the same binary fidelity it was signed with: fs.readFileSync(path) with no encoding argument (Buffer, not utf-8 string).","Cross-check the expected hash from SHA256SUMS against a fresh `sha256sum <file>` in your shell — if those agree but verifyRelease still fails, assetBytes is being transformed before the call.","If the mismatch persists on a clean re-download, escalate to the release signer/maintainer: either the manifest or the published binary is wrong, and installing is unsafe."],"exampleFix":"// before — assetBytes possibly transformed (text read, re-encoded)\nconst assetBytes = Buffer.from(fs.readFileSync(path, 'utf-8'), 'utf-8');\nverifyRelease({ assetFilename, sumsBytes, sigBase64, assetBytes });\n\n// after — read raw bytes, no encoding conversion\nconst assetBytes = fs.readFileSync(path); // Buffer, binary-safe\nverifyRelease({ assetFilename, sumsBytes, sigBase64, assetBytes });","handlingStrategy":"validation","validationCode":"import { createHash } from 'node:crypto';\nimport { parseSha256Sums, verifySha256SumsSignature } from './verify';\n\nfunction preflightAsset(input: {\n  sumsBytes: Buffer; sigBase64: string; assetBytes: Buffer; assetFilename: string;\n}): { ok: true; sha256: string } | { ok: false; reason: string } {\n  if (!verifySha256SumsSignature(input.sumsBytes, input.sigBase64)) {\n    return { ok: false, reason: 'signature failed — do not install' };\n  }\n  const sums = parseSha256Sums(input.sumsBytes.toString('utf-8'));\n  const expected = sums[input.assetFilename];\n  if (!expected) return { ok: false, reason: 'no manifest entry' };\n  const actual = createHash('sha256').update(input.assetBytes).digest('hex');\n  return actual === expected\n    ? { ok: true, sha256: actual }\n    : { ok: false, reason: `expected ${expected}, got ${actual}` };\n}\n\n// re-download if preflight fails before calling verifyRelease\nconst check = preflightAsset({ sumsBytes, sigBase64, assetBytes, assetFilename });\nif (!check.ok && /expected .* got/.test(check.reason)) {\n  assetBytes = fs.readFileSync(reDownloadAsset(assetFilename)); // fresh download\n}","typeGuard":null,"tryCatchPattern":"try {\n  verifyRelease(input);\n} catch (e) {\n  if (e instanceof ReleaseVerificationError && e.message.startsWith('sha256 mismatch')) {\n    // tamper or corruption. Re-download ONCE from a trusted source; if it still\n    // fails, halt and escalate — do not loop.\n    await reDownloadFromTrustedSource(input.assetFilename);\n    verifyRelease({ ...input, assetBytes: fs.readFileSync(localPath) });\n  } else {\n    throw e;\n  }\n}","preventionTips":["Read asset bytes with fs.readFileSync(path) (Buffer) — never pass an encoding that rewrites bytes.","Download assets atomically (temp file + rename) so partial files aren't verified.","Cross-check expected hash with `sha256sum` in CI before invoking verifyRelease.","Treat a persistent mismatch as a security incident, not a transient failure."],"tags":["security","supply-chain","checksums","tamper-detection","adr-307"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}