Skip to content

文件沙箱与安全控制 ​

本文引用的源码文件

本文引用的文件

目录 ​

  1. 引言
  2. 项目结构与安全边界
  3. 核心安全组件
  4. 架构总览
  5. 路径白名单与规范化机制
  6. 目录遍历防护与符号链接检测
  7. 权限模型与工作目录特殊处理
  8. 命令分级管控与文件沙箱的边界
  9. 完整路径安全检查流程
  10. 依赖关系分析
  11. 性能与资源限制
  12. 故障排查指南
  13. 安全最佳实践
  14. 结论

引言 ​

本文聚焦 Agent 端的文件沙箱与安全控制,目标是说明:

  • 路径输入如何被净化、规范化、硬阻止检查、白名单匹配和符号链接验证。
  • normalizePathFormat、allowedPaths、workdir、MEMORY_DIR、HARD_BLOCKED_PATHS、isHardBlocked 等关键概念如何协作。
  • 文件沙箱与命令分级管控的关系,以及当前实现的能力边界。
  • 扩展端工具定义与 Agent 端执行器之间的职责划分。
  • 面向生产的安全建议、审计日志和资源限制要点。

本项目采用双进程架构:Chrome 扩展端负责用户交互、工具调度与网络访问策略;Agent 端(Node.js)负责本地文件系统、命令执行、长期记忆与 RAG。版本以 2.3.1 为准,Agent 源码修改后必须重启进程,新接口才生效。

项目结构与安全边界 ​

从安全视角看,本仓库中与文件沙箱直接相关的代码主要分布在以下位置:

  • Agent 端安全核心:agent/src/security.js
  • Agent 端配置与默认工作目录:agent/src/config.js
  • Agent 端命令执行器:agent/src/executor.js
  • 扩展端 Agent 工具定义:src/background/tools/agent-tools.js
  • 扩展端工具执行与确认流程:src/background/tool-executor.js
graph TB
Extension["扩展端<br/>tool-executor.js"] --> ToolDef["工具定义<br/>agent-tools.js"]
Extension --> AgentClient["本地 Agent 客户端"]
AgentClient --> AgentServer["Agent 服务端"]
AgentServer --> Security["安全模块<br/>security.js"]
AgentServer --> Config["配置模块<br/>config.js"]
AgentServer --> Executor["命令执行器<br/>executor.js"]
Security --> FS["操作系统文件系统"]
Config --> FS
Executor --> FS

图表来源

章节来源

核心安全组件 ​

Agent 端的安全能力集中在 security.js,它同时承担两类职责:

  1. 文件沙箱:校验文件路径是否属于允许范围,防止越权读取、写入、删除或下载。
  2. 命令分级管控:对 shell 命令进行黑名单、灰名单与强制放行判断。

此外,config.js 提供默认工作目录、允许路径列表、记忆存储目录等基础配置;executor.js 负责命令执行、超时控制、输出缓冲与进程生命周期管理;agent-tools.js 暴露给扩展端的 Agent 工具定义;tool-executor.js 在扩展端组织工具调用、用户确认与审计记录。

章节来源

架构总览 ​

下图展示一次“Agent 文件操作”的典型调用链:扩展端工具定义 → 扩展端工具执行 → 本地 Agent 客户端 → Agent 服务端 → 安全模块 → 文件系统。

sequenceDiagram
participant LLM as "大模型"
participant Ext as "扩展端工具执行器"
participant Agent as "本地 Agent 客户端"
participant Server as "Agent 服务端"
participant Sec as "安全模块"
participant FS as "文件系统"
LLM->>Ext : 调用 agent_file / agent_exec
Ext->>Agent : 发起 HTTP/WebSocket 请求
Agent->>Server : 转发到对应 API
Server->>Sec : 传入 path/command 做安全检查
Sec-->>Server : 返回 allowed/denied/confirm
Server->>FS : 通过后再执行读/写/删/查
FS-->>Server : 返回结果或错误
Server-->>Ext : 返回最终结果
Ext-->>LLM : 格式化后的结果

图表来源

路径白名单与规范化机制 ​

normalizePathFormat:跨平台路径规范化 ​

normalizePathFormat 是路径进入沙箱前的第一道规范化步骤,主要解决 Windows、MSYS/Git Bash 与跨平台分隔符差异:

  • 修正异常盘符前导斜杠,例如 /C:/ 归一为 C:/。
  • 将 MSYS 风格路径 /d/Users/... 转换为 Windows 盘符形式 D:/Users/...。
  • 统一反斜杠为正斜杠,交给 Node.js path.resolve/normalize 做最终归一。
  • 将 Windows 盘符统一大写,避免小写盘符与大写白名单前缀比较失败。

该函数支持单元测试时通过 platformOverride 覆盖平台行为,保证测试可预测。

章节来源

allowedPaths:配置解析与自动追加 ​

allowedPaths 来自 config.js 中的 loadConfig():

  • 默认值为 [WORKSPACE_DIR],即 .ai-helper-agent/workspace。
  • 如果配置文件不存在,会创建默认配置并缓存。
  • 加载配置后,会自动把 SKILLS_DIR 和 WORKSPACE_DIR 加入 allowedPaths,确保技能目录与工作目录始终可访问。
  • 当 allowedPaths 为空时,安全模块会回退到使用 workdir。

这意味着:

  • 用户可以在 config.json 中自定义允许路径。
  • 但系统不会移除内置的工作目录和技能目录。
  • 若用户清空 allowedPaths,实际仍会保留工作目录作为兜底。

章节来源

workdir:工作目录的特殊放行逻辑 ​

workdir 是用户当前选定的操作目录,由 config.workdir 决定,默认等于 WORKSPACE_DIR。安全模块对 workdir 有特殊处理:

  • 即使 workdir 不在 allowedPaths 中,只要目标路径落在 workdir 下,就优先放行。
  • 放行前会对 resolved 路径调用 realpath,检查符号链接是否指向硬阻止目录。
  • 如果路径不存在(ENOENT),且父目录可解析,则尝试构造真实路径继续校验;无法解析时拒绝。
  • 如果符号链接绕过到硬阻止目录,直接拒绝。

这体现了“用户明确选择的工作目录应始终可用,但不能绕过核心敏感文件保护”的设计。

章节来源

目录遍历防护与符号链接检测 ​

HARD_BLOCKED_PATHS:硬阻止清单 ​

HARD_BLOCKED_PATHS 列出任何情况下都不可访问的核心敏感文件和目录,包括:

  • Agent 配置文件 config.json
  • 配对记录 pairings.json
  • MCP 服务器配置 mcp_servers.json
  • 日志目录 logs
  • 回收站目录 .trash
  • 内置技能状态 builtin_skills_state.json
  • 停用技能状态 disabled_skills.json

这些路径不会被白名单绕过,也不会因 workdir 特殊处理而放行。

章节来源

isHardBlocked:大小写不敏感比较 ​

Windows 文件系统不区分大小写,因此 isHardBlocked 在比较时会:

  • 根据平台决定是否转为小写。
  • 对目录型硬阻止项,检查是否以该目录开头或完全相等。
  • 对文件型硬阻止项,要求精确匹配。

这样可防止通过 .AI-HELPER-AGENT/CONFIG.JSON 等大小写变换绕过硬阻止。

章节来源

realpath:符号链接检测与绕过防护 ​

checkPath 在多个阶段使用 realpath:

  1. 对 workdir 内路径先做符号链接检测,再决定是否放行。
  2. 对普通白名单路径,先做前缀匹配,再解析真实路径。
  3. 对真实路径再次做硬阻止检查。
  4. 对真实路径再次做白名单前缀检查,防止符号链接跳转到其他允许目录。

当路径不存在时,实现会向上回溯父目录,尝试解析已存在的父路径,再拼接剩余部分;如果所有父目录都无法解析,则拒绝。

flowchart TD
Start(["开始检查路径"]) --> Normalize["规范化路径<br/>normalizePathFormat + resolve + normalize"]
Normalize --> HardCheck["硬阻止检查<br/>isHardBlocked(normalized)"]
HardCheck --> |命中| DenyHard["拒绝:命中核心敏感文件"]
HardCheck --> |未命中| MemoryCheck["检查 MEMORY_DIR"]
MemoryCheck --> |命中| AllowMemory["放行:记忆存储目录"]
MemoryCheck --> |未命中| WorkdirCheck["检查工作目录"]
WorkdirCheck --> |命中| RealpathWorkdir["realpath 符号链接检测"]
RealpathWorkdir --> |绕过硬阻止| DenyHard
RealpathWorkdir --> |未绕过| AllowWorkdir["放行:工作目录"]
WorkdirCheck --> |未命中| AllowedPathsCheck["allowedPaths 前缀匹配"]
AllowedPathsCheck --> |未命中| DenyNotInAllowed["拒绝:不在允许目录"]
AllowedPathsCheck --> |命中| RealpathAllowed["realpath 解析真实路径"]
RealpathAllowed --> RealpathHard["对 realPath 做硬阻止检查"]
RealpathHard --> |命中| DenyHard
RealpathHard --> |未命中| RealpathAllowedPrefix["对 realPath 再做白名单前缀检查"]
RealpathAllowedPrefix --> |命中| AllowFinal["放行:最终授权"]
RealpathAllowedPrefix --> |未命中| DenyOutOfRange["拒绝:真实路径超出范围"]

图表来源

章节来源

权限模型与工作目录特殊处理 ​

MEMORY_DIR:记忆存储目录自动放行 ​

MEMORY_DIR 是 Agent 内部管理的长期记忆存储目录,位于 .ai-helper-agent/memory。安全模块将其视为系统级受信任目录:

  • 一旦路径命中 MEMORY_DIR,直接放行。
  • 不需要经过 allowedPaths 配置。
  • 这是合理的,因为记忆数据由 Agent 自身管理,不应被外部白名单策略阻断。

注意:MEMORY_DIR 的放行发生在硬阻止检查之后,因此仍可防止直接访问硬阻止文件。

章节来源

系统目录保护 ​

系统目录保护主要通过两种方式实现:

  1. 硬阻止清单:禁止访问 Agent 配置、配对、日志、回收站、技能状态等核心文件。
  2. 命令层拦截:命令分级管控会阻止破坏性系统命令,如递归删除系统目录、格式化磁盘、覆盖 /etc/passwd、fork bomb 等。

需要明确的是:文件沙箱的 allowedPaths 只约束文件类工具;命令通道本身不受 allowedPaths 约束,因此像 cat /etc/passwd 这类“读取即外泄”的动作,应由更上层的网络/命令策略和用户确认机制控制。

章节来源

工作目录权限模型 ​

工作目录权限模型可以概括为:

  • workdir 是用户当前上下文的操作根目录。
  • 所有相对路径都会基于 workdir 解析。
  • workdir 下的路径优先放行,但仍需通过符号链接检测。
  • 非 workdir 的路径必须命中 allowedPaths。
  • 所有路径最终都要通过 realpath 的真实路径校验。
flowchart TD
Input["输入路径"] --> Relative{"是否相对路径"}
Relative --> |是| ResolveWorkdir["基于 workdir 解析绝对路径"]
Relative --> |否| UseInput["直接使用输入路径"]
ResolveWorkdir --> Normalize["规范化"]
UseInput --> Normalize
Normalize --> Decision["授权决策"]
Decision --> |workdir 内| FastAllow["快速放行 + 符号链接校验"]
Decision --> |allowedPaths 内| PrefixCheck["白名单前缀匹配"]
PrefixCheck --> RealpathVerify["realpath 真实路径验证"]
RealpathVerify --> FinalDecision["最终授权"]

图表来源

章节来源

命令分级管控与文件沙箱的边界 ​

黑名单与灰名单 ​

命令分级管控分为三类:

  • 黑名单:绝对禁止,不可绕过。例如递归删除系统目录、格式化磁盘、覆盖系统关键文件、fork bomb、shebang 直接执行、恶意管道执行等。
  • 灰名单:需要用户确认。例如 sudo、rm -rf、chmod 777、网络外联、脚本解释器执行外部文件等。
  • 强制放行:用户确认后跳过灰名单,但黑名单仍然生效。

子命令拆分与包装器剥壳 ​

命令不是只看整串开头,而是:

  • 按 shell 控制符拆分为子命令段。
  • 剥离 nohup、sudo、env、timeout 等包装器。
  • 提取 -c/-e、/c /k、PowerShell -Command/-EncodedCommand 的内联载荷。
  • 提取 $( … ) 和反引号内的子命令。
  • 对路径参数做归一化,防止 //usr、/.//usr 等写法绕过。

文件沙箱与命令通道的边界 ​

文档明确指出:命令通道不受 allowedPaths 约束。也就是说,文件沙箱不能替代命令安全网关。对于高危命令,应依赖黑名单、灰名单和用户确认流程。

章节来源

完整路径安全检查流程 ​

综合前面的分析,路径安全检查的完整流程如下:

  1. 输入验证:检查是否为字符串,空值直接拒绝。
  2. 展开用户主目录:支持 ~ 和 ~/path。
  3. 路径规范化:调用 normalizePathFormat,然后 resolve + normalize。
  4. 硬阻止检查:优先检查是否命中 HARD_BLOCKED_PATHS。
  5. 记忆目录放行:命中 MEMORY_DIR 直接放行。
  6. 工作目录放行:命中 workdir 放行,但需 realpath 检测符号链接。
  7. 白名单匹配:检查是否在 allowedPaths 前缀范围内。
  8. 符号链接验证:解析真实路径,再次检查硬阻止和白名单。
  9. 最终授权决策:返回 allowed、resolved 或拒绝原因。
flowchart TD
A["输入 pathStr"] --> B["类型与空值校验"]
B --> C["展开 ~ 到 home 目录"]
C --> D["normalizePathFormat 规范化"]
D --> E["resolve + normalize 得到 normalized"]
E --> F{"是否命中 HARD_BLOCKED_PATHS"}
F --> |是| G["拒绝:核心敏感文件"]
F --> |否| H{"是否命中 MEMORY_DIR"}
H --> |是| I["放行:记忆存储目录"]
H --> |否| J{"是否命中 workdir"}
J --> |是| K["realpath 符号链接检测"]
K --> |绕过硬阻止| G
K --> |未绕过| L["放行:工作目录"]
J --> |否| M["allowedPaths 前缀匹配"]
M --> |未命中| N["拒绝:不在允许目录"]
M --> |命中| O["realpath 解析真实路径"]
O --> P{"realPath 是否命中 HARD_BLOCKED_PATHS"}
P --> |是| G
P --> |否| Q{"realPath 是否命中 allowedPaths"}
Q --> |是| R["放行:最终授权"]
Q --> |否| S["拒绝:真实路径超出范围"]

图表来源

章节来源

依赖关系分析 ​

graph LR
Security["security.js"] --> Path["Node path 模块"]
Security --> FsPromises["fs/promises.realpath"]
Security --> Os["os.homedir / os.platform"]
Security --> Config["config.loadConfig"]
Security --> I18n["i18n.translate"]
Config --> FsSync["fs.readFileSync/writeFileSync/mkdirSync"]
Config --> Crypto["crypto-lite.encodeSecret/decodeSecret"]
Executor["executor.js"] --> ChildProcess["child_process.spawn"]
Executor --> Config
Executor --> I18n
Executor --> Os

图表来源

章节来源

性能与资源限制 ​

Agent 端在执行命令时实现了多项资源限制:

  • 输出缓冲区上限:stdout/stderr 最多缓存 5MB,超过后截断并广播截断提示。
  • 命令超时策略:支持基础超时、延长次数上限和总预算封顶。
  • 静默进程终止:如果在预算期内无输出,视为挂起或静默失败,到期即杀。
  • 环境变量白名单:子进程只继承必要环境变量,避免泄露宿主敏感信息。
  • 进程树清理:Windows 使用 taskkill /T /PID,Unix 使用负 PID 终止进程组。

这些机制与文件沙箱共同构成 Agent 端的安全基线:文件访问受路径白名单约束,命令执行受分级管控和资源限制约束。

章节来源

故障排查指南 ​

路径被拒绝但确实在工作目录下 ​

可能原因:

  • 路径包含符号链接,且符号链接指向硬阻止目录。
  • 路径尚未存在,父目录也无法解析。
  • 路径大小写或分隔符导致规范化后不匹配。

排查建议:

  • 检查路径是否经过 realpath 解析。
  • 检查是否存在符号链接。
  • 检查 workdir 是否与预期一致。

章节来源

白名单配置未生效 ​

可能原因:

  • 配置文件损坏或解析失败,回退到默认配置。
  • allowedPaths 被自动追加了 SKILLS_DIR 和 WORKSPACE_DIR。
  • 路径未正确规范化。

排查建议:

  • 检查 config.json 是否可解析。
  • 检查 loadConfig 是否返回默认配置。
  • 检查路径是否经过 normalizePathFormat。

章节来源

命令被拒绝但认为风险不高 ​

可能原因:

  • 命中黑名单,黑名单不可绕过。
  • 命中灰名单,但未走用户确认流程。
  • 命令包含内联载荷或子命令替换。

排查建议:

  • 检查命令是否命中 BLACKLIST_PATTERNS。
  • 检查是否命中 GRAYLIST_PATTERNS。
  • 检查命令是否包含 $( … )、反引号、-c/-e、-Command 等特征。

章节来源

安全最佳实践 ​

输入净化 ​

  • 始终对路径进行类型校验,拒绝非字符串输入。
  • 对用户输入的 ~ 展开为主目录,避免误判。
  • 使用 normalizePathFormat 统一跨平台路径格式。
  • 使用 resolve + normalize 消除 ..、.、重复分隔符。

输出验证 ​

  • 对文件读取结果进行长度和内容校验。
  • 对命令输出设置缓冲区上限,防止内存耗尽。
  • 对截断输出广播提示,避免上层误以为完整成功。

资源限制 ​

  • 为命令执行设置合理超时。
  • 对长时间无输出的命令及时终止。
  • 限制子进程环境变量,避免敏感信息泄露。
  • 使用进程组终止,防止僵尸进程残留。

审计日志记录 ​

扩展端工具执行会记录审计日志,包括工具名称、参数、结果、错误信息等。建议在 Agent 端也记录:

  • 每次路径检查的结果。
  • 被拒绝的路径和原因。
  • 被拒绝的命令和分类。
  • 用户确认后的强制放行记录。

章节来源

结论 ​

Agent 端的文件沙箱与安全控制围绕“路径白名单 + 硬阻止清单 + 符号链接检测 + 工作目录特殊处理”构建,形成多层防御:

  • normalizePathFormat 解决跨平台路径差异。
  • allowedPaths 提供可配置的访问范围。
  • workdir 保证用户当前工作目录始终可用。
  • MEMORY_DIR 作为系统级受信任目录自动放行。
  • HARD_BLOCKED_PATHS 与 isHardBlocked 防止核心敏感文件被绕过。
  • realpath 防止符号链接逃逸。
  • 命令分级管控补充文件沙箱无法覆盖的命令安全风险。

在生产环境中,应结合输入净化、输出验证、资源限制和审计日志,形成完整的可信执行边界。同时需注意:Agent 源码修改后必须重启进程,新接口才生效;命令通道不受 allowedPaths 约束,不能仅依赖文件沙箱保护系统安全。