hashicorp/nomad · error
file path %q escapes capture directory %q
Error message
file path %q escapes capture directory %q
What it means
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.
Source
Thrown at command/operator_debug.go:1411
func (c *OperatorDebugCommand) writeBytes(dir, file string, data []byte) error {
// Replace invalid characters in filename
filename := helper.CleanFilename(file, "_")
relativePath := filepath.Join(dir, filename)
c.manifest = append(c.manifest, relativePath)
dirPath := filepath.Join(c.collectDir, dir)
filePath := filepath.Join(dirPath, filename)
// Ensure parent directories exist
err := escapingfs.EnsurePath(dirPath, true)
if err != nil {
return fmt.Errorf("failed to create parent directories of %q: %w", dirPath, err)
}
// Ensure filename doesn't escape the sandbox of the capture directory
escapes := escapingfs.PathEscapesSandbox(c.collectDir, filePath)
if escapes {
return fmt.Errorf("file path %q escapes capture directory %q", filePath, c.collectDir)
}
// Create the file
fh, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("failed to create file %q, err: %w", filePath, err)
}
defer fh.Close()
_, err = fh.Write(data)
if err != nil {
return fmt.Errorf("Failed to write data to file %q, err: %w", filePath, err)
}
return nil
}
// newFilePath returns a validated filepath rooted in the provided directory and
// path. It has been checked that it falls inside the sandbox and has been addedView on GitHub (pinned to 482b49bf1a)
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.
Example fix
// before
name := node.Name // could be "../../etc/evil"
c.writeBytes(dir, name, resp, err)
// after
name := filepath.Base(strings.Map(func(r rune) rune {
if r == '/' || r == os.PathSeparator { return '_' }
return r
}, node.Name))
c.writeBytes(dir, name, resp, err) Defensive patterns
Strategy: validation
Validate before calling
func safeName(name string) (string, error) {
base := filepath.Base(name)
if base == "." || base == ".." || base == "/" || strings.Contains(base, "..") {
return "", fmt.Errorf("unsafe filename %q", name)
}
return base, nil
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Automated tooling generating capture filenames from untrusted input (node names, job names) that include path separators or `..`; a symlink inside collectDir pointing elsewhere.
Related errors
- path escapes the alloc directory
- file path escapes capture directory
- Reading secret file prohibited: %s
- archive contains object that escapes alloc dir
- archive contains symlink that escapes alloc dir
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/b7ca5ec10917e9ae.
Report an issue: GitHub.