{"record":{"id":"a353e26f97902322","repo":"hashicorp/nomad","slug":"artifact-destination-path-escapes-alloc-directory","errorCode":null,"errorMessage":"artifact destination path escapes alloc directory","messagePattern":"artifact destination path escapes alloc directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/allocrunner/taskrunner/getter/util.go","lineNumber":75,"sourceCode":"\t\tq.Set(k, taskEnv.ReplaceEnv(v))\n\t}\n\tu.RawQuery = q.Encode()\n\n\t// add the prefix back if necessary\n\tsourceURL := u.String()\n\tif gitSSH {\n\t\tsourceURL = fmt.Sprintf(\"%s%s\", githubPrefixSSH, sourceURL)\n\t}\n\n\treturn sourceURL, nil\n}\n\nfunc getDestination(env interfaces.EnvReplacer, artifact *structs.TaskArtifact) (string, error) {\n\tdestination, escapes := env.ClientPath(artifact.RelativeDest, true)\n\tif escapes {\n\t\treturn \"\", &Error{\n\t\t\tURL:         artifact.GetterSource,\n\t\t\tErr:         fmt.Errorf(\"artifact destination path escapes alloc directory\"),\n\t\t\tRecoverable: false,\n\t\t}\n\t}\n\treturn destination, nil\n}\n\nfunc getMode(artifact *structs.TaskArtifact) getter.ClientMode {\n\tswitch artifact.GetterMode {\n\tcase structs.GetterModeFile:\n\t\treturn getter.ClientModeFile\n\tcase structs.GetterModeDir:\n\t\treturn getter.ClientModeDir\n\tdefault:\n\t\treturn getter.ClientModeAny\n\t}\n}\n\nfunc chownDestination(destination, username string) error {","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/allocrunner/taskrunner/getter/util.go#L57-L93","documentation":"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.","triggerScenarios":"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","commonSituations":"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","solutions":["Set RelativeDest to a relative path inside the task directory (e.g. \"local/data.txt\" or \"sub/dir/file\")","Remove any leading '/' or '..' segments from RelativeDest","If you need data elsewhere, download to local/ and copy/symlink within the task via a template or script task","Check interpolated env attributes used in RelativeDest to ensure the expansion stays within alloc dir"],"exampleFix":"// before\nartifact {\n  source = \"https://example.com/bin.tgz\"\n  destination = \"/usr/local/bin\"\n}\n// after\nartifact {\n  source = \"https://example.com/bin.tgz\"\n  destination = \"local/bin\"\n}","handlingStrategy":"validation","validationCode":"// ensure destination stays inside the task dir\nif filepath.IsAbs(dest) || strings.HasPrefix(filepath.Clean(dest), \"..\") {\n    return fmt.Errorf(\"destination %q must be relative to the task dir\", dest)\n}","typeGuard":null,"tryCatchPattern":"// non-recoverable: surface to user without retry\nvar gerr *getter.Error\nif errors.As(err, &gerr) && !gerr.Recoverable {\n    failTask(gerr) // no retry\n}","preventionTips":["Always use paths relative to the task directory for destination","Never use leading '/' or '..' in RelativeDest","Interpolate carefully — verify env expansions resolve within alloc dir","Download to local/ and relocate via later task steps"],"tags":["artifacts","path-traversal","sandbox"],"backgroundTag":"path-escape-sandbox","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"}