Go

What is os.Root (Go 1.24), and how does it prevent path-traversal attacks compared to filepath.Join plus filepath.Clean?

Question 543HardGo 1.22 to 1.25

os.OpenRoot(dir) returns an *os.Root. All of its operations (Open, Create, OpenFile, Mkdir, Remove, Stat, Lstat, plus ReadFile, WriteFile, MkdirAll, Rename, RemoveAll and others added in Go 1.25) are guaranteed to stay inside that directory. It rejects .. escapes and absolute paths, and it follows symlinks only when their target stays inside the root. On Unix it resolves paths one component at a time with openat relative to the directory handle, which also closes the check-then-use (TOCTOU) race.

// The naive approach - what does this print?
fmt.Println(filepath.Join("/srv/uploads", "../../etc/passwd")) // /etc/passwd

// Clean normalizes but does not confine; a symlink uploads/evil -> /etc
// also defeats any strings.HasPrefix(cleaned, base) check.

root, err := os.OpenRoot("/srv/uploads")
if err != nil {
	return err
}
defer root.Close()

f, err := root.Open(userName) // "../../etc/passwd" -> error: path escapes from parent
if err != nil {
	return err
}
defer f.Close()

// One-shot helper and fs.FS view:
f2, err := os.OpenInRoot("/srv/uploads", userName)
fsys := root.FS() // use with fs.WalkDir, http.FileServerFS, etc.

filepath.Join plus Clean is purely lexical. It knows nothing about symlinks, and a prefix check is racy because a symlink can be swapped in after the check. filepath.IsLocal (Go 1.20) and filepath.Localize (1.23) help validate names, but they are still lexical. Caveats: os.Root does not protect against Linux bind mounts or special files in /proc, and on js/wasm and plan9 it can only do lexical checks. What the interviewer is looking for: lexical checks are not enough against symlinks, and you should use a descriptor-relative API for any path a user controls.

More on More Standard Library Essentials

All 16 More Standard Library Essentials questions