{"record":{"id":"99b8446447342de0","repo":"schollz/croc","slug":"remote-filename-must-be-a-basename-namevalue","errorCode":null,"errorMessage":"Remote filename must be a basename: ${nameValue}","messagePattern":"Remote filename must be a basename: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/metadata.ts","lineNumber":40,"sourceCode":"}\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(\"/\") || \".\";\n  if (normalized.includes(\".ssh\")) {\n    throw new Error(`Remote path is not allowed: ${value}`);\n  }\n  return normalized;\n}\n\nexport function normalizeFilePath(folderValue: string, nameValue: string) {\n  const folder = normalizeFolder(folderValue);\n  const nameSegments = cleanSegments(nameValue);\n  if (nameSegments.length !== 1 || nameSegments[0] !== nameValue.replaceAll(\"\\\\\", \"/\")) {\n    throw new Error(`Remote filename must be a basename: ${nameValue}`);\n  }\n  const name = nameSegments[0];\n  if (!name) throw new Error(\"Remote filename is empty\");\n  const path = folder === \".\" ? name : `${folder}/${name}`;\n  return { folder, name, path };\n}\n\nexport function normalizeOutgoingFileName(value: string) {\n  // Go's unicode.IsPrint accepts ASCII space but rejects the other Unicode\n  // separator characters commonly inserted into filenames by macOS.\n  const compatible = value.replace(/\\p{Z}+/gu, \" \");\n  return normalizeFilePath(\".\", compatible).name;\n}\n\nfunction finiteSize(file: WireFileInfo) {\n  const size = file.s ?? 0;\n  if (!Number.isSafeInteger(size) || size < 0) {\n    throw new Error(`Invalid file size for ${file.n ?? \"unnamed file\"}`);","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/metadata.ts#L22-L58","documentation":"normalizeFilePath() requires the file's name to be a single path segment: after backslash→slash replacement, cleanSegments(name) must yield exactly one segment equal to the replaced name. Any '/', multiple segments, or trailing slash (which would leave an empty final segment) fails this basename invariant.","triggerScenarios":"Calling normalizeFilePath(folder, name) with name='docs/file.txt', name='a/', name='dir/' (trailing slash yields a segment list mismatch or empty last segment), or any name containing separators. Reached from validateSenderInfo for each offered file and from normalizeOutgoingFileName.","commonSituations":"Sender-side code passing a full relative path as the filename instead of splitting folder/name; a hostile peer smuggling path components in n; sending a directory-shaped entry as a file; names ending in '/' from sloppy path joins.","solutions":["Split the path yourself: put directories in the folder argument and the last segment in name — normalizeFilePath('docs', 'file.txt').","Strip trailing slashes from names before offering.","Reject offers from peers whose file names contain separators (they should use fr for folders)."],"exampleFix":"// before\nnormalizeFilePath(\".\", \"docs/file.txt\");\n// after\nnormalizeFilePath(\"docs\", \"file.txt\");","handlingStrategy":"validation","validationCode":"function isBasename(value: string): boolean {\n  const replaced = value.replaceAll(\"\\\\\", \"/\");\n  return replaced.length > 0 && !replaced.includes(\"/\");\n}\nif (!isBasename(nameValue)) throw new TypeError(`not a basename: ${nameValue}`);","typeGuard":null,"tryCatchPattern":"try {\n  const { path } = normalizeFilePath(folder, name);\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith(\"Remote filename must be a basename\")) {\n    // split folder/name yourself and retry, or reject the offer\n    const idx = name.lastIndexOf(\"/\");\n    return normalizeFilePath(`${folder}/${name.slice(0, idx)}`, name.slice(idx + 1));\n  }\n  throw error;\n}","preventionTips":["Always split paths into (folder, basename) on the sender; keep 'n' a single segment.","Strip trailing slashes from names before offering.","Treat separator-containing names from peers as malformed metadata."],"tags":["path-validation","metadata","protocol"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}