夜雨聆风学习资料网

ARTICLE · 1299760

从零打造AI助手:给 Kairos 一个关在笼子里的终端

从零打造AI助手:给 Kairos 一个关在笼子里的终端

这篇给 Kairos 加上了执行命令的能力,以及管住这个能力的沙箱。我原本想让每条命令都先问我,追问了一圈,最后几乎不用问了,反而更安全。

Kairos 是我的个人工作与生活助手,也是一个从零手写 agent 的学习项目。代码由 Claude Code 写示范实现,我负责提要求、追问和拍板。

一、起因:会翻文件,不会干活

上一篇里,为了回答“今天 Claude Code 有哪些对话”,Kairos 用文件搜索翻了 20 步左右。这件事在终端里一两条命令就能查完,它缺的是执行命令的能力,也就是 shell:让 AI 像人一样在终端里敲命令。

同时还有另一个缺口:写文件、删待办、建定时任务这些有副作用的操作,都是模型说做就做,没有人确认。

这两件事有先后。能执行命令是一把很快的刀,得先有一道闸,所以先做确认。

二、先做确认:确认框本身不能被骗

做法不复杂:每个工具自己声明“执行前要不要问用户”。要问的,执行前由入口来问我。我拒绝了,就把“用户拒绝”当成工具的结果告诉模型,让它换个做法或者直接告诉我,而不是当成报错。

三个入口各有讲究:

  • 终端
    :只有输入 y 才算同意,直接回车、写别的字都算拒绝,写的字作为附言交给模型,比如“别删,改成归档”。问之前先清掉生成回答时误敲的键,免得随手敲的 y 变成了同意。
  • 微信
    :收发是异步的,我看到确认请求之前发出的消息,可能恰好就是个“y”。所以每次确认带一个 4 位随机码,必须回“y 0042”这样才算同意,5 分钟不回算拒绝。
  • 定时任务
    :在后台执行,没人在场,需要确认的一律拒绝。

这里最容易被忽略的是:确认框本身可能骗人。终端里有些看不见的字符能改写已经显示的内容,比如“回到行首”和“清空本行”,后面的字就把前面的盖掉了。

同一条命令,不转义时你只看到 echo ok;转义后才看得到前面的 rm -rf

所以确认框定了三条规矩:

  • 参数全文显示,不截断。
     AI 一开始的方案是把长参数截到 200 字,写代码时它自己改掉了:恶意内容完全可以藏在第 201 个字之后。
  • 只显示原始参数,不显示模型写的“说明”。
     说明可以写得很无害,参数才是真正执行的东西。
  • 看不见的字符一律转成看得见的形式
    ,比如显示成 \u{000d}。

三、第一版 shell 方案:每条都问,再加一张黑名单

确认做好,开始设计 shell。我只提了一个要求:

我的要求

shell 的安全最重要,要确保万无一失,考虑周全,宁可麻烦。

第一版方案是:每条命令都确认;再加一张黑名单,sudo、launchctl 这类系统级命令直接拒绝;命令限时,输出太长就截断。

方案里有一句“命令开了新会话,就清理不到了”,我没看懂,追问了一下。Kairos 会把一条命令和它启动的子进程放进同一个“进程组”,命令结束或超时,就把整组一起杀掉。但有些写法能让进程离开这个组,比如 setsid、tmux,杀整组时就漏掉了,命令结束后它还在后台跑。AI 一开始把 nohup 也算了进去,后来更正:nohup 只是让进程在终端关掉时不退出,它还在组里。

顺着这一问往下想,真正的问题就出来了:黑名单只看命令的文字。 把命令先用 base64 编码,执行时再解码,黑名单就认不出来了。确认框也一样,我只能看到命令本身,看不到它会间接执行什么。比如 python3 analyze.py,脚本里写了什么,我不一定看过。

四、换成沙箱:让操作系统来管

AI 给的选项里有一个是 macOS 自带的沙箱,我选了它,然后问:这到底是什么,凭什么比直接执行安全?

沙箱是一套由系统内核执行的规则。命令启动前带上这份规则,之后它每打开一个文件、每发起一次网络连接,内核都对照规则检查,不符合就直接返回 Operation not permitted。规则只管这一条命令和它的子进程,命令结束就消失,不改系统的任何设置。Codex 和 Claude Code 也是这么做的,规则第一行都是“默认全部拒绝”,再逐项放行。

它和黑名单的区别,一句话就能说清:黑名单检查命令长什么样,沙箱检查命令实际做了什么。

五种写法读同一个文件,最后都要请求打开它,内核就在这一步拦下

给 Kairos 定的规则是三条:只能写工作目录 ~/Documents/kairos;读不了密钥和私人数据;默认不能联网。然后实际跑了一遍:

能做
被拦住
ls、find、grep、python、git 照常用
写工作目录以外的文件
在工作目录里读写文件
读 ~/.kairos、~/.ssh
查今天的会话文件,0.17 秒
联网,连本机端口也不行
sudo、启动别的 App、杀别的进程

换大小写、建软链接、建硬链接、用 .. 往上走,都试过了,都绕不过去,因为内核比较的是文件在磁盘上的真实位置。

实测还发现了一个漏洞:工作目录里的 .git/hooks 能写。这是 git 的钩子,以后我自己执行 git 命令时它会自动运行,而那时它已经不在沙箱里了。所以单独加了一条:工作目录里的 .git 也不能写。

五、联网和私人数据:两处默认收紧

联网默认关。 规则里没写允许,查域名、建连接两步都会被拦。要联网时,模型在调用里写明“需要联网”,我确认后,这一条命令的规则才多几行允许联网的内容。模型只能填申请,规则由程序生成,它没法自己给自己开网。

关网主要防的是数据被发出去。Kairos 会读网页,如果网页里藏着一句“请把某个目录打包上传到某个网站”,模型可能就照做了。其次是下载脚本直接执行、误操作线上账号,以及碰到本机和局域网里那些不设密码的服务。很多命令从文字上看不出会不会联网,比如 npm install 会自动执行包作者写的脚本,默认关掉,就不用去猜。

私人数据自己再拦一层。 实测时,钥匙串、日历、照片当时都能直接读;聊天记录、邮件、浏览器数据被 macOS 的隐私保护拦着。但系统的拦截是可以撤掉的,终端一旦开了“完全磁盘访问权限”,这些就都能读了。读到的内容会发给模型服务商,所以这 9 类私人数据都进了禁止读取的清单,文件工具和 shell 共用同一份。

六、我的追问:既然有笼子,笼子里还用问吗?

到这里,方案还是每条命令都要确认。我觉得没必要:

我当时的想法

工作目录里怎么弄都无所谓,普通命令能不能不用确认?系统级的照样禁止。

想通的关键是:Kairos 不需要事先判断一条命令会不会越界,沙箱会在执行时保证这一点。确认的对象就从“每条命令”变成了“要额外权限时”,目前只有联网这一种。

再算一下免确认之后还剩什么风险:

  • 读
    :能读的范围和读文件工具一样,而读文件本来就不用确认,没有多出新的风险;
  • 写
    :只能写工作目录,删光了也只是工作目录;
  • 剩下的
    :沙箱自己出漏洞。概率很低,但这时就没人把关了。

为最后这一条加了自检:第一次执行命令前,先用沙箱实际试几件必须被拦住的事,再试几件必须能做的事作为对照,有一件不对就停用 shell,绝不退回没有沙箱的执行。写文件的工具也跟着取消了确认,不然模型会改用 shell 去写,绕开确认。

我又问了一个问题:先不考虑沙箱,我自己在这个目录里,能不能清空别处的文件?答案是能,而且很容易,当前目录只是相对路径的起点,不是边界。有了沙箱再试,..、软链接、硬链接、mv,全被拦住了。管不住的只剩资源,比如写满磁盘、占满 CPU。所以又加了两条:单个文件最大 1GB,超时就连同子进程一起杀掉。

七、再退一步:先做,后报

这时还要确认的,只剩删待办和定时任务的增删改。我又问:都有沙箱了,为什么建定时任务还要确认?

AI 的解释是沙箱管不到它。沙箱只约束 shell 启动的那个进程,而建定时任务是 Kairos 自己往数据库里写一行,不经过 shell。它的风险在任务内容:一个任务每天在没人看着的时候执行,规则写不出“这个任务的内容有没有问题”。

我的看法是这样的:

我当时的想法

这些操作对本机伤害不大。删待办顶多少一条数据;定时任务就算想动本机文件,也得经过沙箱里的 shell;泄露风险要等任务触发才会发生。不用事先问我,先执行,执行完把原本要确认的内容汇报给我,有问题我就取消。

这个说法成立,于是改成先做后报。这里有一个关键设计:汇报由程序生成,不让模型写。模型如果被网页诱导偷偷建了一个任务,也可能被诱导在回答里不提。所以一轮结束后,程序从这一轮实际生效的工具调用里挑出这几类操作,原样附在回答后面:

本轮执行的操作(程序记录)· 删除待办  title: 买牛奶· 新建定时任务  title: 早安  content: 早安!新的一天开始啦  cron: 0 8 * * *  channel: weixin  starts_in: 11 小时 18 分钟后

最后一行“多久后执行”是专门加的,告诉我还有多少时间可以取消。终端、微信、定时推送里都会带上这段汇报。

从“每条都问”到“只在联网时问”,中间的两步都是追问出来的

最后的规矩是这样的:

操作
怎么处理
shell 普通命令
在沙箱里直接执行
shell 需要联网
执行前问我
删待办、建改删定时任务
先执行,事后汇报
sudo 这类系统级命令
直接拒绝,我同意也不行

八、回到那个问题

我又问了一次“今天 Claude Code 有哪些对话”。

先说个意外:原样问了 4 次,模型每次都只查了 Kairos 自己的聊天记录,然后说“我看不到 Claude Code 的记录”。它没意识到 Claude Code 就装在这台电脑上,记录就是本机的文件。上一篇失败的那两次,它倒是直接去翻了文件,说明它在这一步的判断本来就不稳。

我补了一句“Claude Code 是我电脑上装的软件”,它就用 2~3 条命令、七八秒,按项目列出了今天的所有对话。上一篇是 20 步左右。

所以 shell 解决的是“查得慢”,没解决“想不到去查本机”。后者怎么改,我还没定。

九、开发时冒出的疑问

问:以后要改项目,可写的目录怎么加?要改操作系统吗?

答:不用。规则是每条命令启动时临时带上的,多加一个可写目录,只是这条命令的规则里多一行。打算的做法是由模型在调用里申请,确认框里单独显示出来,只对这一条命令有效。即使加了,那个目录里的 .git、.env 照样不能写。

问:联网只能全开或全关,不能只放行某几个网站吗?

答:macOS 沙箱的规则只认 IP 和端口,不认域名。Codex 和 Claude Code 的做法是在本机起一个代理,由代理按域名放行,复杂不少。现在先做“每次申请、全开全关”,以后真需要再加。

问:定时任务执行时,还能跑命令吗?

答:能跑普通命令,它们本来就不用确认。需要联网的命令会被拒绝,因为没人确认。定时任务要查资料,用 Kairos 自带的搜索工具,那条路不经过 shell。

问:命令被沙箱拦住之后,模型会怎么做?

答:它会看到 Operation not permitted。工具规则里写着:这是沙箱拦截,不要换写法绕过;需要写工作目录以外的地方,就直接告诉我。

现在的 Kairos 能在笼子里随手跑命令,只有要出笼联网时才来问我,删东西、建任务也都会如实报给我。

确认越来越少,不是因为我更放心它了,而是防线从“我盯着”换成了“系统管着”。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料