11// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
2- // Copyright (C) 2017-2024 SUSE LLC. All rights reserved.
2+ // Copyright (C) 2017-2025 SUSE LLC. All rights reserved.
33// Use of this source code is governed by a BSD-style
44// license that can be found in the LICENSE file.
55
@@ -24,6 +24,10 @@ func IsNotExist(err error) bool {
2424 return errors .Is (err , os .ErrNotExist ) || errors .Is (err , syscall .ENOTDIR ) || errors .Is (err , syscall .ENOENT )
2525}
2626
27+ // errUncleanRoot is returned if the user provides SecureJoinVFS with a path
28+ // that is not filepath.Clean'd.
29+ var errUncleanRoot = errors .New ("root path provided to SecureJoin was not filepath.Clean" )
30+
2731// SecureJoinVFS joins the two given path components (similar to [filepath.Join]) except
2832// that the returned path is guaranteed to be scoped inside the provided root
2933// path (when evaluated). Any symbolic links in the path are evaluated with the
@@ -46,7 +50,21 @@ func IsNotExist(err error) bool {
4650// provided via direct input or when evaluating symlinks. Therefore:
4751//
4852// "C:\Temp" + "D:\path\to\file.txt" results in "C:\Temp\path\to\file.txt"
53+ //
54+ // If the provided root is not [filepath.Clean] then an error will be returned,
55+ // as such root paths are bordering on somewhat unsafe and using such paths is
56+ // not best practice. We also strongly suggest that any root path is first
57+ // fully resolved using [filepath.EvalSymlinks] or otherwise constructed to
58+ // avoid containing symlink components. Of course, the root also *must not* be
59+ // attacker-controlled.
4960func SecureJoinVFS (root , unsafePath string , vfs VFS ) (string , error ) {
61+ // The root path needs to be clean, otherwise when we join the subpath we
62+ // will end up with a weird path. We could work around this but users
63+ // should not be giving us unclean paths in the first place.
64+ if filepath .Clean (root ) != root {
65+ return "" , errUncleanRoot
66+ }
67+
5068 // Use the os.* VFS implementation if none was specified.
5169 if vfs == nil {
5270 vfs = osVFS {}
0 commit comments