{"record":{"id":"12c93d6464f3e952","repo":"hashicorp/nomad","slug":"destination-escapes-allocation-directory","errorCode":null,"errorMessage":"destination escapes allocation directory","messagePattern":"destination escapes allocation directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/structs.go","lineNumber":6151,"sourceCode":"\tFile string\n}\n\nfunc (d *DispatchPayloadConfig) Copy() *DispatchPayloadConfig {\n\tif d == nil {\n\t\treturn nil\n\t}\n\tnd := new(DispatchPayloadConfig)\n\t*nd = *d\n\treturn nd\n}\n\nfunc (d *DispatchPayloadConfig) Validate() error {\n\t// Verify the destination doesn't escape\n\tescaped, err := escapingfs.PathEscapesAllocViaRelative(\"task/local/\", d.File)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid destination path: %v\", err)\n\t} else if escaped {\n\t\treturn fmt.Errorf(\"destination escapes allocation directory\")\n\t}\n\n\treturn nil\n}\n\nconst (\n\tTaskLifecycleHookPrestart  = \"prestart\"\n\tTaskLifecycleHookPoststart = \"poststart\"\n\tTaskLifecycleHookPoststop  = \"poststop\"\n)\n\ntype TaskLifecycleConfig struct {\n\tHook    string\n\tSidecar bool\n}\n\nfunc (d *TaskLifecycleConfig) Copy() *TaskLifecycleConfig {\n\tif d == nil {","sourceCodeStart":6133,"sourceCodeEnd":6169,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/structs.go#L6133-L6169","documentation":"DispatchPayloadConfig.Validate() rejects a `template` dispatch payload whose destination `File` resolves outside the allocation directory. It calls escapingfs.PathEscapesAllocViaRelative(\"task/local/\", d.File), and if the path escapes (e.g. via `..` traversal or symlinks), validation fails. This guards the `task/local/` sandbox against path traversal.","triggerScenarios":"Submitting or validating a job whose `template` block uses `data` (dispatch payload) with a `destination` file path such as `../../etc/foo`, an absolute path, or any relative path that escapes `task/local/`.","commonSituations":"Typo of extra `../` segments in a template destination; copy-pasting paths from other tools assuming a different working directory; trying to write outside the alloc dir to share files between tasks; absolute paths (`/etc/...`) used by mistake.","solutions":["Set the template `destination` to a relative path inside the alloc dir, e.g. `task/local/payload.txt` or `local/payload.txt`.","Remove any `../` traversal segments from the destination path.","If you need data in another location, copy it at runtime from task/local/ rather than pointing the destination there."],"exampleFix":"// before\ntemplate {\n  data        = \"{{ payload }}\"\n  destination = \"../../shared/input.txt\"\n}\n// after\ntemplate {\n  data        = \"{{ payload }}\"\n  destination = \"task/local/input.txt\"\n}","handlingStrategy":"validation","validationCode":"func validDispatchDestination(dest string) bool {\n\tif filepath.IsAbs(dest) {\n\t\treturn false\n\t}\n\tescaped, err := escapingfs.PathEscapesAllocViaRelative(\"task/local/\", dest)\n\treturn err == nil && !escaped\n}\n// call before building the job: validDispatchDestination(cfg.File)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always write template destinations as paths relative to the alloc dir rooted at task/local/.","Never use `..` segments or absolute paths in template destinations.","Run `nomad job validate` in CI before submitting dispatch-template jobs."],"tags":["nomad","job-spec","path-traversal","validation"],"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"}