我用“氛围编程”亲手编出了一个安全漏洞
原文:https://every.to/working-overtime/i-vibe-coded-a-security-risk
“这个功能上线了,哈哈。”
我从没想过有一天自己会打出这几个字,更不用说是在说自己做的东西。那个“哈哈”完全是我这个千禧一代发自内心的、紧张到不行的本能反应。我根本笑不出来。正在审查我代码的AI智能体也一样。它刚刚告诉我,我做进应用里的那个功能必须立刻下线,因为我们要搞清楚它是否暴露了一个安全问题。
大家好,我是一个“氛围编程”新手。你可能想知道我是怎么走到这一步的。答案是一锅浓稠的乱炖:想象力和无知、狂妄自大,还有——怎么可能少得了它——AI。
今年一月,当Claude Opus 4.5 让所有人目瞪口呆,智能体原生架构(agent-native architecture)开始作为一种构建软件的新范式崭露头角时,我萌生了“氛围编程”的冲动:如果我来做一个专门直接跟AI智能体协作的应用,会怎么样?
读者朋友们,我干了。我的应用Tastemaker可以让你从自己喜欢的文字里收集片段,描述你喜欢它们什么地方,然后根据其中的模式生成一份风格指南。在它看起来能跑通之后,我怀着幼儿园小朋友把自己手指画拿回家给妈妈贴冰箱的那股骄傲劲儿,买了个域名,把它发布到了网上。
应用真的能用。人们能用它。令我震惊的是,真的有一小撮人在用。它成了一座虽小但鲜活的纪念碑,见证我身兼多职的能力:证明了我是一个手里握着真东西的“写作者-创造者”,而不只是一个满脑子产品想法的写作者。
然后,就像那个被独自留下、守着自己作品和一瓶没盖盖的颜料的幼儿园小朋友一样,我把整件事都给毁了。
OpenAI的研究员们给我在捣鼓API密钥、Supabase(Supabase,一个我几乎搞不懂的后端数据库服务),以及一大堆一年前才第一次听说的词汇时所做的事起了个名字。他们称之为“任务跨界”(task crossover):利用AI从事历史上属于另一个职业的工作。在他们对超过80万条与工作相关的ChatGPT消息分析中,有16.8%涉及任务跨界。
在某处,有营销人员在调试网站,有小企业主在审查合同,还有其他写作者正看着一个能跑的应用想:好吧,如果我连这个都能搞定,那还有什么我做不到的?
对我来说,答案变成了:足以把自己惹上麻烦。
你能理解这事儿的诱惑力。一个带着特定问题的人,再也不必去寻找那个可能解决它的专家,再费口舌证明自己的问题值得对方花时间,然后等它慢慢排到对方待办清单的前列。AI能帮助我们在还没判断一件事是否安全或适合给别人用之前,就把它做出来。
让我给你讲讲我的惨痛经历。
初学者的傲慢
Tastemaker是一个收藏我热爱的文字,并将我对它们的看法转化为我能实际运用之物的地方。我手动做这种事已经很多年了:往笔记应用里复制好句子,在书里划线,然后试图解释为什么某一段文字充满电力,另一段读起来却像一封入职邮件的感情温度。
然后,我又有了一个点子。
我想让人们直接把他们的Tastemaker个人资料连接到Claude Code、Codex,或是他们正在使用的任何一种智能体上。这样,智能体就能在他们拥有了一份风格指南后检索到它,还能把新的样本直接从智能体那边添加进Tastemaker,而不用来回倒手。实现这个功能的技术术语,我当时刚好知道到足以点名索要的程度,叫作MCP:一种让AI智能体连接到外部服务的接口。(我得声明一下,我是在知道我们Every的写作智能体Spiral也要加入类似功能之前,自己想到这点的。)
这个点子立刻就显得非常合理,这或许本应是我的第一个警告信号。Tastemaker知道你喜欢什么,而你的智能体正是你写作的地方。一个直接的连接可以让人们免于在两处之间烦人的往返。我可以把应用变得更有用,更像我现在相信自己有能力打造的那种复杂产品。
不过,我也不傻。我确实问过一位经验丰富的软件工程师朋友,这是不是等着爆发的安全噩梦。
他告诉我,一个经过认证的API不会因为被智能体使用就变得更有风险——它永远只会做被要求做的事。但一个负责管理用户访问的后端智能体则是另一回事:它决定谁可以进来,所以一个错误或被操纵的调用,就可能打开不该打开的门。这是个好建议,但并不是对我所构建之物的安全审查。
我把他的解释拿回去给Claude,然后给了它一个大概相当于技术指令的话:确保你做的是安全的版本。
Claude构建了这个连接器。它运行了检查。它把这个功能描述为已完成。我测试了,它能用。由于我那编码初哥的脑袋把“能用”等同于“安全”,我说出了那句如今我才意识到,可能是一个氛围程序员能说出的最危险的几个词:“提交并部署。”
后来,我获得了GPT-5.6 Sol 的访问权限,我让它检查一下Tastemaker,想看看一个更新潮的模型会怎么评价我的代码。我本以为它会批评代码结构,或者建议清理旧代码。
然而,它发现那个已上线的连接器,有一个公开注册路由——一个任何人都能走进来的敞开大门——而它本不应该是公开的。
万幸,没有证据显示有谁访问过用户数据。但这个功能是上线的,漏洞正等着被利用,并且审查发现了足够的风险,以至于Sol请求我允许它关闭连接器,并在调查期间让其活动会话失效。
我立刻就批准了。
Sol提议构建一个更安全的版本,但我拒绝了。我已经摸过火炉,被烫伤了。在我决定再次触摸它是否安全之前,我需要一些时间来恢复。
人类的认知方式如何让你陷入困境
我的第一反应,是想把这个故事讲成一个不懂技术的人拿着自己的新鲜玩意儿嗨过头了的故事。但我不认为我自己的傲慢就是故事的全部——我也不认为我是一个人在任务跨界的海妖歌声面前格外脆弱。
如果你问我:“你是个安全工程师吗?”我当然会说不是。我掉入的是一个更隐蔽、更危险的陷阱:高估了自己的理解能力。毕竟,做这个功能的不是我——是Claude。我是在监督。我能看到Claude的思维链,因为那些话听起来都有道理,我以为自己理解了它正在做的工作。我想当然地觉得,那些我因为无知而提不出的问题,一定在某个环节已经被处理好了。
心理学家对此有个名词:解释深度错觉(illusion of explanatory depth)。大意是说,人们会自信地认为自己理解某样普通机制——直到他们试图一步步解释这个东西到底是怎么工作的。
AI让你轻易就能跳过那个时刻。它把解释和代码都给了你,而且那个按钮在你点的时候真的管用。在未经训练的眼睛看来,“完成”的表象就成了已经完成的证据。
我测试了我想让Tastemaker做的事:连接到一个智能体。它确实连接上了一个智能体。但我没有测试,一个本来不该连接的人是不是也能连接上。我当时压根都不知道这是个需要问的问题。当功能已经能用时,那些没有答案的问题就感觉没那么火烧眉毛了。然后,他们就真的不是问题了。
软件测试的研究描述了一种类似的模式。人们倾向于测试他们期望程序做的事,而不是寻找它可能出故障的方式。一切顺利的路径是值得测试的。我只是把一个通过的测试,当成了它从未检查过的事情的证明。
这两种认知倾向都不是Claude发明的。但搜索引擎至少会让你自己去点开链接,自己做判断。Claude把一个成型的点子变成一个能跑的应用速度太快了,快到我从没来得及感到害怕。
在限制中学会编程
我为下一次设定自己的规则很简单:学习我正在进入的领域的基本原则。请一位人类专家帮我看看我漏掉了什么。如果东西是AI做的,就别让同一个系统的保证成为证明它已经准备就绪的唯一证据。但愿有了这些减速带——有了这些让人能说“等等”的机会——我能有更好的机会,维持一个我能够负责任地支撑起来的速度。
而且,为了确保我真的把事儿想透彻了,我正在拉AI来帮忙。
我已经在我的AGENTS.md里——这是Codex在采取任何行动之前会先读取的文件——添加了如下指令:
成为Every的付费订阅者,即可解锁本文全部内容,并获得Katie那能让人工智能体在做不可逆操作前暂停的五步提示法。
核心启示:AI极大地降低了“制造”的门槛,但没有降低“判断”的成本;把“能用”误认为“安全”,是任务跨界者最容易踩中最危险的陷阱。
I Vibe Coded a Security Risk 的发芽报告
材料核心
作者 Katie Parrott 以亲身经历揭示了“氛围编程”(vibe coding)的核心陷阱:AI 让非技术人员能以惊人速度跨越职业边界、产出可用软件,但这同时制造了一种危险的认知幻觉——产品“能用”被等同于“安全”,而使用者甚至不知道自己该问什么问题。一篇关于任务跨界、解释性深度错觉与安全责任的鲜活批评。
发芽 01:当“能用”成为最高赞美——测试思维的沦陷
种子
Parrott 的故事里有一个关键转折点:她测试了功能是否连通,但没有测试是否有人不该连通。这不是粗心,而是一种被 AI 速度掩盖的认知盲区:当按钮真的按下去就工作了,我们倾向于把“一次成功”当成“全部安全”。
心理学研究很早就揭示了这种倾向。1994 年,Paul C. N. Y. Te'eni 等人在 Journal of Applied Psychology 上发表了一项关于软件测试行为的研究,发现人们天然倾向于进行“确认性测试”——验证程序按照预期运行,而不是去寻找它可能崩溃的方式。测试者设计用例时,脑子里想的是“这个功能怎么走通”,而非“这个系统怎么被搞垮”。这就是为什么一个开发新手会测试“能不能连上 agent”,而不会问“没权限的人能不能连”。
深层问题在于测试思维的本质差异。专业测试工程师接受的训练是“破坏性思维”:他们预设在代码的阴影里藏着什么东西,任务就是把它挖出来。而业余构建者——尤其是被 AI 赋予超人速度的业余构建者——往往停留在“建设性思维”里:东西造出来了,跑通了,就是成功的。
微软在 2015 年的一项内部工程实践分析中总结了“快乐路径测试”与“负面测试”的灾难性差距。他们发现,野生产品中的大多数安全漏洞,其触发场景都是开发者从未想过要测试的输入组合。换句话说,漏洞之所以存在,不是因为代码太难写对,而是因为测试时没人想到要把它写错。
Parrott 用 AI 构建的 MCP 连接器有一个公共注册路由“不应为公开”——这恰恰是负面测试要抓的那类问题。如果她有一个 checklist 问“谁能调这个接口?不该调的人调了会怎样?”,问题可能在部署前就会暴露。但 AI 的流畅输出制造了这样一种印象:既然代码是被“跑过检查”的模型生成的,那么安全检查也应该在某个步骤里被隐含地完成了。这种“自动化光环”让提出质疑的冲动被安抚,直到有人——这次是另一个 AI 模型 GPT-5.6 Sol——主动用审计视角重新审视。
Aha 瞬间
“AI 不会替你问该问的问题。它只会完美地回答你提出的问题——而你需要用破坏性思维去提出它们。”
发芽 02:负责任的“快”——来自 DevOps 世界的一课
种子
Parrott 给自己的教训之一是:不要用同一个系统既建造又验收。这和软件工程里的一个经典原则惊人地一致——分离职责。只不过,她是在踩坑之后才靠本能提炼出来的。
在专业软件工程领域,这个原则已经演进了二十年。2000 年代初,敏捷宣言推动了快速迭代的文化,但很快,运维团队就发现自己半夜被叫起来处理生产事故,因为开发者把“能跑”的代码直接扔过墙。2010 年前后,DevOps 运动的核心主张之一就是“你不能既要速度又要安全”——除非把安全的检查点分散到开发流程的每一个环节里,而不是留到最后。
2012 年,Etsy 的工程团队公开分享了他们的经验:将安全扫描、自动化测试、代码审查等流程嵌入持续集成管线后,部署频率从一周几次提升到一天几十次,而故障率反而下降了。关键机制不是让每个人变成安全专家,而是让“非专家”能在最可能犯错的时刻被阻断——就像飞机驾驶舱的检查单,不要求机长记住所有步骤,但要求起飞前必须逐项确认。
Nick Galbreath,曾任 Etsy 的工程总监,后来创立了 Signal Sciences,他提出过一个概念叫“可防御的快速”:速度本身不是问题,问题在于速度是否被检验过。他的逻辑是,提高速度的同时,必须提高“错误可见性”——让每一个动作的边界风险在完成前就曝光,而不是在事后追查。
Parrott 给自己制定的新规则——让人类专家审查、不允许 AI 的自检成为唯一验收标准、在 AGENTS.md 中预设“暂停”指令——本质上就是为她的工作流增加“错误可见性”。她并非要放弃 AI,也不是要退回到手动编码的慢速,而是在学习设计一种“可防御的氛围编程”。这恰好与 Netflix 著名的 Chaos Monkey 哲学形成镜像:不是祈祷系统不故障,而是主动在可控条件下制造故障,以验证系统能承受。Parrott 的暂停指令就是她的 Chaos Monkey——在 AI 流畅输出时,刻意引入中断,以检测未经审视的假设。
这种思路适用于所有被 AI 加速的工作。你不是需要一个比你聪明的东西替你思考,而是需要一个在你跳过步骤时喊“等一下”的机制。
Aha 瞬间
“真正的速度不是冲过终点线的瞬间,而是每一次加速都被制动系统保护着的行进。”
你的思考空间
- 在你的工作中,哪个环节最可能存在“确认性测试”陷阱——你只验证了事情能做对,却从未想过它可能在哪里被做错?那个没被测试的失败场景是什么?
- 如果你是一个依赖 AI 的非专家,你会设计什么样的“检查单”或“暂停点”,来确保在你不知道该问什么的时候,有人(或系统)能替你问?
- 组织层面,当越来越多的员工通过任务跨界用 AI 进入陌生领域,“安全责任”应该由个人承担,还是应该像 DevOps 模式一样,由组织提供嵌入流程的保护机制?边界在哪里?
- 你的团队或项目里,有没有一个“既建造又验收”的 AI 链条?如果这个链条中的 AI 给出了错误但符合你预期的答案,你能在什么时候发现?