{"record":{"id":"ecc53498948231ae","repo":"can1357/oh-my-pi","slug":"shared-folder-destination-escapes-its-configured-r","errorCode":null,"errorMessage":"Shared-folder destination escapes its configured root","messagePattern":"Shared-folder destination escapes its configured root","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts","lineNumber":281,"sourceCode":"\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn publication(\"ftp\", request, publicUrl(publicBase, directory, filename));\n\t\t},\n\t};\n}\n\nfunction createSharedFolderUploader(config: DestinationRuntimeConfig): BlobUploader {\n\tconst root = path.resolve(requiredStringOption(config, \"root\"));\n\tconst directory = optionString(config, \"path\");\n\tconst publicBase = requiredStringOption(config, \"publicBaseUrl\");\n\thttpBase(publicBase, \"publicBaseUrl\");\n\treturn {\n\t\tdestination: \"shared-folder\",\n\t\tasync upload(request) {\n\t\t\tconst filename = safeFileName(request);\n\t\t\tconst target = path.resolve(root, ...pathParts(directory), filename);\n\t\t\tif (target !== root && !target.startsWith(`${root}${path.sep}`)) {\n\t\t\t\tthrow new Error(\"Shared-folder destination escapes its configured root\");\n\t\t\t}\n\t\t\tawait Bun.write(target, request.bytes, { createPath: true });\n\t\t\treturn publication(\"shared-folder\", request, publicUrl(publicBase, directory, filename));\n\t\t},\n\t};\n}\n\nfunction createOwnCloudUploader(config: DestinationRuntimeConfig): BlobUploader {\n\tconst host = httpBase(requiredStringOption(config, \"host\"), \"host\").toString().replace(/\\/$/, \"\");\n\tconst username = requireCredential(config, \"username\");\n\tconst password = requireCredential(config, \"password\");\n\tconst directory = optionString(config, \"path\");\n\tconst direct = optionBoolean(config, \"directLink\", true) ?? true;\n\tconst preview = optionBoolean(config, \"previewLink\", false) ?? false;\n\tconst expiryDays = optionNumber(config, \"expiryDays\");\n\tconst authorization = basicAuthorization(username, password);\n\tconst requestHeaders = { Authorization: authorization, \"OCS-APIREQUEST\": \"true\" };\n\tconst requestFetch = fetchFor(config);","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts#L263-L299","documentation":"Thrown by the shared-folder uploader after resolving the final target path when it falls outside the configured root directory. This is a path-traversal guard: path.resolve collapses any ../ segments, and the resolved absolute target must equal the root or live beneath root + path separator, otherwise the upload is refused.","triggerScenarios":"options.path (directory) containing segments that escape root — e.g. path \"../../etc\" — or a filename crafted to traverse after pathParts() normalization; note pathParts already rejects \"..\" segments and NUL bytes, so this usually fires when root itself is symlinked/renamed or resolve produces an unexpected location.","commonSituations":"Symlinked subdirectory inside root pointing outside; root configured as a relative path that resolves differently than expected; hostile filenames in multi-tenant setups; mistyped options.path like \"/srv/share/../../var/www\".","solutions":["Inspect the resolved target vs configured root in the log; ensure options.path contains no traversal segments.","Remove or replace symlinks under the shared-folder root that point outside it, or point root at the real directory.","Set options.root to a canonical absolute path (no trailing separator, fully resolved) so the startsWith check matches.","If you genuinely need writes outside root, configure a second destination whose root is that directory instead of bypassing the guard."],"exampleFix":"// before\n{ \"options\": { \"root\": \"/srv/share\", \"path\": \"../other\" } }\n// after\n{ \"options\": { \"root\": \"/srv/share\", \"path\": \"uploads\" } }","handlingStrategy":"validation","validationCode":"import * as path from 'node:path';\nconst root = path.resolve(dest.options.root);\nconst target = path.resolve(root, dest.options.path ?? '.', safeFileName);\nif (target !== root && !target.startsWith(root + path.sep)) throw new Error('resolved upload path escapes shared-folder root');","typeGuard":null,"tryCatchPattern":"try {\n  await uploader.upload(request);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('escapes its configured root')) {\n    // reject the request as a security violation; audit the supplied path/filename\n  } else throw err;\n}","preventionTips":["Sanitize user-supplied paths: reject '..' segments and absolute paths before passing them as options.path.","Resolve root to a canonical absolute path at config load and log the resolved target on upload.","Avoid symlinks inside the shared root, or resolve and re-check the real path (fs.realpath) before writing.","Treat this error as a security signal — audit the input that produced it."],"tags":["security","path-traversal","filesystem","validation"],"backgroundTag":"path-traversal-detected","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}