Use os.Root, added in Go 1.24, when you open files inside a directory and must not escape it. It blocks escapes through .. components and symbolic links. It is the safer replacement for string checks followed by a normal open.
The problem with cleaning paths
Path traversal happens when a program opens a file other than the one it intended, often because untrusted input contains .. or follows a symlink. The Go team's article notes that filepath.IsLocal helps with sanitizing a name, but it cannot protect you from an attacker who controls part of the local filesystem, such as a symlink inside an unpacked archive.
Resolving symlinks first with filepath.EvalSymlinks and then opening the result does not fix this. The Go article says that two step process is vulnerable to time of check to time of use races: the filesystem can change between the check and the open.
How os.Root works
You open a directory as a root, then do all file operations relative to it. The operations refuse to leave that directory.
root, err := os.OpenRoot("/srv/uploads")
if err != nil {
return err
}
defer root.Close()
f, err := root.Open(userSuppliedName)
if err != nil {
return err // escapes and bad paths fail here
}
defer f.Close()
Methods include Open, Create, OpenFile, Mkdir, Remove, Stat, Lstat and OpenRoot. For a single file there is a shortcut, os.OpenInRoot(dir, name). A path that stays inside the root is allowed, so a/../b is fine if it resolves within the directory.
When to use it
The Go team's rule of thumb: if you combine a fixed directory with a filename that comes from outside, use os.Root. Typical cases are serving files from a folder, extracting archives and handling uploads.
Do not use it when the user is allowed to choose any location on the filesystem. In that case the filename is trusted by design and a root adds nothing.
Limits to know about
The Go article lists these:
- On Unix, bind mounts are not restricted. The design targets symlinks ordinary users can create, not mounts that need root.
- On Windows, an open root handle prevents the directory from being renamed or deleted until you close it. Reserved device names are blocked.
- Creating symlinks and renaming files are not yet supported.
- Paths with many components or heavy use of
..can be much slower than plain operations. - On some platforms, such as
GOOS=js, symlink validation stays vulnerable to races.
Migration checklist
- Find places where
filepath.Join(baseDir, name)feedsos.Openoros.Create. - Check whether
namecan come from a request, an archive entry or a config file. - Replace the join and open with a root and a relative open.
- Add a test that tries
../, an absolute path and a symlink pointing outside.
Reviewing the third party modules that handle files is the other half of the job. Safeguard's dependency scanning and reachability analysis helps you see which of those modules carry advisories your code reaches. Try the free scan at gold.safeguard.sh/scan.
Quick check
Is filepath.Clean enough to stop traversal?
Not against symlinks or a changing filesystem. The Go team recommends os.Root when escapes through links matter.
Which Go version do I need?
Go 1.24 or later, since os.Root was introduced there.
Sources
Last reviewed: 19 October 2026.