主题
命令执行引擎
本文引用的源码文件
本文引用的文件
目录
引言
本文件聚焦 Agent 端的「命令执行引擎」,围绕以下目标展开:
- 命令解析与安全分级:解释黑名单、灰名单、链式命令拆分、包装器剥壳、内联载荷与命令替换扫描。
- 参数处理与执行上下文:工作目录、安全环境变量白名单、平台 Shell 选择、输出缓冲与截断。
- 进程树管理与监控:子进程生命周期、WebSocket 流式输出、已完成进程回放、跨平台终止。
- 超时与预算控制:受管模式、静默即杀、延长策略、总预算封顶。
- 沙箱隔离与权限控制:路径白名单、硬阻止目录、脚本执行权限剥离、CORS 与认证边界。
- 并发、竞争与内存保护:运行中进程 Map、输出上限、定时器清理、孤儿进程防护。
- 调试与可观测性:日志分类、运行进程列表、错误事件、完成缓存。
本项目为双进程架构:Chrome 扩展端(Manifest V3)通过本地 HTTP/WebSocket 调用 Agent;Agent 是独立 Node.js 服务。命令执行能力由 agent/src/server.js 暴露接口,并委托 agent/src/executor.js 管理子进程,由 agent/src/security.js 做命令与路径安全校验,agent/src/process-tree.js 提供进程树终止工具。
章节来源
- agent/src/server.js#L1-L56
- agent/src/executor.js#L1-L10
- agent/src/security.js#L1-L17
- agent/src/process-tree.js#L1-L9
项目结构
命令执行相关代码集中在 Agent 端:
server.js:HTTP 路由、认证、WebSocket 连接、命令执行接口、状态查询。executor.js:命令执行器、Shell 选择、超时策略、进程跟踪、输出流、WS 广播。security.js:路径沙箱、命令黑白名单、链式命令拆分、包装器剥壳、内联载荷提取。process-tree.js:跨平台进程树终止与优雅退出封装。- 单元测试:覆盖超时策略纯函数与命令安全检查回归用例。
graph TB Client["扩展端 / 外部客户端"] --> Server["Agent HTTP+WebSocket 服务器<br/>server.js"] Server --> Security["命令与路径安全层<br/>security.js"] Server --> Executor["命令执行器<br/>executor.js"] Executor --> OS["操作系统 Shell 与子进程"] Server --> ProcessTree["进程树终止工具<br/>process-tree.js"] Server --> Logger["日志系统"]
图表来源
- agent/src/server.js#L1-L56
- agent/src/executor.js#L1-L10
- agent/src/security.js#L1-L17
- agent/src/process-tree.js#L1-L9
章节来源
- agent/src/server.js#L1-L80
- agent/src/executor.js#L1-L120
- agent/src/security.js#L1-L120
- agent/src/process-tree.js#L1-L42
核心组件
- 命令执行器:负责选择 Shell、创建子进程、流式 stdout/stderr、超时裁决、进程终止、运行状态查询。
- 安全层:负责路径白名单与硬阻止检查、命令分级(allow/confirm/deny)、链式命令与内联载荷扫描。
- 进程树工具:提供跨平台递归终止与优雅退出封装。
- 服务器入口:统一认证、CORS、请求体限制、命令执行接口、WebSocket 流式输出、运行进程列表上报。
章节来源
- agent/src/executor.js#L198-L386
- agent/src/security.js#L231-L758
- agent/src/process-tree.js#L18-L73
- agent/src/server.js#L417-L549
架构总览
命令从扩展端进入 Agent 后,经历“认证 → 安全校验 → 执行调度 → 流式输出 → 超时/终止 → 结果回放”的完整链路。
sequenceDiagram
participant Ext as "扩展端"
participant Srv as "server.js"
participant Sec as "security.js"
participant Exe as "executor.js"
participant OS as "OS Shell/子进程"
participant WS as "WebSocket 客户端"
Ext->>Srv : "POST /api/exec含 command、cwd、timeoutMs"
Srv->>Sec : "checkCommand(command, force)"
Sec-->>Srv : "{level, reason}"
alt level=deny
Srv-->>Ext : "拒绝响应"
else level=confirm
Srv-->>Ext : "需要用户确认"
Ext-->>Srv : "force=true 或取消"
end
Srv->>Exe : "executeCommand(command, cwd, wsClient, onComplete, collectOutput, {timeoutMs})"
Exe->>OS : "spawn(shell, args, command)"
OS-->>Exe : "stdout/stderr/close/error"
Exe->>WS : "broadcast(stdout/stderr/error/exit)"
Exe->>Exe : "decideTimeoutAction(...) 裁决延长或终止"
Exe->>OS : "killProcess(execId) 必要时终止进程树"
Exe-->>Srv : "onComplete({execId, exitCode, killed, timedOut})"
Srv-->>Ext : "最终结果"
图表来源
- agent/src/server.js#L417-L549
- agent/src/security.js#L691-L758
- agent/src/executor.js#L198-L386
- agent/src/executor.js#L495-L549
章节来源
详细组件分析
命令解析与安全分级
安全层对命令进行三层处理:
- 子命令范围收集:原始整串 + 链式拆分 + 包装器剥壳 + 解释器内联载荷 + 命令替换内部。
- 黑名单判定:绝对禁止项(如破坏系统目录、格式化磁盘、fork bomb、恶意管道执行等),不可被
force绕过。 - 灰名单判定:高风险但可确认的操作(sudo、rm -rf、全局安装、网络外联、进程操作、脚本执行等),可通过用户确认跳过。
关键机制包括:
collectCommandScopes:递归收集所有需检查的命令片段,深度上限防止无限展开。splitShellSegments:按&&、||、;、|、&、换行拆分,但保留引号、反引号、$( )内部内容。peelCommandWrappers:剥离nohup、time、env、sudo、doas等前缀,暴露真实命令。extractInlinePayloads:提取 POSIX-c/-e、cmd/c /k、PowerShell-Command/-EncodedCommand中的载荷。extractSubstitutions:提取反引号与$()内的子命令,避免引号包裹导致的安全逃逸。normalizeShellPathTargets:折叠//、/.、..等路径变形,防止绕过系统目录黑名单。
flowchart TD
Start(["输入命令"]) --> Collect["collectCommandScopes<br/>收集原始整串与子段"]
Collect --> Split["splitShellSegments<br/>按控制符拆分"]
Split --> Peel["peelCommandWrappers<br/>剥离 nohup/env/sudo 等包装器"]
Split --> Inline["extractInlinePayloads<br/>提取 -c/-e / /c / -Command"]
Split --> Subst["extractSubstitutions<br/>提取 $( ) 与反引号内部"]
Split --> PathNorm["normalizeShellPathTargets<br/>折叠 // /. .. 路径"]
Peel --> CheckBlack["BLACKLIST_PATTERNS 命中?"]
Inline --> CheckBlack
Subst --> CheckGray["GRAYLIST_PATTERNS 命中?"]
PathNorm --> CheckBlack
CheckBlack --> |是| Deny["返回 deny不可 force 绕过"]
CheckBlack --> |否| Force{"force 已确认?"}
Force --> |是| Allow["返回 allow"]
Force --> |否| CheckGray
CheckGray --> |是| Confirm["返回 confirm需用户确认"]
CheckGray --> |否| Allow
图表来源
章节来源
参数处理与执行上下文
命令执行器在启动子进程前构建执行上下文:
- 工作目录:优先使用传入
cwd,否则回退到配置中的workdir。 - Shell 选择:根据平台与环境变量选择 bash/zsh/fish/cmd/powershell,并处理 Git Bash MSYS 路径问题。
- 环境变量白名单:仅透传
PATH、HOME、USER、COMSPEC、PSModulePath等必要变量,强制关闭彩色输出。 - 输出缓冲:stdout/stderr 分别维护缓冲区,单流最大 5MB,超过则标记截断并广播通知。
- WebSocket 广播:实时推送 stdout、stderr、error、exit 消息给所有连接的客户端。
flowchart TD Enter(["executeCommand 入口"]) --> Cwd["确定 workdir"] Cwd --> Shell["pickShell 选择 shell 与参数"] Shell --> Env["buildSafeEnv 构建安全环境变量"] Env --> Spawn["spawn(child_process) 创建子进程"] Spawn --> Stdout["stdout 数据回调"] Spawn --> Stderr["stderr 数据回调"] Stdout --> Buffer["appendWithLimit 追加到缓冲区"] Stderr --> Buffer Buffer --> Broadcast["broadcast 推送 WS 消息"] Spawn --> Close["close/error 事件"] Close --> Finalize["completedProcesses 缓存 + runningProcesses 清理"]
图表来源
章节来源
进程树管理与子进程监控
执行器维护两个核心数据结构:
runningProcesses:当前运行中的进程映射,包含子进程对象、WS 客户端集合、定时器、输出缓冲、命令文本、开始时间、超时状态等。completedProcesses:已完成进程的短期缓存,用于延迟连接的 WS 客户端回放全部输出与结束事件。
进程终止策略:
- Unix:优先向进程组发送 SIGTERM,5 秒后升级为 SIGKILL。
- Windows:使用
taskkill /T /PID递归终止进程树,失败后强制/F。 - 显式 detach:允许命令脱离超时控制继续运行,适用于后台任务。
classDiagram
class RunningProcessEntry {
+string execId
+ChildProcess process
+Set~wsClient~ wsClients
+number timeoutId
+number forceKillId
+string stdoutBuf
+string stderrBuf
+boolean truncStdout
+boolean truncStderr
+string command
+number startTime
+number baseTimeout
+boolean managed
+number extensions
+boolean outputInPeriod
+boolean detached
+boolean timedOut
}
class CompletedProcessEntry {
+string stdoutBuf
+string stderrBuf
+number exitCode
+boolean killed
+boolean timedOut
+string error
+number cleanupTimeout
}
class Executor {
+Map~execId, RunningProcessEntry~ runningProcesses
+Map~execId, CompletedProcessEntry~ completedProcesses
+executeCommand()
+killProcess()
+addWsClient()
+disconnectWsClient()
+detachProcess()
+getRunningProcesses()
}
Executor --> RunningProcessEntry : "持有"
Executor --> CompletedProcessEntry : "短期缓存"
图表来源
- agent/src/executor.js#L93-L116
- agent/src/executor.js#L240-L246
- agent/src/executor.js#L332-L343
- agent/src/executor.js#L429-L453
- agent/src/executor.js#L495-L549
章节来源
- agent/src/executor.js#L93-L116
- agent/src/executor.js#L240-L246
- agent/src/executor.js#L332-L343
- agent/src/executor.js#L429-L453
- agent/src/executor.js#L495-L549
超时处理机制与预算控制
超时策略分为两种模式:
- 旧模式:到期直接 kill,不延长。
- 受管模式:通过
decideTimeoutAction决策是否延长,规则如下:- 若延长次数已达上限,直接 kill。
- 若本预算期内无输出(stdout/stderr 未触发),视为挂起,直接 kill。
- 若 nextBudget 无法增长(触及总预算封顶),kill。
- 否则延长一次,重置
outputInPeriod,重新设置定时器。
总预算封顶为 600 秒,延长次数上限为 5 次。该逻辑为纯函数,便于单元测试验证。
flowchart TD
Start(["超时到达"]) --> Managed{"受管模式?"}
Managed --> |否| KillOld["直接 kill 进程"]
Managed --> |是| Decision["decideTimeoutAction(state)"]
Decision --> ExtensionsMax{"extensions >= MAX_TIMEOUT_EXTENSIONS?"}
ExtensionsMax --> |是| KillExtend["kill 进程"]
ExtensionsMax --> |否| OutputCheck{"outputInPeriod ?"}
OutputCheck --> |否| KillSilent["kill 进程静默即杀"]
OutputCheck --> |是| BudgetCalc["计算 currentBudget 与 nextBudget"]
BudgetCalc --> CanExtend{"nextBudget > currentBudget ?"}
CanExtend --> |否| KillCap["kill 进程触及封顶"]
CanExtend --> |是| Extend["延长一次,重置 outputInPeriod,重新 armTimeout"]
图表来源
章节来源
- agent/src/executor.js#L165-L186
- agent/src/executor.js#L248-L275
- agent/test/unit/executor-timeout-policy.unit.test.js#L10-L40
输出流处理、错误捕获与调试信息
- 输出流:stdout/stderr 分别流式广播,同时累积到缓冲区供回放与可选内存收集。
- 截断通知:当缓冲区超过 5MB,广播
stdout_truncated或stderr_truncated消息。 - 错误捕获:子进程 error 事件会广播错误消息,并将进程移入 completedProcesses 缓存。
- 调试信息:
getRunningProcesses返回运行中进程的 execId、pid、command、startTime、duration。 - 日志分类:server.js 将日志分类为 auth、fs、exec、security、system、error,便于审计与排查。
章节来源
并发执行控制、资源竞争与内存泄漏防护
- 并发控制:每个命令生成唯一 execId,通过 Map 管理,避免重复执行冲突。
- 资源竞争:
finished标志防止 close/error 重复处理;定时器在 clearTimers 中统一清理。 - 内存泄漏防护:
- 输出缓冲区上限 5MB,超限时截断并标记。
- completedProcesses 缓存 TTL 30 秒,自动清理。
- disconnectWsClient 在受管模式下不解除超时,避免 SW 空闲被 Chrome 终止后命令提前释放。
- killProcess 5 秒后强制 kill,防止孤儿进程。
章节来源
- agent/src/executor.js#L93-L116
- agent/src/executor.js#L232-L246
- agent/src/executor.js#L287-L295
- agent/src/executor.js#L459-L493
- agent/src/executor.js#L533-L546
依赖关系分析
命令执行引擎的核心依赖如下:
- server.js 依赖 executor.js、security.js、process-tree.js、logger.js、config.js、auth.js。
- executor.js 依赖 child_process、fs、crypto、os、config.js、i18n.js。
- security.js 依赖 path、fs/promises、os、config.js、i18n.js。
- process-tree.js 依赖 child_process。
graph LR Server["server.js"] --> Executor["executor.js"] Server --> Security["security.js"] Server --> ProcessTree["process-tree.js"] Server --> Logger["logger.js"] Server --> Config["config.js"] Server --> Auth["auth.js"] Executor --> ChildProc["child_process"] Executor --> FS["fs"] Executor --> Crypto["crypto"] Executor --> OS["os"] Security --> Path["path"] Security --> FSPromises["fs/promises"]
图表来源
- agent/src/server.js#L1-L56
- agent/src/executor.js#L1-L10
- agent/src/security.js#L1-L6
- agent/src/process-tree.js#L10-L10
章节来源
- agent/src/server.js#L1-L56
- agent/src/executor.js#L1-L10
- agent/src/security.js#L1-L6
- agent/src/process-tree.js#L10-L10
性能与资源约束
- 输出缓冲上限:5MB,防止长时间命令导致内存无限增长。
- 上传大小限制:JSON body 200MB,流式上传 200MB。
- 搜索结果上限:单次最多 5000 条。
- 文件读取默认上限:50MB,可通过配置调整。
- 超时总预算封顶:600 秒,延长次数上限 5 次。
- 进程终止宽限期:5 秒后强制 kill,防止孤儿进程。
- 已完成进程缓存 TTL:30 秒,自动清理。
章节来源
- agent/src/executor.js#L96-L98
- agent/src/executor.js#L165-L167
- agent/src/server.js#L132-L136
- agent/src/server.js#L722-L748
故障排查指南
常见问题与定位方法:
- 命令被拒绝:检查 security.js 的黑名单与灰名单,确认是否命中敏感操作。
- 命令超时:查看 executor.js 的 decideTimeoutAction 逻辑,确认是否因静默或达到延长上限被 kill。
- 输出缺失:检查 WS 客户端是否正确连接,completedProcesses 是否已回放。
- 进程残留:确认 killProcess 是否成功调用,Windows 下 taskkill 是否执行。
- 路径访问失败:检查 checkPath 的白名单与硬阻止逻辑,确认 workdir 与 allowedPaths 配置。
章节来源
- agent/src/security.js#L691-L758
- agent/src/executor.js#L165-L186
- agent/src/executor.js#L429-L453
- agent/src/executor.js#L495-L549
结论
命令执行引擎通过安全层与执行器的解耦设计,实现了命令解析、安全分级、进程管理、超时控制与资源保护的完整闭环。其核心优势在于:
- 安全分层清晰:黑名单绝对禁止,灰名单可确认,路径沙箱与硬阻止目录双重保障。
- 执行可控性强:受管模式下的超时预算与延长策略,避免长尾任务占用资源。
- 可观测性好:WS 流式输出、运行进程列表、日志分类与完成缓存,便于调试与审计。
- 跨平台兼容:Windows 与 Unix 的 Shell 选择、进程终止策略均经过细致适配。
未来优化方向可考虑:
- 动态调整超时预算与输出缓冲上限。
- 增加命令执行配额与并发限制。
- 提供更细粒度的命令模板与策略配置界面。
[本节为总结性内容,不直接分析具体文件]
附录:命令模板、策略配置与调优建议
命令模板建议
- 只读查询类:
ls、pwd、whoami、date、uname、git status、npm run build。 - 安全写入类:仅在 workdir 内写入,避免系统目录与敏感文件。
- 高风险操作类:sudo、rm -rf、chmod、chown、curl、wget、kill、systemctl 等,需用户确认。
- 脚本执行类:避免直接执行外部脚本,优先使用内置工具或受控环境。
执行策略配置
commandTimeout:基础超时时间,影响受管模式的初始预算。workdir:工作目录,始终放行且不受 allowedPaths 限制。allowedPaths:额外允许访问的路径白名单。fileMaxSize:文件读取默认大小限制。MAX_TIMEOUT_EXTENSIONS:最大延长次数。TOTAL_BUDGET_CAP_MS:总预算封顶时间。
性能调优指南
- 合理设置
commandTimeout,避免过长等待。 - 监控
runningProcesses,及时终止卡死任务。 - 调整输出缓冲上限,平衡内存与回放需求。
- 使用流式下载与上传,避免大文件内存膨胀。
- 定期清理 completedProcesses 缓存,防止内存泄漏。
章节来源