Safeguard
Developer Tools•

Stopping path traversal in Go with os.Root (Go 1.24)

os.Root opens files inside a directory and blocks escapes through dot dot or symlinks. How to use it, where it fits, and the limits the Go team lists.

Safeguard Security Team
3 min read

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

  1. Find places where filepath.Join(baseDir, name) feeds os.Open or os.Create.
  2. Check whether name can come from a request, an archive entry or a config file.
  3. Replace the join and open with a root and a relative open.
  4. 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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.