{"record":{"id":"cf660424e5a547c0","repo":"schollz/croc","slug":"remote-path-contains-a-non-printable-character","errorCode":null,"errorMessage":"Remote path contains a non-printable character: ${value}","messagePattern":"Remote path contains a non-printable character: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/metadata.ts","lineNumber":17,"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(\"/\") || \".\";\n  if (normalized.includes(\".ssh\")) {\n    throw new Error(`Remote path is not allowed: ${value}`);\n  }\n  return normalized;\n}\n","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/metadata.ts#L1-L35","documentation":"cleanSegments() checks each path segment's characters against the Unicode 'Other' category (\\P{C}: control, format, surrogate, private-use, unassigned) and rejects any segment containing one. Only printable characters are allowed in remote filenames, matching Go's unicode.IsPrint expectations and preventing terminal/download-sink confusion.","triggerScenarios":"A peer-supplied path segment containing control characters (e.g. U+0007 bell, U+001B escape), zero-width/format characters (U+200B, U+202E RTL override), lone surrogates, or other non-printable code points. Note the check runs on each character, so even one hidden character anywhere in a segment triggers it.","commonSituations":"Filenames copied from terminal output with embedded ANSI escapes; spoofing attempts using bidi/zero-width characters to disguise extensions; macOS filenames with unusual Unicode; hostile sender deliberately testing the sanitization gate.","solutions":["Reject the transfer from the offending peer; hidden characters in paths are a spoofing vector.","As a sender, sanitize filenames to printable Unicode before offering (see normalizeOutgoingFileName for the outgoing equivalent that also maps \\p{Z} separators to ASCII space).","If a legit file triggers it, rename the file on the sender to remove the invisible characters and re-offer."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function isPrintablePath(value: string): boolean {\n  return [...value.replaceAll(\"\\\\\", \"/\")].every((ch) => /\\P{C}/u.test(ch));\n}","typeGuard":null,"tryCatchPattern":"try {\n  const offer = validateSenderInfo(info);\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith(\"Remote path contains a non-printable character\")) {\n    rejectOffer(\"non-printable characters in path\");\n    return;\n  }\n  throw error;\n}","preventionTips":["Display remote filenames with escapes/JSON encoding so hidden characters cannot spoof UI.","Sanitize outgoing filenames to printable Unicode before building offers.","Treat zero-width and bidi control characters in filenames as a spoofing red flag."],"tags":["security","unicode","path-validation","metadata"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}