{"record":{"id":"721d1baf3e0f9e90","repo":"schollz/croc","slug":"streaming-downloads-support-sha-256-verification-o","errorCode":null,"errorMessage":"Streaming downloads support SHA-256 verification only","messagePattern":"Streaming downloads support SHA-256 verification only","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/storage.ts","lineNumber":268,"sourceCode":"    if (position !== this.offset) {\n      throw new Error(\"Streaming browser downloads require sequential chunks\");\n    }\n    await wasm().sha256Update(this.hashHandle, bytes);\n    const copy = Uint8Array.from(bytes);\n    await this.send({ type: \"chunk\", bytes: copy.buffer }, [copy.buffer]);\n    this.offset += bytes.byteLength;\n  }\n\n  async finalize() {\n    if (this.closed) return;\n    this.closed = true;\n    this.digest = await wasm().sha256Final(this.hashHandle);\n    await this.send({ type: \"end\" });\n  }\n\n  async hash(algorithm: \"xxhash\" | \"sha256\" = \"sha256\") {\n    if (algorithm !== \"sha256\") {\n      throw new Error(\"Streaming downloads support SHA-256 verification only\");\n    }\n    if (!this.digest) throw new Error(\"Destination must be finalized before hashing\");\n    return this.digest;\n  }\n\n  async commit() {}\n\n  async abort() {\n    if (this.closed) return;\n    this.closed = true;\n    await this.send({ type: \"abort\" }).catch(() => undefined);\n  }\n}\n\nexport class StreamingDownloadDestination implements ReceiveDestination {\n  async createEmptyFolder() {}\n\n  async openFile(file: OfferedFile) {","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/storage.ts#L250-L286","documentation":"Thrown by StreamingDownloadSink.hash() when called with \"xxhash\". The streaming download sink (service-worker based receive path used when File System Access API is unavailable) hashes data incrementally with SHA-256 as chunks are written, so it physically has no xxhash digest to return. Any verification flow that requests the xxhash algorithm on this sink fails immediately.","triggerScenarios":"Calling sink.hash(\"xxhash\") or verifySink(sink, expected) (which defaults to xxhash) on a StreamingDownloadDestination-produced sink. This happens when receive code paths written for DirectorySink/DownloadSink are reused verbatim with StreamingDownloadDestination.","commonSituations":"Receiving on Firefox/Safari (no showDirectoryPicker) where chooseStoredReceiveDestination returns a StreamingDownloadDestination, then running the legacy xxhash verification step; adding a new destination type without updating the verification branch to use verifySinkSHA256.","solutions":["Use verifySinkSHA256(sink, expected) / sink.hash(\"sha256\") for streaming destinations","Branch on destination type before verification: xxhash for Directory/Download sinks, SHA-256 for StreamingDownloadSink","Have the sender/manifest also advertise SHA-256 (stored transfers already store per-file SHA-256 in manifest field h) so SHA-256 verification is always possible"],"exampleFix":"// before\nawait verifySink(sink, offered.hash); // xxhash -> throws on StreamingDownloadSink\n\n// after\nawait verifySinkSHA256(sink, expectedSHA256); // matches StreamingDownloadSink.hash(\"sha256\")","handlingStrategy":"type-guard","validationCode":"import { StreamingDownloadDestination } from \"../protocol/storage\";\n// destination comes from chooseStoredReceiveDestination()","typeGuard":"const isStreaming = (d: ReceiveDestination): boolean => d instanceof StreamingDownloadDestination;\n// then: isStreaming(d) ? await verifySinkSHA256(sink, sha) : await verifySink(sink, xxhash)","tryCatchPattern":"try { await sink.hash(\"sha256\"); } catch (e) { if (e instanceof Error && e.message.includes(\"SHA-256 verification only\")) { /* branch to sha256 */ } throw e; }","preventionTips":["Always pair StreamingDownloadDestination sinks with verifySinkSHA256, never verifySink","Require stored manifests to carry SHA-256 (they do: field h) and make SHA-256 the universal verification path"],"tags":["streaming","hash","verification","service-worker","browser"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}