{"record":{"id":"4e955f30d9896264","repo":"hashicorp/nomad","slug":"archive-contains-object-that-escapes-alloc-dir","errorCode":null,"errorMessage":"archive contains object that escapes alloc dir","messagePattern":"archive contains object that escapes alloc dir","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/allocwatcher/alloc_watcher.go","lineNumber":599,"sourceCode":"\tbuf := make([]byte, 1024)\n\tfor !canceled() {\n\t\t// Get the next header\n\t\thdr, err := tr.Next()\n\n\t\t// Snapshot has ended\n\t\tif err == io.EOF {\n\t\t\treturn nil\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error streaming previous alloc %q for new alloc %q: %w\",\n\t\t\t\tp.prevAllocID, p.allocID, err)\n\t\t}\n\n\t\tif escapes, err := escapingfs.PathEscapesAllocDir(dest, \"\", hdr.Name); err != nil {\n\t\t\treturn fmt.Errorf(\"error evaluating object: %w\", err)\n\t\t} else if escapes {\n\t\t\treturn fmt.Errorf(\"archive contains object that escapes alloc dir\")\n\t\t}\n\n\t\tif hdr.Name == errorFilename {\n\t\t\t// Error snapshotting on the remote side, try to read\n\t\t\t// the message out of the file and return it.\n\t\t\terrBuf := make([]byte, int(hdr.Size))\n\t\t\tif _, err := tr.Read(errBuf); err != nil && err != io.EOF {\n\t\t\t\treturn fmt.Errorf(\"error streaming previous alloc %q for new alloc %q; failed reading error message: %w\",\n\t\t\t\t\tp.prevAllocID, p.allocID, err)\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"error streaming previous alloc %q for new alloc %q: %s\",\n\t\t\t\tp.prevAllocID, p.allocID, string(errBuf))\n\t\t}\n\n\t\t// If the header is for a directory we create the directory\n\t\tif hdr.Typeflag == tar.TypeDir {\n\t\t\tname := filepath.Join(dest, hdr.Name)\n\t\t\tos.MkdirAll(name, os.FileMode(hdr.Mode))","sourceCodeStart":581,"sourceCodeEnd":617,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/allocwatcher/alloc_watcher.go#L581-L617","documentation":"If PathEscapesAllocDir determines that a tar entry (hdr.Name) would land outside the destination allocation directory, streamAllocDir refuses it with the literal error 'archive contains object that escapes alloc dir'. This is a security guard against path traversal (e.g. ../../) or absolute-path/symlink attacks carried inside the snapshot received from another node.","triggerScenarios":"The snapshot tar from the previous allocation contains an entry whose resolved path escapes dest: names with '..' components, absolute paths, or symlinks (including hardlink/linkname entries) that resolve outside the alloc dir.","commonSituations":"A compromised or malicious node sending a crafted snapshot during migration; tasks that created symlinks pointing outside their alloc dir; a buggy task writing paths with ../; fuzzed/corrupted archive producing path escapes.","solutions":["Inspect the previous alloc dir for symlinks or paths escaping the allocation directory and fix/remove them before rescheduling","Verify the source node's integrity — this error can indicate a malicious snapshot; investigate the node","Ensure tasks do not create symlinks outside their alloc dir (chroot/ isolation settings, task guidance)","Upgrade Nomad if running a version with known escapingfs gaps, then retry the migration"],"exampleFix":"// before (inside task)\nln -s /etc/passwd $NOMAD_ALLOC_DIR/leak\n// after\nln -s $NOMAD_ALLOC_DIR/data/link-target $NOMAD_ALLOC_DIR/link  # keep symlinks inside the alloc dir","handlingStrategy":"type-guard","validationCode":"func entryEscapes(dest, name string) bool {\n\tescapes, _ := escapingfs.PathEscapesAllocDir(dest, \"\", name)\n\treturn escapes\n}","typeGuard":"func isSafeEntry(dest string, hdr *tar.Header) bool {\n\tescapes, err := escapingfs.PathEscapesAllocDir(dest, \"\", hdr.Name)\n\treturn err == nil && !escapes\n}","tryCatchPattern":"if err := migrate(); err != nil {\n\tif strings.Contains(err.Error(), \"escapes alloc dir\") {\n\t\t// security event: alert, quarantine snapshot, audit source node\n\t}\n\treturn err\n}","preventionTips":["Audit task scripts/templates for symlinks or writes targeting paths outside the alloc dir","Never bypass the PathEscapesAllocDir check when handling migration snapshots","Investigate any node producing such archives — it may be compromised","Keep Nomad updated for the latest escapingfs/path-hardening fixes"],"tags":["security","path-traversal","tar-stream","allocation-migration"],"backgroundTag":"path-traversal-blocked","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"}