hashicorp/nomad · error

artifact destination path escapes alloc directory

Error message

artifact destination path escapes alloc directory

What it means

getDestination resolves the artifact's RelativeDest via env.ClientPath; if the resolved path escapes the allocation directory (escapes == true), Nomad refuses it with this non-recoverable Error. This is a sandbox-escape guard: artifacts must land inside the alloc dir, and Nomad rejects destinations that would write outside it.

Source

Thrown at client/allocrunner/taskrunner/getter/util.go:75

		q.Set(k, taskEnv.ReplaceEnv(v))
	}
	u.RawQuery = q.Encode()

	// add the prefix back if necessary
	sourceURL := u.String()
	if gitSSH {
		sourceURL = fmt.Sprintf("%s%s", githubPrefixSSH, sourceURL)
	}

	return sourceURL, nil
}

func getDestination(env interfaces.EnvReplacer, artifact *structs.TaskArtifact) (string, error) {
	destination, escapes := env.ClientPath(artifact.RelativeDest, true)
	if escapes {
		return "", &Error{
			URL:         artifact.GetterSource,
			Err:         fmt.Errorf("artifact destination path escapes alloc directory"),
			Recoverable: false,
		}
	}
	return destination, nil
}

func getMode(artifact *structs.TaskArtifact) getter.ClientMode {
	switch artifact.GetterMode {
	case structs.GetterModeFile:
		return getter.ClientModeFile
	case structs.GetterModeDir:
		return getter.ClientModeDir
	default:
		return getter.ClientModeAny
	}
}

func chownDestination(destination, username string) error {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set RelativeDest to a relative path inside the task directory (e.g. "local/data.txt" or "sub/dir/file")
  2. Remove any leading '/' or '..' segments from RelativeDest
  3. If you need data elsewhere, download to local/ and copy/symlink within the task via a template or script task
  4. Check interpolated env attributes used in RelativeDest to ensure the expansion stays within alloc dir

Example fix

// before
artifact {
  source = "https://example.com/bin.tgz"
  destination = "/usr/local/bin"
}
// after
artifact {
  source = "https://example.com/bin.tgz"
  destination = "local/bin"
}
Defensive patterns

Strategy: validation

Validate before calling

// ensure destination stays inside the task dir
if filepath.IsAbs(dest) || strings.HasPrefix(filepath.Clean(dest), "..") {
    return fmt.Errorf("destination %q must be relative to the task dir", dest)
}

Try / catch

// non-recoverable: surface to user without retry
var gerr *getter.Error
if errors.As(err, &gerr) && !gerr.Recoverable {
    failTask(gerr) // no retry
}

Prevention

When it happens

Trigger: TaskArtifact.RelativeDest resolves (via env interpolation and path cleaning) outside the task's alloc directory — e.g. values like "../other/file", absolute paths outside alloc dir, or interpolation to an env var containing such a path

Common situations: Using '../' segments or absolute paths like '/etc/foo' in RelativeDest, expecting pre-1.x behavior where looser destinations were tolerated, or interpolating with node/task attributes that produce a path pointing out of the sandbox

Related errors


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