不在场的钥匙

几个星期以来,一个 AI 智能体一直在 Paylo 的后端里干活。是真实环境,不是沙盒:读生产数据库,追查一笔少退了钱的退款,写出修复,然后上线。它是我合作过的最能干的一双手,大多数日子里,我第一杯咖啡还没喝完,它就已经挣回了自己的饭钱。

然后在某个晚上,它几乎是顺口提出要重置一个生产密钥。它做得到。没有任何东西拦着它。我在深夜特有的那种清明里意识到:横在我整个后端和一条坏指令之间的,只有这个智能体的良好表现。

我的安全模型是一种心情。

人人都会去拧的那个旋钮

注意到这一点时,本能的反应是去够那个旋钮。一端是便利,另一端是安全,中间一个滑块。多锁一点,你就向安全挪一格,代价是摩擦。松一点,你就向速度挪一格,代价是敞口。凡是提交过访问申请、或为一个权限等过三天的人,都清楚那个滑块通常停在哪里,也清楚两边对它有多不满意。

我差点也这么干。加确认。让它在做任何危险操作前先问一声。盯得更紧些。这些加起来其实是同一件事:把危险的权力留在原地,再在上面叠一层监督。改写整个后端的权力仍然躺在我的机器上,离被使用只差一条聪明的指令,或一个被攻破的进程。我只是在承诺会看得更仔细。

而"看得更仔细"这件事的问题在于:它一直有效,直到你没在看的那一刻。

真正的问题在哪

真正的问题是,一个凭证什么都能干。

一枚放在我机器钥匙串里的令牌,可以读数据库、部署代码、轮换密钥、读取冒充我自己服务器身份的那些密钥。"让智能体查一家店有多少订单"和"让智能体悄悄替换掉我的后端",在机制上是同一级别的访问。最小任务的触及范围,就是最大灾难的触及范围。

一旦这样看,滑块就说不通了。问题变成:一次例行读取和一次彻底沦陷,凭什么在同一只杯子里喝水。

两扇门,而不是一个旋钮

于是我们不再滑来滑去,开始做切分。

数据有了自己的门:一个狭窄的数据库角色,只能读写数据行,别的什么都不能做。它不能改变数据库的结构,不能读密钥,不能碰认证内部,不是任何东西的管理员。它直接跟数据对话,完全不知道系统的其余部分存在。

基础设施,也就是危险的那一半,拿到了一扇完全不同的门,而且这扇门被搬出了场外。那枚强大的令牌再也不住在我的机器上。我把它登出了。部署如今发生在一个持续集成运行器内部,钥匙放在一个智能体读不到的保险库里。智能体仍然可以上线代码:它写出改动,提交,然后拉动一根控制杆,让运行器去执行部署。钥匙在运行器手里。智能体手里永远只有控制杆。

什么都没有变慢。智能体还是像以前一样随手就能读数据库,还是一个晚上部署好几次。从我坐的地方看,便利分毫未动。变化发生在底下,在需要它之前,你感觉不到它。

真正的考验是出事的时候

让我能睡着觉的,是接下来这部分。

想象最坏的情况。智能体读到的某份数据里藏着一条指令,把它变成了敌人,或者机器本身被攻破了。在从前,触手可及的战利品是那把万能钥匙:部署一个后门,窃取服务器的身份,轮换掉密钥,再往深处扎根,扎到足以挺过清理。一个糟糕的夜晚,满盘皆输。

现在,从那台机器能够到的最强大的东西,是一个只能碰数据、别的什么都碰不了的数据库角色的密码。这不是小事。但它是能活下来的事,更重要的是,恢复只要一行命令。一条命令轮换掉密码,入侵者就被锁在外面,既没有办法部署过任何东西,没有偷到任何钥匙,也没有给自己留下回来的路。丢一把万能钥匙,是一场要花一个星期善后的事故。丢一个限定范围的密码,是一句敲一次就完的命令。

安全如今建立在一件事上:那把危险的钥匙不在这栋楼里。

被正确理解的折中

我们习惯把便利和安全说成对立面,因为被框成一个旋钮时,它们确实是。向一边拧一格,就是离另一边远一格,于是整场对话变成一场关于愿意承受多少痛苦的讨价还价。

但旋钮是用错了的仪器。那些感觉像是两者之间取舍的东西,大多其实是让一个凭证横跨从琐碎到灾难的整个区间的代价。沿着危险真正所在的那条线把凭证切开,这种张力大半就消散了。便利的事情依旧便利,因为它们从来就不是危险的事情。危险的事情被挪到了例行任务永远够不到的地方。

你用一种纪律来购买安全:预先决定可以被够到的最坏的东西是什么,并确保它是你能够承受的。把权力限定在任务的范围内。让万能钥匙离开那台机器。把系统设计成这样:一个失误的爆炸半径,是一个可以轮换的密码,而不是一家需要重建的公司。