主题
命令执行引擎
本文引用的源码文件
本文引用的文件
目录
引言
本文面向“命令执行引擎”的完整技术文档,聚焦以下目标:
- 解释命令安全分级系统:黑名单(deny)与灰名单(confirm)的区别,以及 force 参数的作用机制。
- 深入说明命令解析器:splitShellSegments 链式拆分、collectCommandScopes 子命令范围收集、substitutionCheckScopes 命令替换特征检测。
- 描述进程管理功能:进程树构建、父子关系追踪、超时控制与资源清理。
- 说明命令执行策略:并行执行限制、输出流处理、错误恢复机制。
- 明确安全边界:变量间接执行等静态不可判定场景。
- 给出命令执行的最佳实践和安全建议。
本项目采用双进程架构:Chrome 扩展端(Manifest V3,src/)与独立本地代理端 Agent(Node.js,agent/src/server.js)。命令执行能力由 Agent 端实现,扩展端通过 HTTP/WebSocket 调用;Agent 源码修改后需重启进程才生效。
项目结构
命令执行相关代码主要分布在 Agent 端的三个模块中:
- agent/src/security.js:命令安全分级、路径沙箱、命令解析与模式匹配。
- agent/src/executor.js:命令执行、进程生命周期、超时策略、WebSocket 流式输出。
- agent/src/process-tree.js:进程树终止工具,用于 npm 等派生孙进程场景。
- src/background/completion-verifier.js:扩展端完成度校验侧的命令构建逻辑,体现只读模板与 shQuoteArg 转义约束。
graph TB subgraph "扩展端" CV["completion-verifier.js<br/>构建验证命令"] end subgraph "Agent 端" SEC["security.js<br/>命令安全分级 + 解析"] EXEC["executor.js<br/>进程执行 + 超时 + 流式输出"] PT["process-tree.js<br/>进程树终止工具"] end CV --> |"HTTP/WebSocket 调用"| SEC SEC --> |"checkCommand / collectCommandScopes"| EXEC EXEC --> |"killProcess / taskkill / SIGKILL"| PT
图表来源
- agent/src/security.js#L691-L758
- agent/src/executor.js#L198-L386
- agent/src/process-tree.js#L18-L42
- src/background/completion-verifier.js#L207-L234
章节来源
- agent/src/security.js#L1-L761
- agent/src/executor.js#L1-L571
- agent/src/process-tree.js#L1-L74
- src/background/completion-verifier.js#L207-L234
核心组件
- 命令安全分级:基于黑名单与灰名单的正则模式匹配,支持链式命令逐段检查、解释器内联载荷展开、命令替换特征检测,并区分 deny/confirm/allow 三种结果。
- 命令解析器:splitShellSegments 按 shell 控制符拆分命令串;collectCommandScopes 递归收集原始整串、拆分子段、剥壳包装器、解释器 -c/-e 载荷、$( ) 与反引号替换;substitutionCheckScopes 对替换位置做误报纠偏。
- 进程执行器:spawn 启动 shell,选择平台 shell,设置安全环境变量,流式广播 stdout/stderr,维护运行中进程映射与已完成缓存,提供同步执行封装。
- 超时策略:受管模式下根据 decideTimeoutAction 决策 extend/kill,非受管模式直接 kill;支持最大延长次数与总预算封顶。
- 进程树终止:POSIX 使用负 PID 向进程组发信号,Windows 使用 taskkill /T /PID,并提供 stopProcessTree 等待退出或强制升级。
章节来源
架构总览
命令执行从扩展端到 Agent 端的典型流程如下:
sequenceDiagram
participant UI as "扩展端界面"
participant CV as "completion-verifier.js"
participant API as "Agent HTTP/WebSocket"
participant SEC as "security.js"
participant EXEC as "executor.js"
participant PT as "process-tree.js"
UI->>CV : "构建验证命令只读模板/shQuoteArg"
CV-->>UI : "command 或 reason"
UI->>API : "提交命令执行请求"
API->>SEC : "checkCommand(command, force)"
SEC-->>API : "{safe, level, reason}"
alt level=deny
API-->>UI : "拒绝执行黑名单命中"
else level=confirm
API-->>UI : "弹出确认弹窗"
UI->>API : "用户确认force=true"
API->>EXEC : "executeCommand(command, wsClient)"
else level=allow
API->>EXEC : "executeCommand(command, wsClient)"
end
EXEC->>EXEC : "spawn shell + 流式输出 + 超时控制"
EXEC->>PT : "killProcess / stopProcessTree必要时"
EXEC-->>UI : "stdout/stderr/exit/error 事件"
图表来源
- src/background/completion-verifier.js#L207-L234
- agent/src/security.js#L691-L758
- agent/src/executor.js#L198-L386
- agent/src/process-tree.js#L18-L74
详细组件分析
命令安全分级系统(deny/confirm/allow 与 force)
- 黑名单(deny):绝对禁止的高危模式,包括递归删除系统目录、格式化磁盘、覆盖敏感系统文件、fork bomb、shebang 直接执行、管道到解释器提权执行、根目录权限变更等。黑名单始终生效,即使 force 也不可绕过。
- 灰名单(confirm):需要真实用户确认的模式,包括 sudo/doas/runas、全局 npm install、pip install/uninstall、chmod 777、rm -rf 变体、git push -f、shutdown/reboot/halt/poweroff、写 /etc/hosts、eval、命令替换、dd of=、shred、truncate、chmod/chown、网络外联(curl/wget/nc/telnet/ssh/scp)、进程操作(kill/pkill/killall)、系统服务(systemctl/launchctl/crontab/sysctl)、脚本解释器执行外部脚本、PowerShell 破坏性 cmdlet 与 base64 内联载荷等。
- allow:未命中任何黑名单或灰名单时允许执行。
- force 参数:当用户已确认(例如点击确认弹窗或关闭确认开关),可跳过灰名单检查,但黑名单仍生效。
flowchart TD
Start(["输入命令"]) --> Validate["校验命令是否为空/非法"]
Validate --> |非法| DenyInvalid["返回 deny命令无效"]
Validate --> |合法| Scopes["collectCommandScopes 收集子命令范围"]
Scopes --> Subst["substitutionCheckScopes 计算替换送检集合"]
Subst --> Blacklist{"是否命中黑名单?"}
Blacklist --> |是| DenyBlock["返回 deny命令被阻止"]
Blacklist --> |否| Force{"force 是否开启?"}
Force --> |是| AllowForce["返回 allow跳过灰名单"]
Force --> |否| Graylist{"是否命中灰名单?"}
Graylist --> |是| Confirm["返回 confirm需要用户确认"]
Graylist --> |否| AllowSafe["返回 allow安全检查通过"]
图表来源
章节来源
命令解析器:splitShellSegments、collectCommandScopes、substitutionCheckScopes
- splitShellSegments:按 shell 控制符拆分命令串,同时尊重引号、反引号、$( ) 内部不拆分,避免把
"a && b"误拆。 - collectCommandScopes:始终包含原始整串,再追加逐段拆分结果;剥离 env/sudo/doas/nohup/time/timeout/nice/stdbuf/exec/command/xargs 等包装器前缀,递归暴露内层命令;归一化路径目标(//usr、/.//usr、/tmp/../usr);提取解释器内联载荷(POSIX -c/-e、cmd /c /k、PowerShell -Command/-EncodedCommand);提取 $( ) 与反引号替换内容;深度封顶防死循环。
- substitutionCheckScopes:对命令替换特征做误报纠偏,排除纯输出头命令(echo/printf/cat/head/tail/wc/ls/pwd/date/whoami/hostname/uname/id/env/printenv/which/type)和无害头命令(true/false/test/cd/dir),并在引号参数内查找含替换片段,避免扩大扫描范围到整个命令串。
flowchart TD Cmd["原始命令"] --> Split["splitShellSegments 拆分"] Split --> Segments["子命令段集合"] Segments --> Peel["peelCommandWrappers 剥壳包装器"] Peel --> Recurse["collectCommandScopes 递归收集"] Recurse --> Inline["extractInlinePayloads 提取 -c/-e /c /k /Command /EncodedCommand"] Recurse --> Subst["extractSubstitutions 提取 $( ) 与反引号"] Inline --> Scopes["最终子命令范围集合"] Subst --> Scopes Scopes --> SubstCheck["substitutionCheckScopes 替换特征纠偏"]
图表来源
- agent/src/security.js#L567-L642
- agent/src/security.js#L653-L683
- agent/src/security.js#L266-L283
- agent/src/security.js#L486-L512
- agent/src/security.js#L521-L560
章节来源
- agent/src/security.js#L567-L642
- agent/src/security.js#L653-L683
- agent/src/security.js#L266-L283
- agent/src/security.js#L486-L512
- agent/src/security.js#L521-L560
进程管理与超时控制
- 进程树构建:executor.js 使用 spawn 启动 shell,并根据平台选择 bash/zsh/fish/cmd/powershell;detached 在 Unix 上启用以支持进程组终止,在 Windows 原生控制台 shell 上禁用以避免 AllocConsole 黑窗。
- 父子关系追踪:runningProcesses 映射 execId 到进程条目,包含 process、wsClients、缓冲区、定时器、状态字段;completedProcesses 缓存已结束进程的输出与状态,供延迟连接的 WS 客户端回放。
- 超时控制:受管模式下,decideTimeoutAction 根据期内是否有输出决定 extend 或 kill;非受管模式到期直接 kill;支持最大延长次数与总预算封顶(默认 600000ms)。
- 资源清理:clearTimers 清理 timeoutId 与 forceKillId;disconnectWsClient 在所有客户端断开且非受管或未显式 detach 时取消超时;detachProcess 显式声明 daemon 化取消超时;killProcess 跨平台递归终止进程树,5 秒后升级为 SIGKILL/taskkill /F。
classDiagram
class Executor {
+executeCommand(command, cwd, wsClient, onComplete, collectOutput, execOpts) string
+executeCommandSync(command, cwd, timeoutMs) Promise
+addWsClient(execId, wsClient) boolean
+disconnectWsClient(execId, wsClient) boolean
+detachProcess(execId) boolean
+killProcess(execId) boolean
+getRunningProcesses() Array
+pickShell(platform, env, exists) Object
+decideTimeoutAction(state, capMs, maxExtensions) Object
}
class ProcessEntry {
+process ChildProcess
+wsClients Set
+stdoutBuf string
+stderrBuf string
+truncStdout boolean
+truncStderr boolean
+command string
+startTime number
+baseTimeout number
+managed boolean
+extensions number
+outputInPeriod boolean
+detached boolean
+timedOut boolean
+timeoutId number
+forceKillId number
}
class ProcessTree {
+killProcessTree(child, signal) boolean
+stopProcessTree(child, options) Promise
}
Executor --> ProcessEntry : "维护 runningProcesses"
Executor --> ProcessTree : "必要时调用 killProcessTree"
图表来源
章节来源
- agent/src/executor.js#L32-L186
- agent/src/executor.js#L198-L386
- agent/src/executor.js#L429-L549
- agent/src/process-tree.js#L18-L74
命令执行策略:并行限制、输出流处理、错误恢复
- 并行执行限制:命令执行本身在 executor.js 中为单进程执行;并行限制更多体现在扩展端的子任务调度(dispatchSubtasksParallel),测试用例表明峰值并发应 ≤ maxParallel。
- 输出流处理:stdout/stderr 分别流式广播,超过 5MB 缓冲上限时截断并发送 trunc 通知;completedProcesses 缓存输出以便延迟连接回放。
- 错误恢复:进程 error/close 事件统一处理,finished 标志防止重复处理;onComplete 回调返回 exitCode/killed/error;sync 模式额外兜底超时保护,确保不会永久挂起。
flowchart TD Spawn["spawn shell"] --> Stdout["stdout 数据事件"] Spawn --> Stderr["stderr 数据事件"] Stdout --> Buffer["appendWithLimit 缓冲 + 截断标记"] Stderr --> Buffer Buffer --> Broadcast["broadcast 到 wsClients"] Broadcast --> Exit["close/error 事件"] Exit --> Cleanup["clearTimers + 移入 completedProcesses"] Cleanup --> Complete["onComplete 回调"]
图表来源
章节来源
依赖关系分析
- security.js 依赖 path/fs/promises/os/config/i18n,提供 checkPath/checkCommand/collectCommandScopes 等能力。
- executor.js 依赖 child_process/fs/crypto/os/config/i18n,提供 executeCommand/executeCommandSync/addWsClient/disconnectWsClient/killProcess/detachProcess/getRunningProcesses/pickShell。
- process-tree.js 依赖 child_process,提供 killProcessTree/stopProcessTree。
- completion-verifier.js 在扩展端构建只读验证命令,体现 shQuoteArg 转义与 bad_path 防护。
graph LR SEC["security.js"] --> PATH["path"] SEC --> FS["fs/promises"] SEC --> OS["os"] SEC --> CFG["config.js"] SEC --> I18N["i18n.js"] EXEC["executor.js"] --> CP["child_process"] EXEC --> FS2["fs"] EXEC --> CRYPTO["crypto"] EXEC --> OS2["os"] EXEC --> CFG2["config.js"] EXEC --> I18N2["i18n.js"] PT["process-tree.js"] --> CP2["child_process"] CV["completion-verifier.js"] --> SHQ["shQuoteArg只读模板"]
图表来源
- agent/src/security.js#L1-L6
- agent/src/executor.js#L1-L8
- agent/src/process-tree.js#L10-L10
- src/background/completion-verifier.js#L207-L234
章节来源
- agent/src/security.js#L1-L6
- agent/src/executor.js#L1-L8
- agent/src/process-tree.js#L10-L10
- src/background/completion-verifier.js#L207-L234
性能与资源特性
- 输出缓冲上限:5MB,超过后截断并通知前端,防止长时间命令内存无限增长。
- 环境变量白名单:SAFE_ENV_KEYS 过滤宿主敏感信息,强制 TERM=dumb 与 FORCE_COLOR=0,减少终端颜色干扰。
- 超时策略:decideTimeoutAction 支持 extend/kill,最多延长 MAX_TIMEOUT_EXTENSIONS 次,总预算不超过 TOTAL_BUDGET_CAP_MS。
- 进程树终止:POSIX 优先进程组信号,Windows 使用 taskkill /T /PID,5 秒后升级为 SIGKILL /F。
- 已完成进程缓存:COMPLETED_CACHE_TTL=30000ms,延迟连接 WS 客户端可回放输出与状态。
[本节为通用性能讨论,不直接分析具体文件]
故障排查指南
- 命令被拒绝:检查是否命中黑名单(deny),如递归删除系统目录、格式化磁盘、覆盖敏感文件等;若为灰名单(confirm),需用户确认或关闭确认开关(force)。
- 命令执行无输出:检查 stdout/stderr 是否被截断(trunc 通知),或 WS 客户端是否正确添加(addWsClient)与回放(completedProcesses)。
- 进程未终止:检查 killProcess 是否成功发出信号,Windows 下 taskkill /T /PID 是否失败回退 /F;POSIX 下负 PID 进程组是否存在。
- 超时异常:检查 decideTimeoutAction 的 outputInPeriod 是否为 true,extensions 是否达到上限,baseTimeout 与 capMs 配置是否合理。
- 路径访问失败:检查 checkPath 的 allowedPaths/workdir/MEMORY_DIR 配置,realpath 符号链接绕过防护,硬阻止路径(HARD_BLOCKED_PATHS)。
章节来源
- agent/src/security.js#L109-L229
- agent/src/executor.js#L248-L275
- agent/src/executor.js#L499-L549
- agent/src/process-tree.js#L18-L74
安全边界与已知限制
- 命令通道不受 allowedPaths 路径沙箱约束:cat /etc/passwd 这类读取即外泄的动作无法在本层拦截。
- 变量间接执行(如 S=rm; $S -rf x)与脚本文件正文内容静态不可判:不在本层解决,遇到时以注释说明而非假装防住。
- curl/wget/kill 等条目会给常规用法带来确认弹窗,属预期摩擦,可用“本次任务放行”降低频次。
- 黑名单与灰名单的豁免边界:SUBSTITUTION_HARMLESS_HEADS 的只读豁免仅适用于命令替换这一语法特征,不应用于黑名单与其余灰名单条目。
章节来源
最佳实践与安全建议
- 优先使用只读命令进行验证:completion-verifier.js 构建 test -e 等只读模板,并通过 shQuoteArg 转义路径,避免注入型路径。
- 谨慎使用 force:仅在用户明确确认后启用,避免绕过灰名单确认。
- 合理配置超时:根据任务复杂度调整 commandTimeout,受管模式下利用 decideTimeoutAction 的动态延长机制。
- 监控输出截断:当 stdout/stderr 超过 5MB 时,前端应提示截断并记录 execId 以便回放。
- 避免高危命令:尽量不使用 rm -rf、mkfs、dd of=/dev/、chmod 777、sudo/doas 等高危命令,确需使用时务必经过确认。
- 注意平台差异:Windows 下 cmd/powershell/pwsh 会创建控制台窗口,应避免 detached;Unix 下 detached 有助于进程组终止。
章节来源
- src/background/completion-verifier.js#L207-L234
- agent/src/executor.js#L219-L227
- agent/src/executor.js#L165-L186
结论
命令执行引擎通过安全分级、命令解析、进程管理与超时控制,构建了较为完善的命令执行安全体系。黑名单绝对禁止、灰名单用户确认、force 跳过灰名单的机制提供了灵活的安全策略;splitShellSegments、collectCommandScopes、substitutionCheckScopes 的组合确保了链式命令、包装器、内联载荷与命令替换的全面覆盖;executor.js 的流式输出、超时策略与进程树终止保障了执行过程的稳定性与资源可控性。尽管存在变量间接执行等静态不可判定限制,但在当前边界内,该引擎为 AI 助手提供了可靠且安全的命令执行能力。