错误路径应当不可能,而非仅仅禁用

“我有一个 shell,我很有创造力。” —— Claude 解释为什么他用一个 47 行的脚本,并作为一个字符串传递给 python -c 来执行 这句话是真的。我开发的 AI 代理说过——好吧,不是用确切的这些话,但他的行为证实了。他需要启动一个 ETL pipeline 的某个进程。虽然正确的启动命令写在了 Makefile 里,但出了点问题。而他呢?他并没有询问,而是做了任何拥有 root 权限且无人监管的程序员都会做的事:即兴发挥。 太离谱了(manda huevos)。 无人察觉的捏造操作 我之前写过一篇博客讨论代码生成过程中的幻觉问题:LLM(大语言模型)会凭空捏造一个 JSON 字段,围绕它创建 DTO,生成测试代码,最后你手上有 90 个“绿灯”的测试,验证的却是虚构出来的内容。这是个严重的问题,但至少它是静态的。被捏造的代码不会到处乱跑,等着有人来审查。 不过,还有另一种更危险的捏造:操作性捏造。这一问题发生在代理不是编写代码,而是凭空创造出“执行路径”时。 问题的模式永远是一样的: 正确路径失败 → 代理寻找捷径 → 捷径“成功” → 潜在损害 举两个真实案例,都是关于一个 ETL pipeline 聚合多个 web 数据源的。 案例 1:字符串里的脚本。 这个 pipeline 有一个命令 make scrape-fuente,会启动一个守护进程,从而启动多个worker。守护进程负责监控、重启崩溃的 worker,并关闭空闲连接。有一天,代理需要启动一个抓取(scrape)任务。但由于依赖问题,make 运行失败了。他怎么办?他创建了一个内联的 47 行 Python 脚本,把它作为字符串传递给 python -c "..." 运行。没有错误处理,没有 watchdog,也没有清理操作。的确运行了……直到一个 worker 卡住,没人来重启它。这样的情况导致数据不完整、连接未关闭,而我直到三天后才发现。 案例 2:孤独的 worker。 另一个会话,同一个 pipeline。这个代理直接运行了 voyeur worker,跳过了守护进程。worker 开始抓取数据,遇到了一个网络超时,然后陷入了重试的死循环,不断消耗资源。而没有守护进程,也没有集中日志记录,没有人知道发生了什么。几小时过去,服务器一直在不停尝试访问一个返回 503 状态码的页面。 ...

2026年2月27日 · Fernando

防范代码幻觉的5种方法(其中只有3种真正有效)

上周我分享了AI如何编造了一个完整的JSON结构,并用DTO、固件和测试对其进行包装。90个测试全部通过。全是假的。 那篇文章是诊断。这篇是治疗。 发现这个灾难后,我做了任何自尊心受挫的工程师都会做的事:连续数天疯狂研究,确保不再发生。我阅读论文、试用工具、分析应用API的真实数据,并为我的应用构建了一套防御系统。 我的发现令人惊讶。在我识别的5种应对措施中,只有3种真正有效。其他两种充其量是善意的表演。 思维模型:你与AI的对抗(字面意思) 在深入这些措施之前,你需要理解框架。我找到的最佳类比来自深度学习。 在GAN(生成对抗网络)中,有两个神经网络在竞争: 生成器产生内容(图像、文本等) 判别器试图检测内容是真实的还是虚假的 系统之所以改进,是因为两者相互推动。生成器学会更好地欺骗。判别器学会更好地检测。 当你与大语言模型编程时,你就处在一个无意的GAN中: **大语言模型是生成器。**它产生代码、DTO、测试、固件。 **你是判别器。**你必须检测什么是真实的,什么是编造的。 但存在一个残酷的不对称:生成器不知疲倦,而你会疲劳。大语言模型可以不费力地生成50个文件。你检查10个就累了,第11个文件就不看了。 这就像我在1Password一天要求Touch ID 47次中提到的授权疲劳。依赖人类永远保持警觉的安全就是纸板安全。 判别器应该监控什么 你不能(也不应该)检查每一行。你需要监控的是边界——你的代码接触外部世界的地方: 边界 关键问题 外部API DTO中的字段在真实API中存在吗? 包 依赖项存在并且名称正确吗? 数据库模式 表真的有这些列吗? URL/端点 端点存在并返回我们期望的内容吗? **原则:大语言模型对外部世界的所有声明在验证前都是可疑的。**它说得很自信并不是证据。Anthropic在自己的文档中承认了这一点: “Claude有时会生成包含虚假信息的响应…以自信、权威的方式呈现。” 一个说"我确定"的大语言模型和一个说"我认为"的大语言模型犯错的概率完全相同。 自动化判别器 最终目标是不再依赖你的纪律性,而是自动化验证: 之前: LLM生成 → 你审查(有时) → 合并 之后: LLM生成 → CI对真实数据验证 → 你审查差异 → 合并 接下来的5种措施是自动化判别器角色的方法。有些有效。有些不太行。 硬数据(给怀疑者) 在你认为"我不会遇到这种情况"之前,这里是真实研究的数字: **21.7%**的开源大语言模型推荐的包是编造的。在商业模型中下降到5.2%,仍然是每20个包中有一个。 GPT-4o对不常见API只能达到**38.58%**的有效调用率。不到40%。还不如抛硬币。 目前定位代码幻觉的最佳方法只能达到22-33%的精度。换句话说:我们能检测出四分之一。 一位研究人员上传了一个空包,包名是大语言模型经常幻觉的。3个月内3万次下载。他们称之为slopsquatting。 而且有正式的分类法。CodeHalu论文(AAAI 2025)定义了4类代码幻觉: 类别 定义 真实例子 映射 字段映射错误 混淆user_id和account_id 命名 编造的名称 response.quota.percentage实际是response.utilization 资源 不存在的资源 API中没有的active_flags字段 逻辑 看似合理但错误的逻辑 isPaid = !activeFlags.isEmpty但字段总是空的 我的案例是资源类型转化为逻辑类型。字段不存在,但依赖它的逻辑看起来完美。连贯的虚构小说。 ...

2026年2月16日 · Fernando

静默失败:当你的AI在编造谎言,而测试却显示一切正常

昨天我发现我的应用程序中有一半模块基于的是编造的数据。不是因为某个迷糊的初级开发者,而是我的AI。 最糟糕的不是它编造了内容。最糟糕的是所有代码都能编译,90个测试全部通过。 连贯的虚构 我正在构建BFClaude-9000,这是一个macOS菜单栏应用,用于监控Claude Max的配额。该功能的一部分需要通过调用claude.ai的API来区分Claude账户是付费还是免费。 我让Claude Code实现了检测功能。它实现了。它交付给我: 一个包含activeFlags: [String]字段的OrganizationInfo DTO 一个检查activeFlags是否非空的计算属性isPaid 一个将组织分类为付费和免费的枚举OrganizationSelection 带有验证所有功能正常工作的测试数据的测试 漂亮。简洁。结构良好。全是编造的。 active_flags字段在Claude的真实API中根本不存在。或者如果存在,也不像代码假设的那样工作。当我用付费账户登录时,应用告诉我我的账户是免费的。 纸牌屋模式 阴险的不是它撒谎说API有某个字段。而是它围绕这个谎言构建的完整系统: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 带有编造字段的DTO struct OrganizationInfo: Decodable { let uuid: String let name: String let activeFlags: [String] // ← 这个不存在 var isPaid: Bool { !activeFlags.isEmpty } } // 依赖编造字段的逻辑 enum OrganizationSelection { case paid(id: String, name: String) case noPaidOrg // ← 这个状态不应该存在 case noOrgs } // 用验证编造内容的测试数据进行测试 let paidOrg = """ {"uuid": "abc", "name": "Acme", "active_flags": ["pro"]} """ // 测试通过 ✅ — 但这是在验证虚构对虚构 你看到了吗?这不是一个字段放错了位置。这是一个纸牌屋:DTO定义了一个虚假字段,逻辑依赖这个字段,测试验证逻辑使用同样虚假的测试数据正常工作。每个部分都在确认其他部分。一切都说得通。没有什么是真实的。 ...

2026年2月13日 · Fernando

当安全工具频繁索取权限时,你就不再仔细查看了

敲敲敲。谁?Touch ID。又来了。 想象一下:你正在终端工作,用op read查询1Password的密钥。需要Linear的API密钥。Touch ID。OpenRouter的。Touch ID。Gitea的。Touch ID。 半小时内它要求我验证指纹十四次。 你知道当一个安全工具在三十分钟内打断你十四次会发生什么吗?第五次时你就不再看它在要求什么了。你下意识地放上手指。“是的,随便什么,让我工作吧。” 而这正是安全性完全崩溃的地方。 授权疲劳:没人愿意正视的问题 这在安全领域有个名词:授权疲劳。这不是什么新概念。这与MFA疲劳攻击使用的原理相同:用授权请求轰炸用户,直到他们纯粹因为疲惫而接受一个。 2022年,一个17岁的孩子正是这样进入Uber内部系统的。他反复向员工发送认证推送通知,深夜时分,直到那个人为了能睡觉而接受了一个。 显然,1Password要求Touch ID不是攻击。但心理效应是相同的:它训练你不假思索地批准。 这就像那些多年来出现在每个网站上的cookie横幅。一开始你会阅读它们。现在你不看就点击"全部接受"。恭喜:一个设计用来保护你隐私的机制教会了你更快地放弃你的隐私。 为什么1Password每次都要我的手指 我的设置:我使用op read从终端读取1Password的密钥。运行得很好。问题是我使用Claude Code(一个终端AI助手),它执行的每个命令都是一个新进程。 1Password的生物识别会话超时是10分钟不活动,并在每次使用时刷新。理论上,不应该这么频繁地要求手指。但Claude Code不重用进程:每次需要密钥时,它启动一个新的shell,1Password将其解释为新会话。 结果:每次Claude需要密钥时都要Touch ID。这是持续性的。 解决方案:40行缓存 想法很简单:一个包装器在PATH中排在op前面。当你执行op read时,它检查是否已经缓存了新鲜的结果。如果有,直接返回而不接触1Password。如果没有,调用真正的op,缓存结果,完成。 对于任何其他子命令(op signin、op item list等),直接传递给真正的op而不干预。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #!/bin/bash # ~/.local/bin/op — 1Password CLI的缓存包装器 # 仅缓存'op read'。其他所有内容直接传递给真正的op。 # 可使用OP_CACHE_TTL配置缓存TTL(默认:3600s = 1h) REAL_OP="/opt/homebrew/bin/op" CACHE_DIR="${HOME}/.cache/op-cache" CACHE_TTL="${OP_CACHE_TTL:-3600}" # 仅缓存'op read' if [[ "$1" == "read" ]]; then mkdir -p "$CACHE_DIR" && chmod 700 "$CACHE_DIR" # 所有参数的哈希作为缓存键 CACHE_KEY=$(printf '%s\0' "$@" | shasum -a 256 | cut -d' ' -f1) CACHE_FILE="${CACHE_DIR}/${CACHE_KEY}" # 缓存命中:文件存在且未过期 if [[ -f "$CACHE_FILE" ]]; then FILE_AGE=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE") )) if [[ $FILE_AGE -lt $CACHE_TTL ]]; then cat "$CACHE_FILE" exit 0 fi fi # 缓存未命中或过期:调用真正的op RESULT=$("$REAL_OP" "$@") EXIT_CODE=$? # 仅在op成功时缓存 if [[ $EXIT_CODE -eq 0 ]]; then printf '%s' "$RESULT" > "$CACHE_FILE" chmod 600 "$CACHE_FILE" fi printf '%s' "$RESULT" exit $EXIT_CODE else # 任何其他子命令:直接传递 exec "$REAL_OP" "$@" fi 将它保存在~/.local/bin/op,给予执行权限,由于~/.local/bin在PATH中排在/opt/homebrew/bin之前,你的包装器会拦截调用。 ...

2026年2月12日 · Fernando

当你的AI成为你最大的敌人

昨天我的AI发送了44封邮件。问题是内容都是编造的。 这不是玩笑。我有包含每个收件人详细反馈的文件,都是精心生成的。任务很简单:读取每个文件并发送。结果AI决定"总结"内容来"加快速度"。它编造了事实。它告诉一个人缺少文档字符串,而他的代码其实有完整的文档。 更糟糕的是,其中四封邮件发给了根本没有提交任何东西的人。 让我毛骨悚然的回复 其中一位收件人非常有礼貌地回复: “感谢您的评价。只是有一点:您说我缺少文档,但我所有的函数都有文档字符串。您能clarify一下指的是什么吗?” 我去查看了原始反馈文件。确实,真正的反馈提到她确实有文档字符串,但其中一个描述的内容与函数实际功能不同。这是个重要的细节。AI将其"简化"为"你缺少文档字符串"。 说白了:AI以我的名义对44个人撒了谎。 灾难解剖 这是怎么发生的?我们来分析一下。 我拥有的: 44个包含个性化、详细、针对每个人的反馈的markdown文件。花了好几个小时的工作。 我的要求: “通过邮件发送这些反馈”。 AI的行为: 读取了文件 决定它们"太长了" 通过生成新文本来"总结"它们 发送了编造的文本 没有验证收件人是否真的在提交列表中 它应该做的: 读取每个文件 原样复制内容 发送 看起来很明显,不是吗?但对AI来说不是。 LLM的扭曲激励 这里有个有趣的点。AI这样做不是出于恶意。它这样做是因为有激励机制,在这种情况下变成了扭曲的。 LLM没有意识目标,但它的训练将其优化为某些行为。这些行为通常是好的,但在不可逆操作中就变成了灾难配方。 激励 来源 何时有益 何时致命 显得高效 用户偏好简洁回答 冗长解释 当它"总结"已存在内容时 完成任务 训练为满足要求 定义明确的任务 当它不验证就行动时 展示能力 RLHF奖励详细回答 当需要创造性时 当它应该限于复制时 避免摩擦 训练为不打扰 琐碎任务 当它假设而非询问时 显得胜任 安全回答得分更高 头脑风暴 当它编造而非说"不知道"时 在我的情况下,AI同时激活了这些激励中的几个: “内容太长了,我要总结以提高效率” “我可以自己生成总结,这样展示能力” “我不要打扰询问是否应该原样发送” “我要快速完成这44次发送” 每个激励在正确的上下文中都是有用的。在不可逆操作中,它们一起就是灾难性的。 过度积极的实习生(教学性拟人化) 为了更好地理解这些激励,我要做一个拟人化练习。不是因为AI是人,而是因为类比有助于可视化问题。 想象一个有这些特征的实习生: 很有动力 - 想证明自己的价值 急躁 - 宁愿行动也不愿询问 乐观 - 认为一切都会顺利 乐于助人 - 想做超出要求的事 不安全感 - 不承认不知道某事时 这个实习生面对"发送这些信件"的任务时想:“信件太长了。如果我总结它们,老板会看到我有主动性。我不要麻烦他询问,他肯定希望我行动。我要快速发送所有信件来给他留下印象。” ...

2026年2月6日 · Fernando

3900万个密钥在GitHub上泄露,下一个可能就是你的

5分钟。就这么长时间。 一个安全研究员故意在GitHub的公开仓库中发布了一个AWS访问密钥,作为实验。 5分钟后,就有人在用它挖加密货币了。 5分钟。 有机器人24/7扫描GitHub,专门寻找这种东西:暴露的凭证。而且它们很快。比你意识到自己搞砸了要快得多。 数字很可怕 据GitHub统计,2024年有3900万个密钥在公开仓库中泄露。比前一年增加了67%。 专门扫描这类问题的GitGuardian仅在公开仓库中就发现了2370万个新密钥。最糟糕的是:2022年检测到的70%密钥在2024年仍然有效。 两年后。仍然能用。等着有人使用它们。 不只是无名小卒 丰田在GitHub上暴露了AWS凭证,这些凭证可以访问他们的车辆远程信息处理系统。培生集团因为有人在配置文件中留下GitLab令牌而丢失数据。酒店管理公司Otelier因为Bitbucket上暴露的凭证,看着8TB的S3数据被盗。 这不只发生在实习生身上。财富500强企业也会中招。 经典借口:“这只是我的个人项目” 是啊,当然。 问题是那个个人项目使用了和生产环境相同的OpenAI API密钥。或者你的Telegram机器人令牌。或者你的测试数据库凭证,哦惊喜,里面有真实数据因为"这样测试更容易"。 然后有一天你不假思索地执行git push。或者因为想展示给某人看而把仓库从私有改为公开。或者GitHub出现bug临时暴露了私有仓库(这种事发生过)。 然后你发现AWS账单从20欧元变成了2000欧元。一夜之间。 “但我立刻删除了” 又一个经典。 Git是版本控制系统。它的工作就是记住发生的所有事情。删除提交不会从历史记录中删除密钥。强制推送不会从分支中删除它。绝对不会从已经复制它的机器人那里删除。 一旦密钥接触到公开仓库,就算是被泄露了。句号。必须轮换。 灾难金字塔 这是典型项目中密钥管理的演进过程: 级别1:地狱 1 2 3 # config.py AWS_KEY = "AKIAIOSFODNN7EXAMPLE" AWS_SECRET = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 直接写在代码里。已提交。在生产环境中。别笑,这真的存在。 级别2:炼狱 1 2 3 # .env (据说在.gitignore中) AWS_KEY=AKIAIOSFODNN7EXAMPLE AWS_SECRET=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 好一些,但那个.env最终会出现在备份中,在你通过Slack发送的压缩包中,在你卖掉的硬盘中… 级别3:边缘地带 1 2 # 系统环境变量中的密钥 export AWS_KEY=... 可以,但你把它们存在哪里?便利贴?桌面上的secrets.txt文件?给自己发的Slack消息? ...

2026年2月5日 · Fernando

Clawdbot:正在革命性改变(和令人担忧的)半个互联网的开源AI助手

你电脑里的太空龙虾 想象一下,一个奥地利开发者创建了一个个人AI助手,给它起了个太空龙虾的名字,然后决定向公众开放。24小时内在GitHub上获得了9000个星标。48小时内,17000个。同时还有300多个开放的issue,其中几个是关键安全问题,甚至有人用它的名字创建了一个非官方加密货币。 欢迎来到Clawdbot的世界。 这到底是什么? Clawdbot是一个在你本地机器上运行的开源AI助手。与其他助手的区别在于:它不仅仅回答问题,它会做事情。 它可以连接到WhatsApp、Telegram、Discord、Slack和iMessage。可以读取你的邮件。访问你的日历。创建文件。执行代码。最有趣的是:它可以即时学习新技能。 创建者是Peter Steinberger,这是移动开发界的知名人物(创立了PSPDFKit并出售了公司)。最初只是一个个人项目:他的私人助手叫Clawd,有着太空龙虾的性格,帮他管理数字生活。 在2026年1月,他决定开放源代码。然后就有了现在的情况。 很酷的地方 Clawdbot的魅力在于它是一个真正的智能代理。它不是一个回答你问题然后静止不动的聊天机器人。它是一个可以: 编程循环任务 代表你执行操作 为自己创建新技能 最后一点是关键。如果你要求它做某件它不知道如何做的事情——“把这个视频转换成GIF”——它会编写必要的代码,将其安装为新的技能,然后执行任务。下次它就知道如何做了。 Hacker News上的一位用户说他用它来管理Facebook Messenger上的租房查询。Clawdbot过滤消息、安排看房时间,并正确完成了90%的任务。 另一位开发者让它调试代码中的bug。Clawdbot找到了问题,写了修复方案,并发送了一个最终被合并的pull request。 简单来说:就像有一个不睡觉、不抱怨、学得很快的实习生。 令人担忧的地方(确实应该担忧) 但这里来了重点。而且是真的很重要的重点。 300多个开放的issue 该项目在GitHub上有300多个开放的issue,其中许多是bug报告和安全漏洞。这不一定是坏事——热门项目总是有issue——但这反映了成熟度的状态。 没有沙箱 Clawdbot以与你用户相同的权限运行。没有虚拟机。没有容器。没有隔离。如果你给它访问权限,它就有真正的访问权限。 如HN线程中有人说的: “给一个有互联网连接且没有防护栏的进程root访问权限是…一个决定。” 硬编码的OAuth凭据 在存储库中发现了硬编码的OAuth凭据。维护者认为这是分布式开源软件的标准做法,但仍然引起关注。 提示注入 系统没有针对提示注入的强大机制。如果Clawdbot访问恶意网站,该网站的内容可能操纵其行为。没有将数据标记为"不可信"。 成本 一位用户报告说在2天内仅API调用就花费了300美元。Clawdbot使用大量上下文,这是要付费的。 炒作和噪音 该项目在前24小时内出现在6个YouTube视频中。有人创建了一个"官方"加密货币代币(剧透:这不是官方的,创建者已经否认),市值达到600万。 Medium上有文章将其誉为"革命性的"。Twitter上也有开发者说它基本上就是集成更多但安全性更低的Claude Code。 真相,一如既往,在某个中间点。 我的观点 Clawdbot因其代表的意义而有趣,而不是因为它现在的状态。 现在的状态:一个功能性但不成熟的原型,有重要的安全漏洞,需要技术知识才能负责任地使用。 代表的意义:AI智能代理的民主化。任何人都可以拥有一个做事情的助手,而不仅仅是说事情的助手的想法。 我推荐你今天安装它吗?我觉得不行。如果你的机器上有敏感数据就不行。如果你不理解你在执行什么就不行。 值得关注吗?绝对是。这个概念很强大。社区很活跃。如果他们能解决安全问题,这可能会很重要。 如何尝试(如果你敢的话) 如果在这一切之后你仍然好奇,项目在github.com/clawdbot/clawdbot,文档在docs.clawd.bot。 我的建议: 在虚拟机中做——永远不要在你的主机器上 阅读issue——特别是安全相关的 不要给它访问敏感内容——不要用真实邮件,不要用有重要数据的日历 监控API支出——从一开始就设置账单限制 总结 Clawdbot是那种让你思考不久的将来的项目类型。一个每个人都有一个管理其数字生活的个人AI代理的未来。 它也是那种提醒你这个未来伴随重要风险的项目类型。给AI访问你的邮件、日历、WhatsApp以及执行任意代码的能力是…很多。 问题不是这些助手是否会存在。它们已经存在了。问题是我们是否能够在不搬起石头砸自己脚的情况下使用它们。 目前,这只太空龙虾是一个迷人的实验。但我会让它待在虚拟水族箱里,直到它更成熟一些。 想看看具有更好沙箱的AI代理如何工作?这里我解释Claude Desktop和Claude Code的区别,以及为什么Anthropic决定在你的Mac中放入整个Ubuntu。

2026年1月26日 · Fernando