hashicorp/nomad · error

path escapes the alloc directory

Error message

path escapes the alloc directory

What it means

sanitizePath() enforces containment: escapingfs.ChildEscapesParentDir verifies the requested path stays within the resolved alloc directory. A path that escapes (e.g. via "..") returns the fixed error "path escapes the alloc directory" — a security guard against arbitrary file access.

Source

Thrown at client/allocdir/alloc_dir.go:427

// sanitizePath checks that the path does not escape the alloc directory,
// does not read into the secrets or private directories and returns an absolute
// path of the provided path.
func (a *AllocDir) sanitizePath(path string) (string, error) {
	// In some non linux systmes, directories like /var and /tmp resolve to
	// /private/var and /private/tmp.
	resolvedAllocDir, err := filepath.EvalSymlinks(a.AllocDir)
	if err != nil {
		return "", fmt.Errorf("failed to resolve alloc directory: %w", err)
	}

	requestedPath, err := filepath.Abs(filepath.Join(resolvedAllocDir, path))
	if err != nil {
		return "", fmt.Errorf("failed to resolve requested path: %w", err)
	}

	if err := escapingfs.ChildEscapesParentDir(resolvedAllocDir, requestedPath); err != nil {
		return "", fmt.Errorf("path escapes the alloc directory")
	}

	a.mu.RLock()
	defer a.mu.RUnlock()

	// Check it does not access the secrets or private directories
	for _, taskDir := range a.TaskDirs {
		rps := strings.ReplaceAll(requestedPath, "/Secrets", "/secrets")
		if err := escapingfs.ChildEscapesParentDir(taskDir.SecretsDir, rps); err == nil {
			return "", fmt.Errorf("Reading secret file prohibited: %s", path)
		}

		rpp := strings.ReplaceAll(requestedPath, "/Private", "/private")
		if err := escapingfs.ChildEscapesParentDir(taskDir.PrivateDir, rpp); err == nil {
			return "", fmt.Errorf("Reading secret file prohibited: %s", path)
		}
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Use paths relative to the alloc dir and never start with / or contain .. that exits it
  2. Use task-local dirs (NOMAD_ALLOC_DIR env, task dir) for file access
  3. Remove or fix symlinks inside the alloc dir that point outside
  4. Validate user-supplied paths in tooling built on the Nomad filesystem API

Example fix

// before
allocDir.ReadAt("../../../../etc/passwd", 0)
// after
allocDir.ReadAt("task/mytask/out.txt", 0)
Defensive patterns

Strategy: validation

Validate before calling

func safeRel(base, p string) error {
    abs, err := filepath.Abs(filepath.Join(base, p))
    if err != nil { return err }
    rel, err := filepath.Rel(base, abs)
    if err != nil { return err }
    if rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
        return fmt.Errorf("path escapes base")
    }
    return nil
}

Try / catch

f, err := allocDir.ReadAt(relPath, 0)
if err != nil && strings.Contains(err.Error(), "path escapes the alloc directory") {
    return ErrPathTraversalRejected // do not retry; reject the request
}

Prevention

When it happens

Trigger: Calling List/Stat/ReadAt/BlockUntilExists/ChangeEvents with a relative path containing enough ".." components to resolve outside the alloc dir, or a path that follows a symlink out of it.

Common situations: Misbehaving or malicious task/artifact config referencing paths like ../../etc/passwd; templates or filesystem APIs given host paths instead of alloc-relative paths.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/f37d5930ed148b56. Report an issue: GitHub.