filepath.Join是唯一安全的路径拼接方式,它自动适配系统分隔符、忽略空字符串和"."、遇绝对路径即截断前序段,但不折叠"..",需配合Clean和Abs才能闭环校验。

filepath.Join 是唯一安全的路径拼接方式
硬编码 / 或 拼路径,在 Windows 上大概率触发 no such file or directory;用 + 或 fmt.Sprintf 也一样。Go 不会帮你修复,它照单全收然后静默失败。
真正安全的做法只有一种:filepath.Join("config", "env.yaml")。它自动选 os.PathSeparator,忽略空字符串和 ".",不解析 "..",也不访问文件系统——纯字符串逻辑,快且无副作用。
-
filepath.Join("a", "..", "b")返回a/../b,不是b;需要折叠请后续调用filepath.Clean - 传入任意参数为绝对路径(如
"C:\tmp"或"/etc/passwd"),前面所有参数会被丢弃——这是设计行为,不是 bug - 别用
path.Join替代:它是为 URL 设计的,永远只认/,在 Windows 上处理"C:\foo"会崩成.
用户输入路径必须先 Abs 再 Clean
filepath.Clean 不转绝对路径,filepath.Abs 不归一化。单独用哪个都没用。
比如用户传入 "../etc/passwd":
立即学习“go语言免费学习笔记(深入)”;
-
filepath.Clean("../etc/passwd")→"../etc/passwd"(原样) -
filepath.Abs("../etc/passwd")→"/home/user/../etc/passwd"(仍是相对语义) -
filepath.Clean(filepath.Abs("../etc/passwd"))→"/etc/passwd"(这才是你想要的)
敏感操作前务必写成:cleaned, _ := filepath.Clean(filepath.Abs(userInput)),再校验是否落在白名单内,例如:strings.HasPrefix(cleaned, "/opt/myapp"+string(filepath.Separator))。
filepath.Dir 和 filepath.Base 的边界行为容易误判
这两个函数只是按分隔符切字符串,不判断是否存在、不区分目录/文件、也不做任何归一化。
-
filepath.Base("log/")返回"",不是"log";后续加扩展名会出错:filepath.Base("log/") + ".bak"→".bak" -
filepath.Dir("singlefile")返回".",不是空字符串;接着filepath.Join(dir, "backup")就变成./backup,可能越界 -
filepath.Dir("/")返回"/",而filepath.Dir("/home")返回"/",但filepath.Dir("/home/")返回"/home"
正确做法:先 cleaned := filepath.Clean(input),再用 strings.HasSuffix(cleaned, string(filepath.Separator)) 判断是否为目录结尾。
跨平台输入(如 URL、JSON、命令行)要先 FromSlash
来自 HTTP、配置文件或用户输入的路径,很可能混用 / 和 ,甚至含 // 或 BOM 字符。直接 filepath.Clean 可能失效——Windows 下它只认 ,对 / 视而不见。
-
filepath.Clean("a//b/c")在 Linux 下返回"a/b/c",但在 Windows 上若输入含/,可能原样保留 - 安全写法:先
normalized := filepath.FromSlash(input)(把所有/转成本地分隔符),再filepath.Clean(normalized) - 特别注意 UNC 路径(如
"\\server\share")和驱动器根路径("C:")——别手动strings.ReplaceAll,会破坏语义
最后提醒一句:os.Getwd() 返回的是启动目录,不是程序所在位置;要用 os.Executable() 配合 filepath.Dir 才能拿到二进制真实路径。这点在容器或 CI 环境里尤其容易被忽略。


















