Context Mode 的安全防线通过多层机制确保工具调用的权限控制。它使用三级优先级的策略文件(项目本地 > 项目共享 > 全局)来加载 deny/allow 规则,并利用经过词边界优化的 glob 转正则引擎进行匹配。为了防止通过链式命令绕过限制,系统会拆分 &&、||、;、管道符等操作符并独立检查每个部分。针对文件访问,它会规范化路径并检查绝对路径与符号链接,以防御路径遍历攻击。此外,它还能扫描多种编程语言代码中的 Shell 转义调用。
Context Mode 的安全防线:命令拒绝策略、文件路径评估与 Shell 注入检测
Context Mode 作为服务于多个 AI 编码平台的 MCP 服务器插件,其安全模块 (security.ts) 是保障系统稳定运行、防止恶意或误操作指令的核心。该模块围绕命令执行和文件访问构建了严密的防线,其设计思路体现在策略解析、匹配逻辑与防御性检测的每一个环节。
1. 命令拒绝策略:从配置到决策
安全模块的核心决策逻辑始于对权限策略的读取与解析。策略来源于多个设置文件,遵循明确的优先级顺序。
策略的加载与优先级
readBashPolicies 函数负责从三个层级的设置文件中读取 allow、deny、ask 策略,顺序至关重要:
.claude/settings.local.json(项目本地,最高优先级).claude/settings.json(项目共享)- 适配器特定的全局路径(如
~/.cursor/settings.json、~/.claude/settings.json)
这种分层设计允许团队在项目层面制定严格的规则,同时保留全局默认配置。函数会合并所有可用路径的策略,确保跨适配器的策略一致性(例如,即便检测到 cursor 平台,也会读取 ~/.claude/settings.json 的规则,实现纵深防御)。
Glob 模式的语义化解析
策略文件中使用 Bash(sudo *)、Read(.env) 等模式。parseBashPattern 和 parseToolPattern 函数负责解析这些模式,提取出工具名和 glob 匹配字符串。关键的转换在于 globToRegex 函数,它将 glob 模式转换为正则表达式,但特别处理了词边界,这是防止模式误匹配的核心。
例如,对于 glob 模式 "sudo *":
globToRegex会将其转换为^sudo .*$。- 测试用例证明,它匹配
"sudo apt install",但不匹配"sudoedit"。这是因为sudo后面的空格被视作单词分隔符,而sudoedit被视为一个完整的单词,从而避免了误伤。
对于使用冒号分隔的模式如 "tree:*",正则表达式 ^tree(\s.*)?$ 确保了 "tree" 本身或 "tree -a" 能匹配,但 "treemap" 不会被匹配。这种精确的语义化转换是安全策略生效的基础。
链式命令拆分:防止“绕道而行”
最危险的绕过方式之一是在恶意命令前添加无害指令,例如 echo ok && sudo rm -rf /。如果安全检查只看整个字符串,echo ok 部分可能匹配 allow 规则,从而放行整个链式命令。
splitChainedCommands 函数彻底解决了这个问题。它通过状态机逐个字符解析命令字符串,正确处理了单引号、双引号和反引号内的内容,仅在非引用状态下识别 &&、||、;、| 等链式操作符,将命令拆分为独立的部分。
// 源码逻辑示意
splitChainedCommands("echo hello && sudo rm -rf /")
// 返回: ["echo hello", "sudo rm -rf /"]
测试用例显示,拆分后,evaluateCommand 或 evaluateCommandDenyOnly 会对 "sudo rm -rf /" 这个片段单独进行 deny 规则匹配,从而确保危险命令无法逃脱检查。这构成了防御链式命令绕过的关键机制。
2. 文件路径评估:对抗遍历与逃逸
当 AI 代理请求读取或操作文件时(例如通过 Read 或 Grep 工具),evaluateFilePath 函数负责评估文件路径是否被拒绝。这里的防御重点在于路径规范化与真实性检查。
路径规范化
输入的文件路径可能使用反斜杠(Windows)或包含 .. 相对路径。函数会将其统一转换为正斜杠,并计算出相对于项目根目录的绝对路径(lexical resolved form)。
符号链接真实路径检查
更进一步,如果项目根目录已知,函数会尝试使用 fs.realpathSync 获取该绝对路径的真实文件系统位置(canonical form)。这能有效防御两种攻击:
- 路径遍历:例如
../../.ssh/id_rsa在规范化后会指向项目外的敏感目录,其绝对路径会被 deny 规则(如~/.ssh/**)匹配。 - 符号链接逃逸:一个位于项目内、看似无害的路径
safe.log,如果它是一个指向~/.ssh/id_rsa的符号链接,其真实路径也会被 deny 规则捕获。
测试用例中专门验证了 evaluateFilePath 在提供 projectRoot 时,能够拦截通过 ../../ 进行的路径遍历攻击。
Glob 匹配的跨平台一致性
对于文件路径的 glob 匹配,使用了独立的 fileGlobToRegex 函数。它与命令 glob 不同,专门处理路径分隔符,并支持 ** 匹配多级目录、* 匹配单层(不包括路径分隔符)等路径 glob 语法。在匹配前,glob 模式本身也会被归一化为正斜线,确保在 Windows 平台上定义的 Read(C:\Users\...\secret.env) 规则能够正确匹配输入路径。
3. Shell 注入检测:跨语言的安全扫描
除了直接执行命令,AI 代理生成的代码可能通过编程语言内置函数调用系统命令(例如 Python 的 os.system、JavaScript 的 execSync),从而绕过对直接 Bash 命令的检查。extractShellCommands 函数为此提供了扫描能力。
该函数维护了一个针对多种编程语言的正则表达式模式库(SHELL_ESCAPE_PATTERNS),覆盖了 Python、JavaScript、TypeScript、Ruby、Go、PHP、Rust 等常见语言中调用 Shell 的典型函数。例如,对于 Python:
os.system(“sudo rm -rf /”)subprocess.run([“rm”, “-rf”, “/”])(列表形式也会被提取并拼接为命令字符串)
函数通过扫描代码字符串,提取出所有嵌入的命令字符串。这些提取出的命令随后可以被送入前述的命令拒绝策略引擎中进行评估,从而实现了对代码生成环节的安全闭环。测试用例验证了它能正确提取各语言中的危险命令,并安全地忽略无害代码。
4. 策略的统一评估入口
最终的权限决策由 evaluateCommand 和 evaluateCommandDenyOnly 函数完成。它们综合了上述所有机制:
- 加载分层策略。
- 拆分链式命令。
- 对每个命令片段,按策略优先级(项目 > 全局)和权限等级(deny > ask > allow)进行匹配。
- 返回“允许”、“拒绝”或“询问”的决策。
evaluateCommandDenyOnly 是一个轻量版本,仅执行拒绝检查,适用于没有用户交互界面的服务端场景。
FAQ
Q: Context Mode 为什么要拆分链式命令?这会不会影响正常使用? A: 拆分是为了防止通过在危险命令前添加安全命令来绕过拒绝策略。拆分操作是在内部评估阶段进行,不会影响最终提交给 Shell 执行的原始命令,因此不影响正常的链式命令使用,但能确保安全策略无遗漏地应用到命令的每一部分。
Q: 如何防止 AI 代理通过读取符号链接来访问敏感文件?
A: evaluateFilePath 函数在评估时,如果提供了项目根目录,会尝试解析路径的真实文件系统位置(realpath)。即使一个符号链接文件的路径本身不匹配拒绝规则,只要其指向的真实文件路径匹配了规则(如 ~/.ssh/id_rsa),该访问请求也会被拒绝。