{"record":{"id":"c91a2997a04356ee","repo":"hashicorp/nomad","slug":"error-evaluating-symlink-w","errorCode":null,"errorMessage":"error evaluating symlink: %w","messagePattern":"error evaluating symlink: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/allocwatcher/alloc_watcher.go","lineNumber":635,"sourceCode":"\t\t\tos.MkdirAll(name, os.FileMode(hdr.Mode))\n\n\t\t\t// Can't change owner if not root or on Windows.\n\t\t\tif euid == 0 {\n\t\t\t\tif err := os.Chown(name, hdr.Uid, hdr.Gid); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error chowning directory %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\t// If the header is for a symlink we create the symlink\n\t\tif hdr.Typeflag == tar.TypeSymlink {\n\t\t\tif err = os.Symlink(hdr.Linkname, filepath.Join(dest, hdr.Name)); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating symlink: %w\", err)\n\t\t\t}\n\n\t\t\tfor _, path := range []string{hdr.Name, hdr.Linkname} {\n\t\t\t\tif escapes, err := escapingfs.PathEscapesAllocDir(dest, \"\", path); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error evaluating symlink: %w\", err)\n\t\t\t\t} else if escapes {\n\t\t\t\t\treturn fmt.Errorf(\"archive contains symlink that escapes alloc dir\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\t\t// If the header is a file, we write to a file\n\t\tif hdr.Typeflag == tar.TypeReg {\n\t\t\tfPath := filepath.Join(dest, hdr.Name)\n\t\t\tif _, err := os.Lstat(fPath); err == nil {\n\t\t\t\tif err := os.Remove(fPath); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error removing existing file: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tf, err := os.Create(fPath)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating file: %w\", err)","sourceCodeStart":617,"sourceCodeEnd":653,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/allocwatcher/alloc_watcher.go#L617-L653","documentation":"After creating a symlink from the archive, Nomad validates both the entry name and its link target with escapingfs.PathEscapesAllocDir to prevent path traversal. This error is returned when that security check itself fails (internal error evaluating paths), not when the symlink actually escapes.","triggerScenarios":"escapingfs.PathEscapesAllocDir(dest, \"\", path) returns a non-nil error for either hdr.Name or hdr.Linkname during tar extraction of a TypeSymlink entry.","commonSituations":"Malformed paths in a hand-crafted or corrupted archive (e.g. NUL bytes, invalid separators) that break path evaluation; filesystem errors during evaluation.","solutions":["Inspect the archive for malformed/invalid path entries (invalid characters, absolute paths)","Re-snapshot or re-migrate the alloc dir to regenerate a clean archive","Check escapingfs.PathEscapesAllocDir implementation and host filesystem errors in logs","Reject the migration and redeploy the job instead of retrying with a corrupt archive"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Validate archive paths are well-formed before extraction:\n// reject headers whose Name/Linkname contain NUL bytes, absolute paths, or invalid separators","typeGuard":"func malformedArchivePath(p string) bool {\n    return p == \"\" || strings.ContainsRune(p, 0) || filepath.IsAbs(p)\n}","tryCatchPattern":"if err := watcher.Wait(ctx); err != nil {\n    if strings.Contains(err.Error(), \"error evaluating symlink\") {\n        log.Error(\"archive evaluation failed; treat as corrupt\", \"err\", err)\n    }\n    return err\n}","preventionTips":["Only migrate alloc dirs snapshotted by a trusted Nomad client","Re-snapshot on evaluation failures instead of retrying the same archive","Keep clients on the same Nomad version so escapingfs behavior matches"],"tags":["nomad","security","symlink","path-evaluation"],"backgroundTag":"path-escape-check-failed","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"}