{"record":{"id":"b7ca5ec10917e9ae","repo":"hashicorp/nomad","slug":"file-path-q-escapes-capture-directory-q","errorCode":null,"errorMessage":"file path %q escapes capture directory %q","messagePattern":"file path %q escapes capture directory %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"command/operator_debug.go","lineNumber":1411,"sourceCode":"func (c *OperatorDebugCommand) writeBytes(dir, file string, data []byte) error {\n\t// Replace invalid characters in filename\n\tfilename := helper.CleanFilename(file, \"_\")\n\n\trelativePath := filepath.Join(dir, filename)\n\tc.manifest = append(c.manifest, relativePath)\n\tdirPath := filepath.Join(c.collectDir, dir)\n\tfilePath := filepath.Join(dirPath, filename)\n\n\t// Ensure parent directories exist\n\terr := escapingfs.EnsurePath(dirPath, true)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create parent directories of %q: %w\", dirPath, err)\n\t}\n\n\t// Ensure filename doesn't escape the sandbox of the capture directory\n\tescapes := escapingfs.PathEscapesSandbox(c.collectDir, filePath)\n\tif escapes {\n\t\treturn fmt.Errorf(\"file path %q escapes capture directory %q\", filePath, c.collectDir)\n\t}\n\n\t// Create the file\n\tfh, err := os.Create(filePath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create file %q, err: %w\", filePath, err)\n\t}\n\tdefer fh.Close()\n\n\t_, err = fh.Write(data)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to write data to file %q, err: %w\", filePath, err)\n\t}\n\treturn nil\n}\n\n// newFilePath returns a validated filepath rooted in the provided directory and\n// path. It has been checked that it falls inside the sandbox and has been added","sourceCodeStart":1393,"sourceCodeEnd":1429,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/command/operator_debug.go#L1393-L1429","documentation":"A sandbox/path-traversal guard in `writeBytes`: after joining the capture directory with the caller-supplied subdirectory and filename, Nomad checks `escapingfs.PathEscapesSandbox` and refuses to write if the resolved path would land outside the capture directory. This protects the debug archive from malicious or buggy inputs (e.g. filenames containing `..`) escaping to arbitrary filesystem locations.","triggerScenarios":"Any writeBytes call where the resulting filePath resolves outside c.collectDir — typically a filename or dir argument containing `../` segments, symlinked paths, or an absolute path supplied as filename.","commonSituations":"Automated tooling generating capture filenames from untrusted input (node names, job names) that include path separators or `..`; a symlink inside collectDir pointing elsewhere.","solutions":["Sanitize the filename/subdirectory before passing it: strip path separators and `..` sequences.","Use filepath.Base on any externally-derived name so only the final component is used.","Inspect the wrapped path in the error to see which component escaped and fix the caller generating it.","If a legit symlink causes a false positive, remove the symlink and write within the capture dir directly."],"exampleFix":"// before\nname := node.Name // could be \"../../etc/evil\"\nc.writeBytes(dir, name, resp, err)\n// after\nname := filepath.Base(strings.Map(func(r rune) rune {\n    if r == '/' || r == os.PathSeparator { return '_' }\n    return r\n}, node.Name))\nc.writeBytes(dir, name, resp, err)","handlingStrategy":"validation","validationCode":"func safeName(name string) (string, error) {\n    base := filepath.Base(name)\n    if base == \".\" || base == \"..\" || base == \"/\" || strings.Contains(base, \"..\") {\n        return \"\", fmt.Errorf(\"unsafe filename %q\", name)\n    }\n    return base, nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never build capture filenames from raw untrusted identifiers without filepath.Base/sanitization.","Strip `..` and path separators from externally derived names.","Audit the source of any filename that triggers this error — it may indicate malicious input.","Keep the capture directory free of symlinks."],"tags":["security","path-traversal","filesystem","debug"],"backgroundTag":"path-traversal-sandbox-escape","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}