1. 项目概述:为AI助手戴上“紧箍咒”
如果你和我一样,深度依赖Cursor、Windsurf这类AI编程助手来提升开发效率,那你一定也经历过那种“心惊肉跳”的时刻:AI助手在理解了你的需求后,自信满满地敲下了一行rm -rf ./build或者sudo chmod -R 777 /,而你必须在它执行前的一瞬间,用你的人类直觉去判断——这玩意儿到底安不安全?一次误判,可能就是一场灾难。
AegisAI 正是为了解决这个痛点而生的。它的核心定位,是一个AI命令授权层。你可以把它想象成给AI助手配备的一位“贴身保镖”。这位保镖不干涉AI的思考和建议,但每当AI试图执行任何可能影响你系统的命令时,保镖会立刻站出来,举起一个“STOP”的牌子,然后通过你口袋里的手机向你请示:“老板,AI想执行这个命令,批不批?”
这个项目的诞生,源于一个非常朴素的安全理念:执行权必须牢牢掌握在人类手中。AI可以建议,可以生成代码,但最终按下“执行”按钮的,必须是一个经过深思熟虑的人。AegisAI 通过一套精巧的“人机协同”流程,在AI的意图与系统的执行之间,插入了一道必须由人类批准的、密码学加固的授权边界。
整个系统由三个核心部分组成,它们协同工作,构成了一个完整的防御闭环:
- VS Code 扩展:作为“前线哨兵”,它常驻在你的编辑器里,负责拦截所有由AI触发的命令,进行初步的风险评估,并将需要授权的命令加密签名后,发送给后端“指挥部”。
- Node.js 后端服务:作为“通信与指挥中心”,它使用 WebSocket(Socket.io)进行实时通信,并用 Redis 管理所有活跃的会话和待处理的授权请求,确保消息能准确无误地在扩展和手机App之间转发。
- Flutter 移动端应用:作为你手中的“最终决策终端”,它以卡片形式实时展示待审批的命令详情、风险等级和上下文,你只需轻轻一点“批准”或“拒绝”,这个经过数字签名的决策就会被送回系统执行。
这套机制最巧妙的地方在于它的“预执行拦截”。传统的安全工具可能在命令开始运行后才报警,但AegisAI通过一个Shell Preexec Hook,能在命令被shell解析但尚未执行的瞬间将其捕获并暂停。这意味着,即便是AI在终端里直接输入的命令,也会在真正运行前被你的手机“截胡”。这种设计从根本上杜绝了“手速不够快,命令已跑飞”的尴尬。
2. 核心架构与设计哲学
2.1 系统工作流全景解析
理解AegisAI,最关键的是理清那条贯穿始终的“授权链”。我们以一个高风险命令sudo rm -rf /tmp/critical_data为例,看看它是如何被“驯服”的。
场景:你在Cursor中与AI对话,要求它“清理掉/tmp/critical_data这个临时目录”。AI生成了上述命令。
第一步:拦截与风险评估AI在Cursor中并非直接调用系统终端,而是通过一个特殊的VS Code API命令(例如secure.executeCommand)来执行。AegisAI扩展监听了这个命令。命令一触发,扩展内的策略引擎立刻启动。引擎内置了一套基于正则表达式的规则库,它会扫描命令字符串:
- 看到
sudo?风险等级标记为HIGH。 - 看到
rm -rf且目标不是明确的临时或缓存目录?风险等级标记为CRITICAL。 根据你设定的安全模式(严格、均衡、宽松),引擎决定下一步。对于这种“高危组合拳”,无论何种模式,都会判定为“需要人工审批”。
第二步:构建并签名授权请求一旦判定需要审批,扩展会生成一个唯一的requestId,并组装一个授权请求对象。这个对象包含了命令原文、时间戳、设备ID、风险等级和一段AI生成的命令描述(帮助人类理解意图)。最关键的一步来了:扩展使用其私钥,对这个请求的核心字段(命令+时间戳+请求ID)生成一个Ed25519数字签名。这个签名就像是一个无法伪造的“数字指纹”,证明了“这个请求确实来自已注册的AegisAI扩展,而非某个恶意进程的伪造”。
第三步:实时转发与等待签名的请求通过WebSocket被发送到后端服务。后端服务首先会验证签名的有效性,确认请求来源合法。接着,它会在Redis中查找与该设备ID(如dev-1)关联的移动端Socket连接。找到后,将请求原样转发给你的手机App。
第四步:人类决策与签名确认你的手机屏幕亮起,弹出一个清晰的卡片:“【高危请求】AI试图执行:sudo rm -rf /tmp/critical_data。描述:清理临时数据目录。风险:CRITICAL”。你快速扫了一眼,意识到这个目录里还有未备份的重要文件。你点击了“拒绝”。手机App会用其私钥,对你的决策(请求ID + 批准状态)生成另一个数字签名,然后将这个签了名的决策发回后端。
第五步:验证决策与最终执行后端收到决策,同样先验证手机端的签名,确保决策来自你授权的设备。验证通过后,决策被转发回VS Code扩展。扩展验证签名后,看到“拒绝”的指令,于是向AI抛出一个错误:“命令执行被用户拒绝”。整个流程结束,危险命令被成功阻断。
如果决策是“批准”,扩展则会创建一个受保护的子进程来执行该命令,并将输出结果返回给AI。整个过程中,每一个环节的请求和决策都被记录到本地JSON文件和Redis中,形成不可篡改的审计日志。
2.2 关键设计决策背后的考量
为什么AegisAI要这样设计?每一个技术选型的背后,都是对安全性、可靠性和用户体验的反复权衡。
1. 密码学完整性(Ed25519):防御“冒名顶替”攻击这是系统的安全基石。试想,如果没有签名,一个恶意脚本完全可以伪装成手机App,向你的扩展发送“批准”指令。Ed25519椭圆曲线签名算法解决了这个问题。它速度快,签名短,且抗侧信道攻击。扩展和手机App各自生成一对密钥(公钥和私钥),公钥在注册时交换并存储在后端。每次通信,发送方都用私钥对消息签名,接收方用对应的公钥验证。这确保了:
- 请求真实性:后端可以确信授权请求来自合法的、已安装AegisAI扩展的编辑器。
- 决策权威性:扩展可以确信批准/拒绝指令来自你本人授权的手机,而不是其他任何设备或程序。
- 防篡改:签名基于消息内容生成,任何对命令或决策结果的篡改都会导致验证失败。
2. 状态管理(Redis):追求“无状态”服务的可靠性最初的版本使用内存存储会话映射(哪个Socket ID对应哪个设备)。这很简单,但存在致命问题:一旦后端服务进程重启,所有连接状态丢失,已连接的扩展和手机App会变成“孤岛”,无法通信。引入Redis后,会话状态被持久化。即使Node.js服务崩溃重启,它也能从Redis中恢复所有设备的连接映射,确保服务的高可用性。Redis的列表结构也天然适合用作审计日志的缓冲区,便于持久化到其他系统。
3. 策略驱动拦截:平衡安全与效率“一刀切”地拦截所有命令会严重拖慢开发流程。AegisAI的策略引擎引入了智能判断:
- 宽松模式:只拦截
rm -rf /*,sudo开头等极少数的“毁灭性”命令。适合高度信任AI、追求极致效率的场景。 - 均衡模式:默认模式。自动放行
ls,git status,cat等只读命令;对mkdir,cp,mv等修改性操作要求审批。在安全和便利间取得平衡。 - 严格模式:除明确加入白名单的命令外,一切皆需审批。适合处理极其敏感的项目或环境。 这种分级策略让安全管控变得精细化和可配置,而不是一个令人烦躁的“障碍”。
4. 故障安全行为:安全压倒一切系统的默认行为永远是“拒绝”。如果后端服务宕机、手机离线、网络中断,或者任何签名验证失败,扩展会直接阻止命令执行,并向AI返回错误。这遵循了“失效安全”原则:当系统出现不确定性时,应自动进入最安全的状态(即阻止潜在的危险操作),而不是冒险放行。
2.3 项目结构深度解读
清晰的目录结构是项目可维护性的第一步。AegisAI的代码组织体现了清晰的关注点分离。
AegisAI/ ├── extension/ # VS Code扩展 - “前线哨兵” │ ├── src/ │ │ ├── extension.ts # 入口:命令注册、Socket连接管理、生命周期 │ │ ├── policy.ts # 策略引擎核心:正则规则、风险评估函数 │ │ ├── ipc_server.ts # IPC服务器:与Shell Hook通信的本地HTTP服务 │ │ └── AegisPty.ts # 受保护终端:安全执行已批准命令的伪终端封装 │ └── package.json # 扩展清单,定义命令、配置、依赖 ├── backend/ # Node.js后端 - “指挥中心” │ ├── src/ │ │ └── server.ts # Socket.io服务器、Redis客户端、事件路由逻辑 │ └── package.json ├── mobile/ # Flutter应用 - “决策终端” │ ├── lib/ │ │ └── main.dart # 应用主入口,UI组件,Socket通信,签名逻辑 │ └── pubspec.yaml # Flutter项目配置与依赖 └── shared/ # 共享类型定义 └── protocol.ts # TypeScript接口:AuthRequest, AuthDecision等通信协议扩展 (extension/): 这是与开发者交互最频繁的部分。extension.ts是大脑,协调所有模块。policy.ts是守门人,决定了哪些命令能直接过,哪些需要请示。ipc_server.ts是一个小巧的HTTP服务器,它监听本地端口,为Shell Hook提供评估接口,这是实现“预执行拦截”的关键桥梁。AegisPty.ts则负责在命令被批准后,在一个受控的环境里安全地执行它,并捕获输出。
后端 (backend/): 极其精简,server.ts文件几乎包含了所有逻辑。它的核心是Socket.io的事件监听与转发,以及Redis的读写操作。它的设计目标是稳定、高效、无状态(依赖Redis)。
移动端 (mobile/): 使用Flutter实现,保证了在iOS和Android上都能获得原生般的体验。UI需要清晰、响应迅速,核心逻辑是接收请求、展示信息、获取用户输入、签名并回复。
共享 (shared/): 这个目录虽小但至关重要。它定义了扩展、后端、移动端之间通信的数据结构(TypeScript接口)。保持这些接口的一致,是三个独立组件能够无缝协作的前提。在实际开发中,可以考虑将它发布为一个独立的NPM包或Dart包,供三方引用,确保类型安全。
3. 从零开始的详细部署与实操指南
纸上谈兵终觉浅,绝知此事要躬行。下面,我将带你一步步搭建起完整的AegisAI环境。请准备好你的开发机器(macOS/Linux/WSL2环境为佳)和一部安卓/iOS测试手机。
3.1 环境准备:打好地基
操作系统与工具链
- Node.js (v20.x或更高):这是后端和扩展的运行时。建议使用nvm管理Node版本,避免全局污染。
# 安装nvm (macOS/Linux) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # 重新打开终端后,安装Node 20 nvm install 20 nvm use 20 - Redis (v7.x或更高):我们的状态与审计存储中心。在macOS上,用Homebrew安装是最简单的。
brew install redis brew services start redis # 设置为开机自启并立即启动 # 验证Redis是否运行 redis-cli ping # 如果返回 PONG,说明成功。注意:在生产环境或更严肃的用途中,应考虑为Redis设置密码认证,并调整默认的绑定地址(
bind 127.0.0.1),以增强安全性。开发环境使用默认配置即可。 - Flutter (3.x以上):用于构建移动端App。请严格按照Flutter官网的指南安装,并确保
flutter doctor命令检查通过,特别是Android或iOS的工具链。 - VS Code 或 Cursor (1.93以上):作为扩展的宿主。确保已安装必要的扩展开发依赖(如
npm和vsce)。
克隆项目与初步检查
git clone https://github.com/iamEtornam/AegisAI.git cd AegisAI # 快速浏览项目结构,确认上述目录都存在 ls -la3.2 后端服务启动:启动指挥中心
后端是连接扩展和手机的枢纽,必须先运行起来。
cd backend # 安装依赖包 npm install # 编译TypeScript代码到JavaScript npx tsc # 启动服务器,默认监听3000端口 node src/server.js如果一切顺利,你将在终端看到类似输出:
Connected to Redis AegisAI Broker listening on port 3000 (Redis Enabled)关键配置与环境变量:
PORT: 可以修改服务监听的端口。例如:PORT=4000 node src/server.js。REDIS_URL: 如果你的Redis不在本地默认端口,可以通过此变量指定。例如:REDIS_URL=redis://:password@host:port。
验证后端健康状态: 打开另一个终端,使用curl测试HTTP端点(虽然主要用WebSocket,但HTTP服务通常也运行):
curl -I http://localhost:3000 # 应返回 HTTP/1.1 200 OK 或类似的成功状态码。同时,检查Redis连接是否被正确使用:
redis-cli 127.0.0.1:6379> KEYS * # 此时可能还没有键,或者只有一个测试键。只要不报连接错误即可。3.3 VS Code扩展侧:配置你的哨兵
扩展需要在VS Code的“扩展开发宿主”中运行和调试。
cd ../extension # 从backend目录返回项目根目录,再进入extension npm install npm run compile # 或 `npm run watch` 进入监听模式,代码变动自动重编译- 在VS Code中,打开整个
AegisAI项目文件夹,或者单独打开extension文件夹。 - 按下
F5键。这将启动一个新的VS Code窗口,标题通常是“扩展开发宿主”。这个窗口加载了你正在开发的AegisAI扩展。 - 在新窗口中,打开“输出”面板 (
Ctrl+Shift+U或Cmd+Shift+U),在下拉菜单中选择“AegisAI Audit”。你应该能看到扩展启动的日志,包括生成的公钥、连接后端的状态,以及最重要的——IPC服务器监听的本地端口号。🚀 [2024-05-27T10:00:00.000Z] AegisAI started. 🔑 Public key: (一长串Base64编码) 📋 Policy mode: strict 🔌 Connecting to: http://localhost:3000 ✅ Connected to broker 📤 Registering device: dev-1 🔗 IPC server listening on 127.0.0.1:64912 # 记住这个端口号!实操心得:这个IPC端口号是随机的,每次启动都可能变化。它是Shell Hook能与扩展通信的关键。扩展会将它写入
~/.aegis/ipc_port文件,供Shell Hook读取。
3.4 移动端应用:准备好你的决策终端
这是整个流程中唯一需要一点网络配置的环节,因为你的手机和电脑需要处在同一个局域网。
查找电脑的局域网IP:
- macOS/Linux: 在终端运行
ifconfig | grep "inet " | grep -v 127.0.0.1,找到类似192.168.1.100的地址。 - Windows (WSL2): 在WSL2终端运行
ip addr show eth0 | grep inet,或直接在PowerShell运行ipconfig,查看WSL网络适配器的IPv4地址。
- macOS/Linux: 在终端运行
修改Flutter应用配置: 用编辑器打开
mobile/lib/main.dart文件,找到定义serverUrl的地方(通常在文件顶部或main函数附近)。将其中的http://192.168.1.166:3000替换成你电脑的IP地址和后端端口(默认3000)。// 修改前 final String serverUrl = 'http://192.168.1.166:3000'; // 修改后 (假设你的电脑IP是 192.168.1.100) final String serverUrl = 'http://192.168.1.100:3000';运行移动应用:
cd ../mobile flutter pub get # 获取Dart依赖包 # 连接你的安卓手机或启动iOS模拟器 flutter run应用启动后,界面应该显示“已连接”或类似的绿色状态指示。同时,后端服务的终端里应该会打印出
Mobile connected for device: dev-1的日志。
3.5 安装Shell Preexec Hook:实现终极拦截
这是让AegisAI能力覆盖终端命令的关键一步。没有它,AI通过终端直接执行的命令将无法被拦截。
在扩展开发宿主的VS Code窗口中,按下
Ctrl+Shift+P(Windows/Linux) 或Cmd+Shift+P(macOS) 打开命令面板。输入并选择“AegisAI: Install Shell Preexec Hook”。
命令执行后,它会做两件事:
- 在
~/.aegis/目录下创建shell_hook.sh脚本文件。 - 在你的Shell配置文件(
~/.zshrc或~/.bashrc)末尾追加一行source ~/.aegis/shell_hook.sh。
- 在
手动生效:为了让改动立即生效,你需要“source”一下你的配置文件,或者直接打开一个新的终端窗口。
# 如果你用的是 zsh source ~/.zshrc # 如果你用的是 bash source ~/.bashrc验证Hook是否生效: 在新的终端里,尝试输入一个高风险命令,比如
sudo ls。你应该会立刻看到终端输出Evaluating command...并卡住。同时,你的手机App上会弹出授权请求。这说明Hook已经成功介入到命令执行流程之前。
避坑指南:如果Hook没有生效,请检查:
~/.zshrc或~/.bashrc文件末尾是否确实添加了source行。~/.aegis/ipc_port文件是否存在,并且其中的端口号是否与扩展输出面板中显示的IPC端口一致。- 扩展的IPC服务器是否正在运行(查看Audit输出面板)。
- 尝试完全关闭所有终端窗口,重新打开一个。
4. 深入核心:策略引擎、密码学与Shell Hook
4.1 策略引擎:如何判断一个命令的“善恶”?
策略引擎是AegisAI的“大脑”,它决定了哪些命令可以畅行无阻,哪些必须请示。其核心是一组精心设计的正则表达式规则和一套评估逻辑。
规则分类与示例: 策略引擎通常将命令分为几个风险等级,并对应不同的正则规则:
// 示例规则定义 (policy.ts 中可能的结构) const riskRules = { LOW: [ /^ls(\s+.*)?$/, // 列表,只读 /^git\s+status(\s+.*)?$/, // Git状态,只读 /^pwd$/, // 打印工作目录 /^echo\s+".*"$/, // 简单的echo(需注意命令注入) ], MEDIUM: [ /^git\s+(add|commit|push|pull)(\s+.*)?$/, // Git写操作 /^mkdir\s+[\w\/\-\.]+$/, // 创建目录 /^cp\s+[\w\/\-\.]+\s+[\w\/\-\.]+$/, // 复制文件 ], HIGH: [ /^sudo\s+.*$/, // 任何sudo命令 /^chmod\s+[0-7]+\s+.*$/, // 修改权限 ], CRITICAL: [ /^rm\s+-rf\s+.*$/, // 递归强制删除 /^dd\s+.*$/, // 磁盘操作 /^.*\s*>\s*\/dev\/sd[a-z].*$/, // 输出重定向到磁盘 ] };评估流程:
- 命令规范化:去除多余空格,处理变量替换(基础处理)。
- 规则匹配:从高到低(CRITICAL -> LOW)遍历规则集。一旦匹配,则确定风险等级。
- 模式决策:根据用户设置的安全模式决定行为。
- 严格模式:只有明确在白名单(可自定义)中的命令才自动放行,其余一律审批。
- 均衡模式:LOW风险自动放行,MEDIUM及以上需要审批。
- 宽松模式:LOW和MEDIUM自动放行,HIGH和CRITICAL需要审批。
- 上下文增强:未来可扩展,结合当前工作目录、项目类型(如是否是生产环境仓库)来动态调整风险。
注意事项:正则表达式规则需要谨慎设计,避免误判和绕过。例如,
rm -rf ./node_modules和rm -rf /风险天差地别,但简单的rm\s+-rf规则会将其都归为CRITICAL。更高级的策略可以结合路径分析。在初期,宁可错杀(多审批),不可放过(漏掉高危命令)。
4.2 密码学签名:Ed25519实战解析
AegisAI使用Ed25519算法进行签名,这里我们深入看一下在代码中是如何实现的。我们使用一个流行的、经过审计的库tweetnacl(或@noble/ed25519)。
扩展端签名请求:
import nacl from 'tweetnacl'; import { encodeBase64, decodeBase64 } from './utils'; // 假设的编解码工具 // 1. 生成或加载密钥对 (通常在扩展激活时进行) let keyPair: nacl.SignKeyPair; const keyPath = path.join(context.globalStorageUri.fsPath, 'device_key'); if (fs.existsSync(keyPath)) { const saved = JSON.parse(fs.readFileSync(keyPath, 'utf-8')); keyPair = { publicKey: decodeBase64(saved.publicKey), secretKey: decodeBase64(saved.secretKey), }; } else { keyPair = nacl.sign.keyPair(); fs.writeFileSync(keyPath, JSON.stringify({ publicKey: encodeBase64(keyPair.publicKey), secretKey: encodeBase64(keyPair.secretKey), })); } // 2. 构造待签名的消息 const message = `${command}|${timestamp}|${requestId}`; // 使用分隔符防止歧义 const messageBytes = new TextEncoder().encode(message); // 3. 使用私钥签名 const signature = nacl.sign.detached(messageBytes, keyPair.secretKey); // 4. 将签名(Base64编码)放入请求对象 const authRequest: AuthRequest = { requestId, command, timestamp, deviceId: 'dev-1', description, riskLevel, signature: encodeBase64(signature), // 附加签名 };后端验证签名:
// 收到请求后,从Redis中取出该deviceId对应的扩展公钥 const session = await redisClient.hGetAll(`session:${request.deviceId}`); const extensionPublicKey = decodeBase64(session.extensionPublicKey); // 重构待验证的消息(必须与扩展端构造方式完全一致!) const message = `${request.command}|${request.timestamp}|${request.requestId}`; const messageBytes = new TextEncoder().encode(message); const signatureBytes = decodeBase64(request.signature); // 验证签名 const isValid = nacl.sign.detached.verify(messageBytes, signatureBytes, extensionPublicKey); if (!isValid) { console.error('Invalid request signature, dropping.'); socket.emit('error', { message: 'Invalid signature' }); return; // 丢弃非法请求 } // 验证通过,转发给移动端移动端签名决策:流程类似,只是签名的消息是requestId和approved状态的组合(例如requestId|true),并使用移动端的私钥签名。后端再用移动端的公钥验证。
关键点:
- 密钥管理:私钥必须妥善存储在本地(如VS Code的全局存储路径、手机的Secure Storage),绝不能通过网络传输或硬编码。
- 消息构造:双方构造消息的格式必须严格一致,否则验证必然失败。使用明确的分隔符(如管道符
|)可以避免歧义。 - 签名是“分离的”:我们使用
detached签名,只生成签名值,而不是将签名和消息打包在一起。这更灵活,符合我们的协议设计。
4.3 Shell Preexec Hook:深入原理与实现
为什么需要这个Hook?因为VS Code的终端APIonDidStartTerminalShellExecution是在命令已经开始执行后才触发事件。对于需要人工审批的场景,这太迟了。Shell Preexec Hook利用了Shell自身的机制,在命令被按下回车、Shell真正开始解释和执行它之前,将其截获。
Zsh的实现(更优雅): Zsh有一个强大的功能叫“Zsh Line Editor (ZLE) Widgets”。我们可以覆盖默认的accept-linewidget(它负责处理回车键)。
# ~/.aegis/shell_hook.sh 核心部分 (Zsh) function __aegis_preexec() { local cmd="$1" # 1. 读取扩展IPC服务器端口 local IPC_PORT if [[ -f ~/.aegis/ipc_port ]]; then IPC_PORT=$(cat ~/.aegis/ipc_port) else # IPC服务器未运行,直接放行(或可以选择阻止?这里采用放行) zle .accept-line return fi # 2. 向本地IPC服务器发送命令进行评估 local response response=$(curl -s -X POST -H "Content-Type: application/json" \ -d "{\"command\":\"$cmd\"}" \ "http://127.0.0.1:$IPC_PORT/preexec" 2>/dev/null) # 3. 解析响应 local approved=$(echo $response | grep -o '"approved":[^,}]*' | cut -d':' -f2 | tr -d ' "') if [[ "$approved" == "true" ]]; then # 批准,允许命令继续执行 zle .accept-line else # 拒绝或出错,取消当前命令行的执行 echo -e "\nCommand blocked by AegisAI." zle .send-break # 这相当于按下了 Ctrl+C,取消当前输入行 fi } # 将我们的函数注册为 accept-line widget 的前置钩子 autoload -Uz add-zle-hook-widget add-zle-hook-widget line-init __aegis_preexec # 注意:更精确的做法是包装 accept-line widget,但上述方法演示了概念。Bash的实现(利用DEBUG陷阱): Bash没有ZLE,但它有trap DEBUG机制。这个陷阱在每个简单命令执行前、以及for、case、select等语句的每个命令执行前都会触发。
# ~/.aegis/shell_hook.sh (Bash版本) function __aegis_preexec() { # 在DEBUG陷阱中,$BASH_COMMAND 保存了即将执行的命令 local cmd="$BASH_COMMAND" # 跳过我们自己的curl命令,避免无限递归 if [[ "$cmd" == curl* ]]; then return 0 fi local IPC_PORT if [[ -f ~/.aegis/ipc_port ]]; then IPC_PORT=$(cat ~/.aegis/ipc_port) else return 0 # IPC未运行,允许执行 fi local response response=$(curl -s -X POST -H "Content-Type: application/json" \ -d "{\"command\":\"$cmd\"}" \ "http://127.0.0.1:$IPC_PORT/preexec" 2>/dev/null) local approved=$(echo $response | grep -o '"approved":[^,}]*' | cut -d':' -f2 | tr -d ' "') if [[ "$approved" != "true" ]]; then echo -e "\nCommand blocked by AegisAI: $cmd" return 1 # 返回非零值会取消命令执行 fi return 0 } # 设置DEBUG陷阱,并启用 extdebug 选项以允许陷阱返回非零值来取消命令 shopt -s extdebug trap '__aegis_preexec' DEBUG重要警告:Bash的DEBUG陷阱非常“活跃”,对性能有影响,且在一些复杂脚本中可能行为诡异。因此,AegisAI官方更推荐在Zsh环境下使用,体验更稳定可靠。如果你主要用Bash,需要充分测试。
IPC服务器 (ipc_server.ts): 这个本地HTTP服务器是Hook与扩展通信的桥梁。它接收Hook发来的命令,调用扩展内同样的策略引擎进行评估,如果需要审批,则通过WebSocket向后端发起完整的授权流程;如果自动批准,则直接返回{approved: true}。它运行在本地回环地址,避免了网络暴露风险。
5. 测试、调试与故障排除实战
系统搭建好了,但怎么知道它真的在工作?出了问题怎么排查?这一章全是实战干货。
5.1 端到端功能测试流程
- 启动所有服务:确保后端、扩展开发宿主、手机App都已成功运行并显示连接正常。
- 测试自动放行:
- 在扩展开发宿主的终端里,输入
ls -la。 - 预期:命令立即执行,输出文件列表。同时,手机App上可能会收到一条“通知”类日志(如果配置了可见性日志),显示一条低风险命令被自动放行。
- 在扩展开发宿主的终端里,输入
- 测试人工审批:
- 在同一个终端,输入
sudo echo "test"。 - 预期:终端挂起,显示
Evaluating command...。1-2秒内,手机App弹出授权卡片,显示命令详情和“HIGH”风险。 - 在手机上点击“批准”。
- 预期:终端恢复,执行命令并输出
test。VS Code的AegisAI Audit输出面板会显示完整的请求和决策日志。 - 再次输入
sudo echo "test2",这次在手机上点击“拒绝”。 - 预期:终端显示
Command blocked by AegisAI.或类似错误信息,命令未执行。
- 在同一个终端,输入
- 测试AI命令拦截:
- 在扩展开发宿主的VS Code中,打开命令面板 (
Ctrl+Shift+P),运行“AegisAI: Execute Command (AI)”。 - 在弹出的输入框中,输入
rm -rf /tmp/dummy_folder(确保这个文件夹不存在或可删除)。 - 预期:流程同上,触发手机审批。这模拟了AI通过
secure.executeCommandAPI调用命令的场景。
- 在扩展开发宿主的VS Code中,打开命令面板 (
5.2 分层调试与问题定位
当流程不按预期工作时,需要像剥洋葱一样,一层层排查。
第一层:检查基础连接
- 后端日志:查看运行
node src/server.js的终端,是否有Extension connected和Mobile connected日志?如果没有,说明连接未建立。 - 扩展日志:在扩展开发宿主的“输出”面板,选择“AegisAI Audit”。检查是否有连接错误、注册错误。
- 手机App界面:是否显示“已连接”?
第二层:检查Redis状态Redis是会话存储的中心,出问题会导致消息无法路由。
redis-cli 127.0.0.1:6379> KEYS * # 应该看到类似 `session:dev-1` 的键。 127.0.0.1:6379> HGETALL session:dev-1 # 应该看到 `extensionSocketId`, `mobileSocketId` 等字段都有值。 # 如果 mobileSocketId 为空,说明手机没连上或连接已断开。第三层:模拟测试与网络抓包如果怀疑网络或协议问题,可以用简单的脚本模拟客户端。
- 测试后端Socket.io接口:使用前面“Backend Testing”部分提供的
test-client.js脚本。它能帮你确认后端是否能正常接收和响应事件。 - 检查IPC通信:Shell Hook通过HTTP与扩展的IPC服务器通信。你可以手动测试:
观察返回的JSON是否包含# 先获取IPC端口 PORT=$(cat ~/.aegis/ipc_port) # 发送一个测试命令 curl -v -X POST http://127.0.0.1:$PORT/preexec \ -H "Content-Type: application/json" \ -d '{"command":"ls"}'approved字段。
第四层:深入代码与日志
- 在扩展中打日志:在
extension.ts的activate函数、命令处理函数、Socket事件回调中加入console.log,然后在原始VS Code窗口(不是扩展开发宿主)的“调试控制台” (Ctrl+Shift+Y) 查看输出。 - 使用VS Code调试器:在
extension/src/的TypeScript文件中设置断点,按F5启动调试。当触发命令时,执行会在断点处暂停,你可以查看所有变量值,单步执行。 - 检查签名:在扩展和后端的签名/验证逻辑处添加详细日志,打印出待签名的消息原文、生成的签名、用于验证的公钥等,确保两端完全一致。
5.3 常见问题速查表
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 手机App显示“未连接” | 1. 后端服务未运行。 2. 手机与电脑不在同一局域网。 3. serverUrlIP地址错误或端口被防火墙阻挡。 | 1. 检查后端终端是否在运行。 2. 在电脑上 ping手机的IP,在手机上用浏览器访问http://<电脑IP>:3000看是否能通。3. 确认 mobile/lib/main.dart中的IP正确。 |
| 终端命令未被拦截,直接执行 | 1. Shell Hook未安装或未生效。 2. IPC服务器未运行。 3. Hook脚本有错误。 | 1. 检查~/.zshrc是否有source ~/.aegis/shell_hook.sh,并执行source。2. 检查扩展Audit日志,确认IPC服务器已启动并打印了端口。 3. 直接执行 source ~/.aegis/shell_hook.sh看是否有报错。 |
| 手机收到请求,但点击批准/拒绝后终端无反应 | 1. 扩展与后端WebSocket连接已断开。 2. 决策消息路由失败(Redis中session信息不一致)。 3. 签名验证失败。 | 1. 查看扩展和后端日志,有无断开或错误信息。 2. 在Redis中检查 session:dev-1,对比extensionSocketId是否与当前扩展连接ID匹配。3. 检查后端日志,看是否有“Invalid signature”错误。 |
| 执行命令后,VS Code输出面板报“Timeout”错误 | 1. 手机App未在60秒超时时间内响应。 2. 后端在处理请求时卡住。 | 1. 确保手机App在前台运行,网络通畅。 2. 检查后端CPU/内存是否正常,Redis是否响应缓慢。 |
| 安装扩展时编译失败 | 1. Node.js版本过低。 2. TypeScript编译错误。 3. 依赖包下载失败。 | 1. 使用node --version确认版本 >= 20。2. 在 extension/目录下运行npm run compile查看具体错误。3. 删除 node_modules和package-lock.json,重新npm install。 |
5.4 性能调优与生产环境考量
目前AegisAI是MVP(最小可行产品),专注于功能实现。但在个人高频使用或团队部署前,需要考虑以下几点:
延迟优化:目标审批延迟(从命令触发到手机收到通知)应小于1秒。瓶颈可能在:
- 网络:确保后端服务部署在低延迟的网络环境中。对于个人使用,localhost就是最佳选择。
- Redis:使用本地Redis,避免网络往返。对于大量审计日志,考虑异步写入。
- 移动端通知:Flutter App需保持活跃的Socket连接,避免被系统休眠。
可靠性增强:
- WebSocket重连:目前实现中,Socket.io客户端自带重连机制,但需要处理重连后的会话恢复(重新发送
extension_connect事件)。 - 请求去重:为防止网络波动导致重复请求,后端可对短时间内相同
requestId的请求进行去重。 - 离线队列:考虑在扩展端实现一个简单的待审批命令队列,当网络恢复后自动重新发送。但需注意安全,队列中的命令不应在无授权下执行。
- WebSocket重连:目前实现中,Socket.io客户端自带重连机制,但需要处理重连后的会话恢复(重新发送
安全加固:
- 密钥存储:当前将私钥以Base64形式存储在文件系统中。对于更高安全要求,应使用平台提供的安全存储(如VS Code的
SecretStorageAPI,iOS的Keychain,Android的Keystore)。 - IPC服务器认证:Shell Hook与IPC服务器的通信目前是简单的HTTP。可增加一个简单的共享密钥认证,防止本地其他进程恶意发送批准指令。
- 审计日志保护:本地JSON日志文件应设置为仅当前用户可读,或进行加密。
- 密钥存储:当前将私钥以Base64形式存储在文件系统中。对于更高安全要求,应使用平台提供的安全存储(如VS Code的
配置化与扩展性:
- 规则自定义:允许用户通过JSON或UI界面自定义风险规则,适应不同工作流。
- 多设备支持:当前是1对1配对。可扩展为1个编辑器对多个审批设备(如手机+平板),需要设计多设备通知和“首次响应生效”或“多签”逻辑。
- 与CI/CD集成:可以开发一个命令行工具,在自动化脚本中调用,实现对CI流水线中敏感操作的人工审批门控。
6. 扩展思路与未来演进
AegisAI作为一个开源项目,其MVP版本已经搭建了一个坚实且安全的核心框架。但它的潜力远不止于此。基于现有的架构,我们可以从多个方向进行深化和扩展,使其更智能、更强大、更贴合复杂的生产环境。
6.1 智能化策略引擎:从规则到上下文感知
当前的策略引擎基于正则表达式,虽然有效,但略显笨拙。下一步是引入更丰富的上下文信息进行动态风险评估。
- 项目上下文感知:扩展可以读取当前工作区的
.git/config或项目根目录的特定配置文件(如.aegisrc),来判断项目类型。例如,在标记为“生产环境”的Git仓库中,所有git push命令的风险等级自动提升至CRITICAL;而在个人玩具项目中,规则可以更宽松。 - 命令语义分析:结合简单的自然语言处理或语法树分析,而不仅仅是字符串匹配。例如,识别
rm -rf ./node_modules和rm -rf /home/user/projects的区别,前者可能是安全的构建清理,后者则危险得多。可以结合路径白名单/黑名单。 - 学习用户习惯:在获得用户明确许可的前提下,可以记录用户的审批决策。如果用户多次批准了
rm -rf ./dist且未发生问题,系统可以逐渐将其风险等级从HIGH降为MEDIUM,甚至在学习期后建议加入自动批准白名单。
6.2 审批工作流的强化
- 多级审批与超时升级:对于最高风险的操作(如
sudo操作生产数据库),可以设置为需要两次批准(例如,手机App批准后,还需要在电脑上二次确认)。同时,引入超时升级机制:如果主要审批人未在指定时间内响应,请求会自动转发给预设的备用审批人。 - 审批理由与审计增强:要求用户在批准或拒绝时必须输入简短理由(如“确认是清理缓存”、“路径有误”)。这些理由连同完整的命令上下文(工作目录、环境变量、触发该命令的AI对话片段)一并存入不可篡改的审计日志,便于事后追溯和复盘。
- 批量命令审批:AI有时会生成一系列连续命令(如
git add . && git commit -m "fix" && git push)。系统可以将其作为一个“命令组”一次性呈现在审批界面,允许用户批准全部、拒绝全部或选择性批准。
6.3 部署模式与高可用
- 个人云部署:将后端服务(Node.js + Redis)部署在个人的家庭服务器、NAS或轻量云主机(如Raspberry Pi, AWS Lightsail)上。这样,你可以在公司电脑、家庭电脑、笔记本电脑上使用同一个VS Code扩展,全部连接到你的私有审批中心,由同一个手机App控制。这需要解决动态IP、内网穿透和更严格的身份认证问题。
- 团队/企业版:这是更具商业潜力的方向。架构需要升级为多租户,包含团队管理、角色权限(RBAC)、集中策略管理、统一的审计仪表盘。后端需要改用PostgreSQL等关系型数据库,并可能集成企业单点登录(SSO)。审批流程可以配置为“同级评审”或“主管审批”等模式。
6.4 生态集成与用户体验
- 支持更多编辑器/IDE:核心的“拦截-评估-通信”逻辑可以抽象为一个独立的语言服务器(Language Server)或通用守护进程。然后为VS Code、Cursor、JetBrains全家桶、甚至Neovim开发轻量级客户端插件,它们都连接到同一个核心服务。
- 移动端体验优化:Flutter App可以增加Widget或快捷通知,支持Apple Watch等可穿戴设备审批。甚至可以开发一个MacOS/Windows的桌面通知组件,在电脑端直接进行二次确认,减少掏手机的次数。
- 与AI助手深度集成:最理想的体验是“无缝”。例如,当Cursor AI建议一个命令时,其UI按钮直接集成AegisAI的审批状态。用户点击“运行”后,命令自动进入审批流程,AI界面显示“等待用户批准…”,批准后自动显示结果。这需要与AI助手的插件API进行更深度的合作。
在我实际使用和测试AegisAI的几周里,最大的体会是:安全与效率的平衡是一门艺术,而非科学。最初,我将策略设为“严格模式”,几乎每个git commit都要掏手机,几天后就感到烦躁。切换到“均衡模式”后,体验流畅了许多,只有在面对rm、sudo、chmod这些“大杀器”时,系统才会打断我。
这个项目给我带来的,不仅仅是一道安全屏障,更是一种心理上的“减速带”。每次手机震动,要求我审批一个命令时,我都会下意识地停顿一秒,再看一眼屏幕上的命令。就是这一秒钟的停顿,无数次让我发现了AI理解上的细微偏差,或是自己提示词中的模糊之处。它强迫我,作为人类,在自动化的洪流中保持最终的掌控权和责任感。这,或许就是人机协同中最宝贵的一环。