{"record":{"id":"366c960729aa6960","repo":"slackhq/nebula","slug":"path-q-is-outside-the-sandbox-directory-q","errorCode":null,"errorMessage":"path %q is outside the sandbox directory %q","messagePattern":"path %q is outside the sandbox directory %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ssh.go","lineNumber":551,"sourceCode":"// If sandboxDir is empty, the path is returned as-is for backwards compatibility.\nfunc sshSanitizeFilePath(sandboxDir, filePath string) (string, error) {\n\tif sandboxDir == \"\" {\n\t\treturn filePath, nil\n\t}\n\n\t// Clean and resolve the path relative to the sandbox directory\n\tif !filepath.IsAbs(filePath) {\n\t\tfilePath = filepath.Join(sandboxDir, filePath)\n\t}\n\tcleaned := filepath.Clean(filePath)\n\n\t// Ensure the resolved path is within the sandbox directory\n\tcleanedSandbox := filepath.Clean(sandboxDir)\n\tif cleaned == cleanedSandbox {\n\t\treturn \"\", fmt.Errorf(\"path %q resolves to the sandbox directory itself %q\", filePath, sandboxDir)\n\t}\n\tif !strings.HasPrefix(cleaned, cleanedSandbox+string(filepath.Separator)) {\n\t\treturn \"\", fmt.Errorf(\"path %q is outside the sandbox directory %q\", filePath, sandboxDir)\n\t}\n\n\treturn cleaned, nil\n}\n\nfunc sshStartCpuProfile(sandboxDir string, fs any, a []string, w sshd.StringWriter) error {\n\tif len(a) == 0 {\n\t\terr := w.WriteLine(\"No path to write profile provided\")\n\t\treturn err\n\t}\n\n\tfilePath, err := sshSanitizeFilePath(sandboxDir, a[0])\n\tif err != nil {\n\t\treturn w.WriteLine(err.Error())\n\t}\n\n\tfile, err := os.Create(filePath)\n\tif err != nil {","sourceCodeStart":533,"sourceCodeEnd":569,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/ssh.go#L533-L569","documentation":"sshSanitizeFilePath ensures the requested profile output path stays within the sandbox directory using a prefix check against cleanedSandbox + the OS separator. Any path that escapes the sandbox (via '..', absolute paths elsewhere, or sibling directories sharing a name prefix) is rejected. It protects the SSH debug profile handlers from arbitrary filesystem writes.","triggerScenarios":"SSH debug commands (cpu/heap/mutex profile) given a file path whose cleaned value does not start with sandboxDir + '/', e.g. '/tmp/other/x.pprof', '/var/lib/nebula-sandbox-evil/x' when sandbox is '/var/lib/nebula-sandbox', or anything containing a leading '..' traversal.","commonSituations":"Typing an absolute path outside the sandbox in a profile command; assuming a similarly-named sibling directory is allowed; running the SSH interface with a default sandbox while passing home-directory paths.","solutions":["Provide an output path that is a child of the configured sandbox directory","Check the resolved path with filepath.Clean and confirm it starts with filepath.Clean(sandboxDir)+Separator before issuing the command","If you need a different location, restart nebula with the sandbox configured to (or above) that directory","Use relative paths only after cd-ing logically under the sandbox root; remember the guard applies to the cleaned absolute result"],"exampleFix":"// before\nprofile heap /tmp/heap.pprof\n// after\nprofile heap /var/lib/nebula-sandbox/heap.pprof","handlingStrategy":"validation","validationCode":"func withinSandbox(p, sandboxDir string) bool {\n\tc := filepath.Clean(p)\n\tif c == filepath.Clean(sandboxDir) {\n\t\treturn false\n\t}\n\treturn strings.HasPrefix(c, filepath.Clean(sandboxDir)+string(filepath.Separator))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pre-validate profile output paths client-side against the configured sandbox before sending the command","Never use absolute paths outside the sandbox for SSH debug commands","Watch for sibling directories with shared name prefixes — the separator-aware prefix check will reject them"],"tags":["path-validation","ssh-debug","security","nebula"],"backgroundTag":"path-outside-sandbox","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}