{"record":{"id":"77c43fbf8304c602","repo":"schollz/croc","slug":"a-remote-path-contains-a-null-byte","errorCode":null,"errorMessage":"A remote path contains a null byte","messagePattern":"A remote path contains a null byte","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/metadata.ts","lineNumber":11,"sourceCode":"import { base64ToBytes } from \"./bytes\";\nimport type {\n  OfferedFile,\n  SenderInfoWire,\n  TransferOffer,\n  WireFileInfo,\n} from \"./types\";\n\nfunction cleanSegments(value: string) {\n  const replaced = value.replaceAll(\"\\\\\", \"/\");\n  if (replaced.includes(\"\\0\")) throw new Error(\"A remote path contains a null byte\");\n  const segments: string[] = [];\n  for (const segment of replaced.split(\"/\")) {\n    if (segment === \"\" || segment === \".\") continue;\n    if (segment === \"..\") throw new Error(`Remote path escapes the destination: ${value}`);\n    if ([...segment].some((character) => !/\\P{C}/u.test(character))) {\n      throw new Error(`Remote path contains a non-printable character: ${value}`);\n    }\n    segments.push(segment);\n  }\n  return segments;\n}\n\nexport function normalizeFolder(value = \".\") {\n  if (/^(?:[a-zA-Z]:|\\/)/.test(value)) {\n    throw new Error(`Remote path must be relative: ${value}`);\n  }\n  const segments = cleanSegments(value);\n  const normalized = segments.join(\"/\") || \".\";","sourceCodeStart":1,"sourceCodeEnd":29,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/metadata.ts#L1-L29","documentation":"cleanSegments() sanitizes every remote path coming from the peer's transfer offer. A NUL byte in a path is never legitimate and is a classic trick against path-based filesystems and logging pipelines, so it is rejected outright before any segment splitting.","triggerScenarios":"A received SenderInfo offer where a file's folder (fr), name (n), or an EmptyFoldersToTransfer entry contains '\\0' — e.g. crafted by a hostile sender (\"file.sh\\0.txt\") or produced by a mis-encoding peer.","commonSituations":"Security review / fuzzing of the receive path; a peer running on a system that allows NUL in filenames passed through verbatim; hostile sender attempting path-confusion attacks against the browser download sink.","solutions":["Reject or terminate the transfer from the offending peer — the metadata is malformed or malicious; there is no safe reinterpretation.","If you are the sender side, strip NUL bytes from paths before building the offer.","When fuzzing, treat this as an expected rejection, not a crash: wrap validateSenderInfo in try/catch and assert the error message."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function hasNoNullByte(path: string): boolean {\n  return !path.includes(\"\\0\");\n}\n// before validating an offer:\nif (!hasNoNullByte(wire.fr ?? \".\") || !hasNoNullByte(wire.n ?? \"\")) {\n  rejectOffer(\"null byte in remote path\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  const offer = validateSenderInfo(info);\n} catch (error) {\n  if (error instanceof Error && error.message === \"A remote path contains a null byte\") {\n    abortTransfer(); // hostile/malformed peer metadata; do not continue\n  }\n  throw error;\n}","preventionTips":["Treat remote path metadata as untrusted input; validate before persisting or displaying it.","Never log raw remote paths to systems that treat NUL as a terminator.","In fuzz suites, assert NUL-containing paths are rejected."],"tags":["security","path-validation","metadata","input-sanitization"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}