{"record":{"id":"878535e494d7e10d","repo":"hashicorp/nomad","slug":"copying-cannot-traverse-symlinks","errorCode":null,"errorMessage":"copying cannot traverse symlinks","messagePattern":"copying cannot traverse symlinks","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"helper/escapingfs/copydir.go","lineNumber":34,"sourceCode":"// but with th e important difference that we preserve file modes.\nfunc CopyDir(src, dst string) error {\n\tsrcFs := os.DirFS(src)\n\n\treturn fs.WalkDir(srcFs, \".\", func(oldPath string, d fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnewPath := filepath.Join(dst, oldPath)\n\t\tif d.IsDir() {\n\t\t\tinfo, err := d.Info()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"could not stat directory: %v\", err)\n\t\t\t}\n\t\t\treturn os.MkdirAll(newPath, info.Mode())\n\t\t}\n\t\tif !d.Type().IsRegular() {\n\t\t\treturn fmt.Errorf(\"copying cannot traverse symlinks\")\n\t\t}\n\n\t\tr, err := srcFs.Open(oldPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not open existing file: %v\", err)\n\t\t}\n\t\tdefer r.Close()\n\t\tinfo, err := r.Stat()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not stat file: %v\", err)\n\t\t}\n\n\t\tw, err := os.OpenFile(newPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, info.Mode())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, err := io.Copy(w, r); err != nil {","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/helper/escapingfs/copydir.go#L16-L52","documentation":"CopyDir walks a source directory tree and copies it to a new location, but it deliberately refuses to follow anything that is not a regular file (symlinks, devices, sockets, etc.). When the fs.WalkDir callback encounters such an entry, it returns this error to abort the copy, preventing symlink-based path escape or copying of non-file entries from the escapingfs package.","triggerScenarios":"Calling escapingfs.CopyDir on a source directory that contains a symlink (or any non-regular entry: device, socket, named pipe) anywhere in its tree.","commonSituations":"Copying job/task directories that contain symlinks created by the OS or user (e.g. Linux client data dirs with symlinks to shared libraries or chroot links); copy operations on directories managed by tools that link shared assets; environments where a symlink was accidentally left in a data directory.","solutions":["Find the offending symlink or non-regular entry in the source directory (e.g. `find <srcdir> ! -type f -not -type d`) and remove it or replace it with a real file","Copy the dereferenced contents instead: use `cp -rL` semantics or resolve the symlink target and copy it as a regular file","If symlinks are intentional, do not use escapingfs.CopyDir; use a copy helper that follows or copies symlinks explicitly (e.g. filepath.WalkDir with custom symlink handling or symlinks/copy via os.Readlink + os.Symlink)","If the symlink is generated by another component, fix that component to materialize regular files instead of links"],"exampleFix":"// before\n$ find ./task-dir -type l\n./task-dir/lib -> /usr/lib/libfoo.so\n// after\n$ rm ./task-dir/lib\n$ cp /usr/lib/libfoo.so ./task-dir/lib","handlingStrategy":"validation","validationCode":"func assertNoSymlinks(src string) error {\n    return filepath.WalkDir(src, func(p string, d fs.DirEntry, err error) error {\n        if err != nil { return err }\n        if !d.IsDir() && !d.Type().IsRegular() {\n            return fmt.Errorf(\"non-regular entry %s (type %s) in %s\", p, d.Type(), src)\n        }\n        return nil\n    })\n}\n// call assertNoSymlinks(src) before CopyDir(src, dst)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Scan source directories with `find <dir> ! -type f ! -type d` before copying","Disable or review tooling that creates symlinks inside managed directories","Handle the error by checking os.IsNotExist/Lstat on the reported path to identify the offending entry"],"tags":["filesystem","symlink","copy"],"backgroundTag":"symlink-traversal-not-supported","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"}