{"record":{"id":"a44f8b92d6d626d5","repo":"schollz/croc","slug":"stored-transfer-manifest-is-too-large","errorCode":null,"errorMessage":"Stored-transfer manifest is too large","messagePattern":"Stored-transfer manifest is too large","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/src/protocol/stored.ts","lineNumber":673,"sourceCode":"    senderMachineID: \"encrypted temporary storage\",\n    noCompress: true,\n  };\n}\n\nexport async function inspectStoredTransfer(\n  share: StoredShare,\n  settings: StoredSettings,\n  signal?: AbortSignal,\n) {\n  const redeem = await wasm().storeRedeemCapability(share.key);\n  const response = await authorizedFetch(\n    api(settings, `/${share.id}/manifest`),\n    base64URL(redeem),\n    { signal },\n  );\n  const ciphertext = new Uint8Array(await response.arrayBuffer());\n  if (ciphertext.byteLength > maxManifestCiphertext) {\n    throw new Error(\"Stored-transfer manifest is too large\");\n  }\n  const plaintext = await wasm().storeOpenManifest(\n    share.key,\n    share.id,\n    ciphertext,\n    settings.maxTransferBytes,\n  );\n  const manifest = JSON.parse(textDecoder.decode(plaintext)) as StoredManifest;\n  return {\n    share,\n    manifest,\n    offer: offerFromManifest(manifest),\n    expiresAt: response.headers.get(\"X-Croc-Expires-At\") ?? undefined,\n  } satisfies StoredInspection;\n}\n\nfunction claimSessionKey(id: string) {\n  return `croc-store-claim:${id}`;","sourceCodeStart":655,"sourceCodeEnd":691,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/stored.ts#L655-L691","documentation":"Thrown when fetching a stored transfer's manifest: the ciphertext returned by GET /{share.id}/manifest exceeds maxManifestCiphertext (256 KiB, stored.ts:17). The manifest lists every file in the transfer; this guard stops the client from feeding an oversized (possibly malicious) payload into wasm storeOpenManifest, which would otherwise allocate proportionally.","triggerScenarios":"Inspecting/receiving a share whose encrypted manifest exceeds 256 KiB: transfers with an enormous number of files (each entry carries name, size, hash, mtime). Can also fire if the service returns a corrupted or attacker-controlled oversized body.","commonSituations":"Sharing directory trees like node_modules, photo archives, or build output with tens of thousands of files; a misbehaving storage endpoint returning junk.","solutions":["Reduce file count: zip/archive the directory and store the single archive","Split into several smaller stored transfers","If you fork/operate the client, review whether the 256 KiB cap suits your file counts"],"exampleFix":"# before\nawait uploadStoredFiles({ files: everyFileInNodeModules, settings });\n\n# after\nconst bundle = await zipDirectory(nodeModulesDir);\nawait uploadStoredFiles({ files: [bundle], settings });","handlingStrategy":"validation","validationCode":"// Rough manifest-size projection: ~130+ bytes per file entry after encryption overhead\nconst projected = files.reduce((n, f) => n + f.name.length + 128, 0);\nif (projected > 200_000) throw new Error('too many files for one stored transfer; archive the directory first');","typeGuard":null,"tryCatchPattern":"try { await inspectStoredTransfer(share, settings, signal); } catch (e) { if (e instanceof Error && e.message === 'Stored-transfer manifest is too large') { tellSenderToArchiveAndResend(); return; } throw e; }","preventionTips":["Archive large directory trees into one file before storing","Keep stored transfers to a few thousand files or fewer"],"tags":["stored-transfer","limits","manifest","download"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}