{"record":{"id":"a9e7d5aabb11a7bd","repo":"schollz/croc","slug":"remote-path-is-not-allowed-value","errorCode":null,"errorMessage":"Remote path is not allowed: ${value}","messagePattern":"Remote path is not allowed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/metadata.ts","lineNumber":31,"sourceCode":"  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(\"/\") || \".\";\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","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/metadata.ts#L13-L49","documentation":"normalizeFolder() rejects any normalized folder whose joined path contains the substring '.ssh'. This blocks a malicious transfer from overwriting the receiver's SSH keys/authorized_keys, a classic remote-code-execution vector when downloads land in or near the user's home directory.","triggerScenarios":"An offer folder that normalizes to something containing '.ssh' anywhere — e.g. '.ssh', 'backup/.ssh', or 'x.ssh.y'. The check is substring-based on the joined normalized path, so it is intentionally broad.","commonSituations":"Hostile sender attempting SSH key overwrite; a user legitimately syncing a dotfiles repo that includes .ssh (must be excluded); security scanners asserting the guard fires.","solutions":["Exclude .ssh from any folder-sync source on the sender before offering.","As a receiver, refuse the transfer when this fires — the guard is intentional and must not be bypassed.","If you truly need to transfer SSH configs, use a renamed folder and restore manually after the transfer."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function mentionsSsh(normalizedFolder: string): boolean {\n  return normalizedFolder.includes(\".ssh\");\n}\n// after your own normalization, before accepting:\nif (mentionsSsh(folder)) rejectOffer(\".ssh destinations are blocked\");","typeGuard":null,"tryCatchPattern":"try {\n  const folder = normalizeFolder(value);\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith(\"Remote path is not allowed\")) {\n    rejectOffer(\"path touches .ssh\"); // intentional guard — never bypass\n    return;\n  }\n  throw error;\n}","preventionTips":["Never bypass or special-case this guard to 'make a transfer work' — it blocks SSH key overwrite attacks.","Exclude .ssh from sender-side folder selections before offering.","Transfer SSH configs via a renamed container folder and restore manually."],"tags":["security","path-validation","ssh","metadata"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}