{"record":{"id":"468538e0741ccfb2","repo":"can1357/oh-my-pi","slug":"destination-paths-cannot-contain-parent-traversal","errorCode":null,"errorMessage":"Destination paths cannot contain parent traversal or NUL bytes","messagePattern":"Destination paths cannot contain parent traversal or NUL bytes","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts","lineNumber":99,"sourceCode":"\treturn { id, uploadToken, ...(downloadBase ? { downloadBase } : {}) };\n}\n\nfunction requiredStringOption(config: DestinationRuntimeConfig, key: string): string {\n\tconst value = requireOption(config, key);\n\tif (typeof value !== \"string\" || value.trim() === \"\") {\n\t\tthrow new Error(`Destination option ${key} must be a non-empty string`);\n\t}\n\treturn value.trim();\n}\n\nfunction pathParts(value: string | undefined): string[] {\n\tif (!value) return [];\n\tconst parts = value.replaceAll(\"\\\\\", \"/\").split(\"/\");\n\tconst result: string[] = [];\n\tfor (const part of parts) {\n\t\tif (!part || part === \".\") continue;\n\t\tif (part === \"..\" || part.includes(\"\\0\"))\n\t\t\tthrow new Error(\"Destination paths cannot contain parent traversal or NUL bytes\");\n\t\tresult.push(part);\n\t}\n\treturn result;\n}\n\nfunction safeFileName(request: BlobUploadRequest): string {\n\tconst name = fileNameFor(request);\n\tif (name.includes(\"\\0\") || name === \".\" || name === \"..\") throw new Error(\"Upload filename is invalid\");\n\treturn name;\n}\n\nfunction remotePath(directory: string | undefined, filename: string): string {\n\tconst absolute = directory?.replaceAll(\"\\\\\", \"/\").startsWith(\"/\") ?? false;\n\tconst joined = [...pathParts(directory), filename].join(\"/\");\n\treturn absolute ? `/${joined}` : joined;\n}\n\nfunction encodedPath(parts: readonly string[]): string {","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts#L81-L117","documentation":"Thrown by pathParts when a configured destination directory path (or its segments) contains a '..' parent-traversal segment or a NUL byte. The library normalizes backslashes/slashes but refuses to build remote paths that could escape the intended root or break remote filesystems.","triggerScenarios":"Setting a destination option like root/publicBase/directory (used via joined, relative, ftpUploadUrl, target, parts, directoryParts) to something like '../../etc/webroot', 'a\\0b', or a path that after backslash-to-slash normalization contains '..'.","commonSituations":"Trying to write outside the configured web root by using relative parent paths, a directory value built from unsanitized user input containing NUL, or Windows-style relative paths like '..\\uploads'.","solutions":["Remove '..' segments from the directory/root option — use a path relative to the intended base, or a clean absolute path without traversal","Strip NUL bytes and backslashes from any user-supplied path before storing it in the destination config","If you need a different location on the remote, configure the server-side root rather than traversing with '..'","Pre-sanitize with the same rule the library uses: split on /, drop '' and '.', reject '..' and '\\0'"],"exampleFix":"// before\n{ \"root\": \"/var/www/../sensitive\" }\n// after\n{ \"root\": \"/srv/uploads\" } // point directly at the intended directory, no '..' segments","handlingStrategy":"validation","validationCode":"function assertSafeRemotePath(dir: string | undefined): void {\n  if (!dir) return;\n  for (const part of dir.replaceAll(\"\\\\\", \"/\").split(\"/\")) {\n    if (part === \"..\" || part.includes(\"\\0\")) throw new Error(`unsafe destination path: ${JSON.stringify(dir)}`);\n  }\n}\nassertSafeRemotePath(destConfig.root);\nassertSafeRemotePath(destConfig.directory);","typeGuard":"function isSafePathPart(part: string): boolean {\n  return part !== \"..\" && !part.includes(\"\\0\");\n}","tryCatchPattern":"try {\n  await uploader.upload(blob);\n} catch (err) {\n  if ((err as Error).message.includes(\"parent traversal or NUL\")) {\n    throw new Error(\"Destination directory contains '..' or NUL — configure a clean path under the intended root\");\n  }\n  throw err;\n}","preventionTips":["Never accept '..' segments in user-supplied destination paths","Strip NUL bytes and normalize backslashes from any path built from external input","Reconfigure the server-side root instead of traversing upward with '..'","Reuse the library's normalization rules (split on '/', drop '' and '.') in your own config validation"],"tags":["path-traversal","security","validation","self-hosted"],"backgroundTag":"path-traversal-rejected","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}