很多工程师在配置 Shell 环境变量时,都会下意识写出一条看起来无懈可击的代码:export PATH="$PATH:~/.local/bin/"。给变量赋值包裹一层双引号,在几乎所有 Shell 编程规范里都被当作防御空格断裂的标准习惯;但正是这层防护,把原本应该指向家目录的波浪号,变成了一个极度危险的字面量。

安全研究员在测试开源 AI Agent 沙箱工具 nono 时,就遭遇了这样一条反常警报。沙箱启动阶段的宿主路径检测直接报错,指出沙箱对 ~/.local/bin/ 拥有写权限。顺藤摸瓜排查下去,问题既不在沙箱本体,也不在复杂的权限策略,而在开发者潜意识里认为绝不可能出错的 Shell 展开规则上。

PATH 赋值语法的展开分歧 写法一:加双引号(常见写法) export PATH="$PATH:~/.local/bin" 解析为:./~/.local/bin(相对路径) 写法二:使用显式变量 export PATH="$PATH:$HOME/.local/bin" 解析为:/home/user/.local/bin(绝对路径)

双引号如何选择性杀死波浪号

Bash 和 Zsh 的底层执行顺序决定了这一诡异现象的发生。按照 Bash 手册 §3.5.2 的规范,波浪号展开(Tilde Expansion)只发生在未加引号的标记上,系统仅对紧随冒号或首个等号之后的未带引号的波浪号执行转换,这套逻辑专门用于照顾 PATH、MAILPATH 与 CDPATH 的赋值。Zsh 手册 §14.7.4 同样保留了这一原则:任何对波浪号本身的引用,或者用双引号包裹整个表达式的行为,都会彻底禁用波浪号展开。

问题在于,双引号虽然抑制了波浪号转换,却依然允许参数展开(Parameter Expansion)。当终端执行 "$PATH:~/.local/bin/" 时,前置的 $PATH 被正常替换为已有的绝对路径列表,而后半段的波浪号却以普通纯文本的形式被原样存入系统变量。

此时,系统读取到的不是家目录,而是一个纯粹的字符。Unix 类操作系统在遍历 PATH 查找可执行文件时,遇到任何不以斜杠开头的路径项,都会将其视作相对路径处理。原本计划加入环境的绝对家目录,在操作系统眼底变成了以当前工作目录为基准的 ./~/.local/bin/。

保护变量的双引号,恰好把绝对路径压制成了潜伏在当前目录下的相对路径。

有人或许会认为去掉双引号写成 export PATH=$PATH:~/.local/bin 就能解决。在 Bash 和 Zsh 中这确实能触发展开,但这套语法非常脆弱,只要原先的 PATH 中包含哪怕一个空格,整条赋值语句就会当场断裂。依赖无引号语法并不是健壮的工程方案。


相对路径带来的二进制劫持窗口

在 PATH 环境变量里塞入相对路径,是系统安全里历史悠久的禁忌。最臭名昭著的做法是在 PATH 里配置单个点号代表当前目录,而未展开的波浪号本质上就是这一禁忌的隐蔽变种。

只要当前运行环境中的 PATH 混入了基于相对位置的 ./~/.local/bin/,系统的命令查找逻辑就会受到当前所在目录的直接支配。如果某个外部项目、解压包或者不可信代码库在根目录下预先创建了 ./~/.local/bin/ 目录,并在其中放入恶意编译产物,劫持链条就已成型。

相对路径命令劫持流程 1. 诱导进入目录 恶意仓库预置 ./~ 目录 埋入同名伪造程序 2. 宿主执行命令 键入缺失或拼错的工具 系统沿 PATH 顺序查找 3. 权限转移与逃逸 命中相对路径中的恶意代码 以当前用户身份直接执行

当然,这种劫持并不会在所有场景下百分之百发生。系统最终能否落入陷阱,严格受制于查找顺序、Shell 内置命令优先级以及 Bash 命令哈希缓存机制。如果波浪号路径是追加在变量末尾,标准系统路径中的二进制工具依然会被优先命中;但在工具拼写错误、特定依赖工具未全局安装,或者攻击者针对性命名的情况下,这就构成了完全非预期的任意代码执行漏洞。

  • 风险.当开发者 cd 进入一个包含恶意 ./~ 结构的不可信仓库时,任何未能命中断点的前置指令,都会以当前用户的宿主权限直接执行攻击者留下的代码。

为什么 AI 沙箱对这个字符亮起红灯

这类原本只存在于安全攻防演练中的边角料语法,最近之所以被再次翻出来,完全是因为现代 AI Agent 工具正在把执行权限推向极端复杂的本地环境。

触发这起排查的开源项目是 AI Agent 本地沙箱工具 nolabs-ai/nono。在其源码 crates/nono/src/broker_path.rs 中,实现了一个名为 writable_path_dirs 的自检函数。这个函数的核心任务,就是在沙箱环境启动时对宿主的 PATH 目录进行静态诊断,检测沙箱内部进程是否对宿主 PATH 拥有写权限。

逻辑非常清晰:如果一个沙箱内部的代码能够修改宿主系统的 PATH 目录,沙箱内的 AI Agent 就可以通过植入、覆盖同名二进制文件实现沙箱逃逸。

然而,当 nono 扫描到宿主传递过来的 ~/.local/bin/ 这一项时,系统底层的相对路径机制瞬间让静态检测陷入尴尬。沙箱在判断路径可写性时,必须将其还原为宿主系统上的具体路径。一个带有波浪号字面量的相对路径,直接干扰了运行时边界校验。宿主机器原本粗糙的 Shell 环境卫生状况,直接演变成了现代沙箱防御机制的一声警报。

  • 建议.检查当前终端配置,执行 echo "$PATH" | tr ':' '\n' | grep '^~';如果输出包含波浪号开头的行,立即在配置文件中将其换为 $HOME。

彻底修复这个问题没有任何认知门槛,只需一行改动:将配置文件中的波浪号全部换成显式环境变量,写成 export PATH="$PATH:$HOME/.local/bin/"。变量在双引号内能够稳定展开为绝对路径,既防范了空格切割,又杜绝了解析歧义。自动化工具越聪明,对底层执行环境的洁净度要求反而越苛刻;那些在终端里潜伏了数年的语法坏习惯,终究会在沙箱与安全检测的显微镜下无所遁形。