{"record":{"id":"141bd989ae3eebb9","repo":"schollz/croc","slug":"the-stored-file-failed-sha-256-verification-hex","errorCode":null,"errorMessage":"The stored file failed SHA-256 verification (${hex(actual)} != ${hex(expected)})","messagePattern":"The stored file failed SHA-256 verification \\((.+?) != (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"web/src/protocol/storage.ts","lineNumber":401,"sourceCode":"  return new DownloadDestination();\n}\n\nexport async function verifySink(sink: ReceiveSink, expected: Uint8Array) {\n  const actual = await sink.hash();\n  if (!bytesEqual(actual, expected)) {\n    throw new Error(\n      `The sender advertised xxhash ${hex(expected)}, but the received file hashes to ${hex(actual)}`,\n    );\n  }\n}\n\nexport async function verifySinkSHA256(\n  sink: ReceiveSink,\n  expected: Uint8Array,\n) {\n  const actual = await sink.hash(\"sha256\");\n  if (!bytesEqual(actual, expected)) {\n    throw new Error(\n      `The stored file failed SHA-256 verification (${hex(actual)} != ${hex(expected)})`,\n    );\n  }\n}\n","sourceCodeStart":383,"sourceCodeEnd":406,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/storage.ts#L383-L406","documentation":"Thrown by verifySinkSHA256 when a stored-transfer file, after being written and finalized, hashes to a SHA-256 different from the value recorded in the encrypted manifest (field h). In the stored receive flow every downloaded file is checked this way before commit; a mismatch means the decrypted bytes do not match what the sender uploaded.","triggerScenarios":"downloadStoredFile() calls verifySinkSHA256(sink, fromBase64URL(storedFile.h)) after finalize(); the recomputed digest differs. Can be caused by corrupted chunk ciphertext that nonetheless passed AEAD (unlikely), a manifest/chunk index mix-up, or a buggy sink that drops or duplicates bytes.","commonSituations":"Wrong chunk written at wrong offset (position math bugs), concurrent downloads of the same share interleaving writes, or a service worker streaming bug dropping chunks. Because storeOpenChunk is AEAD-authenticated, most real instances are client write-path bugs rather than network corruption.","solutions":["Retry the receive; the claim/verified-download session bookkeeping makes re-download safe","If reproducible, capture both digests and the file index — check sink.writeAt position math and chunk ordering (streaming sinks require strictly sequential chunks)","Confirm the web client version matches the storage service chunk size (4 MiB); a size mismatch usually fails earlier but can manifest here"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await verifySinkSHA256(sink, fromBase64URL(storedFile.h)); } catch (e) { await sink.abort().catch(() => {}); if (e instanceof Error && e.message.includes(\"SHA-256 verification\")) { return restartStoredReceive(inspection); } throw e; }","preventionTips":["Verify hashes before sink.commit() so a failed check never touches the user's download","On mismatch, restart from inspection rather than resuming — stored chunks are individually AEAD-protected, so a mismatch signals logic corruption, not a flaky network"],"tags":["integrity","sha256","verification","stored-transfer","corruption"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}