hashicorp/nomad · critical

archive contains symlink that escapes alloc dir

Error message

archive contains symlink that escapes alloc dir

What it means

Security guard against path traversal during alloc dir migration: if a symlink entry in the tar archive (its name or its target) would resolve outside the allocation directory, Nomad aborts extraction with this error. It protects against malicious or corrupted archives escaping the sandbox.

Source

Thrown at client/allocwatcher/alloc_watcher.go:637

			// Can't change owner if not root or on Windows.
			if euid == 0 {
				if err := os.Chown(name, hdr.Uid, hdr.Gid); err != nil {
					return fmt.Errorf("error chowning directory %w", err)
				}
			}
			continue
		}
		// If the header is for a symlink we create the symlink
		if hdr.Typeflag == tar.TypeSymlink {
			if err = os.Symlink(hdr.Linkname, filepath.Join(dest, hdr.Name)); err != nil {
				return fmt.Errorf("error creating symlink: %w", err)
			}

			for _, path := range []string{hdr.Name, hdr.Linkname} {
				if escapes, err := escapingfs.PathEscapesAllocDir(dest, "", path); err != nil {
					return fmt.Errorf("error evaluating symlink: %w", err)
				} else if escapes {
					return fmt.Errorf("archive contains symlink that escapes alloc dir")
				}
			}

			continue
		}
		// If the header is a file, we write to a file
		if hdr.Typeflag == tar.TypeReg {
			fPath := filepath.Join(dest, hdr.Name)
			if _, err := os.Lstat(fPath); err == nil {
				if err := os.Remove(fPath); err != nil {
					return fmt.Errorf("error removing existing file: %w", err)
				}
			}
			f, err := os.Create(fPath)
			if err != nil {
				return fmt.Errorf("error creating file: %w", err)
			}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Find the offending symlink in the previous alloc dir and repoint it inside the alloc dir
  2. Fix the task to not create symlinks escaping its alloc directory
  3. Re-snapshot the alloc dir after removing the escaping link, then retry migration
  4. If migration is not essential, disable it (canonicalize/no migration) and let the new alloc start fresh
Defensive patterns

Strategy: validation

Validate before calling

// Before relying on migration, audit task-created symlinks in the alloc dir:
// walk the dir and fail the check if filepath.EvalSymlinks resolves outside the alloc dir root

Type guard

func symlinkEscapes(dest, link, target string) bool {
    resolved := filepath.Clean(filepath.Join(dest, link))
    return !strings.HasPrefix(resolved, filepath.Clean(dest)+string(os.PathSeparator))
}

Try / catch

if err := watcher.Wait(ctx); err != nil {
    if strings.Contains(err.Error(), "escapes alloc dir") {
        // Security violation: do NOT retry with same data; start fresh alloc
        return startWithoutMigration(ctx, alloc)
    }
    return err
}

Prevention

When it happens

Trigger: escapingfs.PathEscapesAllocDir returns escapes=true for hdr.Name or hdr.Linkname of a TypeSymlink entry — e.g. a link target like ../../outside or an absolute path.

Common situations: A task created symlinks pointing outside the alloc dir (shared host paths, /tmp links); adversarial or corrupted archives during cross-node migration; workloads using bind-mount-like relative links.

Related errors


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