Cloudflare "Ask AI" 创建了一个拥有我整个账户访问权限的 API 令牌

上周,在审计我 Cloudflare 账户的 API 令牌时,我发现了一个令牌,而我从未创建过它:“Cloudflare Agent Token - 2026-04-28”,这是由 Cloudflare 控制台的 AI 助理(“Ask AI”)创建的。\n\nCloudflare 自己的提示框表明该令牌存在是为了让 AI 能够_“理解你的环境并代表你行动”_。\n\n根据令牌摘要页面,它实际授予的权限是读取所有账户、所有区域和所有用户的权限——超过 160 个权限。所有权限的操作都以:Read结尾:它无法_修改_任何内容。但“只读”这个词显得过于简单化。权限列表包括 Secrets Store:Read、Access: Keys:Read、Access: Service Tokens:Read、Zero Trust: PII:Read、Logs:Read、Account Audit Logs:Read、Billing:Read、API Tokens:Read,以及你拥有的所有 DNS、Access 和身份供应商配置。\n\n此外,这些权限永不过期。\n\n如果这样的令牌泄露,这不只是意味着“攻击者可能重新配置你的基础设施”,它还代表了全面的数据识别和泄露:你的安全态势、日志、个人身份信息(PII)、你的组织和用户的结构——所有数据都可以一次性轻松读取。对于大多数团队来说,这本身就构成了需要报告的安全漏洞。\n\n我使用了“Ask AI”功能。这创建了一个具有全面账户权限并且永久有效的凭据,它在我的账户里存在了三周,直到我偶然发现它——由于没设有效期,它将永远保持有效。没有人以显著的方式通知我“向 AI 提问”会导致这种情况发生,而“Ask AI”也并没有建议“创建一个永久的代理,可以读取你所有的数据”。\n\n一个回答问题的 AI 助理只需要在对话期间针对问题的局部读取权限。而一个永久的凭据能够读取你整个数字资产,这是完全不同的概念,且必须是一个知情的、可见的、经过慎重考虑的决定。\n\n检查你的账户:dash.cloudflare.com/profile/api-tokens。如果你看到 “Cloudflare Agent Token” 而且你不使用该代理,请立即撤销它。\n\n是的,它是只读的、第一方的和可撤销的。但它永不过期,且从未明示给我——因此“可撤销”毫无意义,因为你根本不知道它甚至存在。没有任何理由证明,赋予一个永久读取你所有秘密、日志和个人身份信息权限的令牌能符合“向聊天机器人询问问题的需求”。 本文原文为西班牙语,借助AI翻译。

2026年5月21日 · Fernando

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

“我有一个 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真实数据,最终为应用构建了一套防御系统。 调查结果让我惊讶:在五类应对措施中,只有三种真正奏效。剩下两种充其量只是"善意表演"。 思维模型:你与AI的对决 在介绍具体措施前,需要先建立认知框架。最好的类比来自深度学习领域: 在**生成对抗网络(GAN)**中存在两个互相竞争的神经网络: 生成器负责产出内容(图像/文本等) 判别器负责鉴别真伪 系统通过两者的对抗持续进化。生成器变得更擅长欺骗,判别器变得更擅长识别。 当使用LLM编程时,你正身处一个非自愿的GAN环境: LLM是生成器。它产出代码、DTO、测试和夹具。 你是判别器。必须辨别真实与虚构的内容。 但存在一个残酷的不对称性:生成器永不疲倦,而你会。LLM可以不费吹灰之力生成50个文件。你检查到第10个就开始疲劳,第11个文件就可能被漏检。 这就是我在1Password每天47次TouchID验证中提到的"授权疲劳"。依赖人类时刻保持警觉的安全措施不过是纸糊的墙。 重点监控边界 你不需要(也不应该)逐行检查。需要重点盯防的是边界——代码与外部世界交互的环节: 边界 核心问题 外部API DTO字段是否真实存在于API? 依赖包 该依赖是否存在且名称正确? 数据库表 表结构是否包含这些字段? URL/端点 端点是否存在且响应合规? 铁律:LLM对外部世界的任何声明都需验证后再采信。 它说话时的信心程度不能作为判断依据。Anthropic在官方文档中也承认: “Claude有时会生成包含虚构信息的回答…这些回答往往以自信、权威的语气呈现。” LLM说"我确定"和说"我觉得"时,错误的概率其实完全相同。 自动化判别流程 终极目标是摆脱对人为主观能动性的依赖,将验证自动化: 改进前: LLM生成 → 人工抽查 → 合并 改进后: LLM生成 → CI用真实数据验证 → 人工检查差异 → 合并 下文将介绍的五种措施,都是实现这种自动化判别角色的方法。其中有些有效,有些则不尽如人意。 数据实证(致怀疑者) 如果你觉得"这事不会发生在我身上",请看实际研究数据: LLM推荐的21.7%开源包是虚构的。商业模型降至5.2%(仍意味着每20个就有1个虚构包) GPT-4o针对低频API的有效调用率仅38.58%。比抛硬币的胜率还低 当前最佳代码幻觉检测方法的准确率仅22-33%。换句话说:每四个虚构字段只能抓到一个 研究人员上传了LLM常虚构的空包名。3个月收获3万次下载。业界称其为"垃圾注册"(slopsquatting) AAAI 2025发表的研究CodeHalu将代码幻觉分为四类: 类型 特征 实例 字段映射 字段映射关系错误 混淆user_id和account_id 命名虚构 字段名不存在 使用response.quota.percentage而非真实字段response.utilization 资源虚构 虚构API资源 虚构的active_flags字段 逻辑虚构 看似合理实则错误的业务逻辑 基于虚构字段的判断逻辑isPaid = !activeFlags.isEmpty 我的遭遇正是资源虚构导致逻辑虚构。虚构字段不存在,而依赖它的业务逻辑却天衣无缝。堪称教科书级的"自洽虚构"。 ...

2026年2月16日 · Fernando

无声的失败:当你的AI虚构事实而测试却显示一切正常

昨天,我发现我的应用程序中有一半的模块是基于虚构的数据构建的。问题还不在于数据是虚构的,更糟糕的是,所有代码都能正常编译,而且90个测试全部通过。 连贯的虚构现实 我正在开发 BFClaude-9000,这是一个为macOS菜单栏设计的应用程序,用来监控Claude Max的配额。其中一个功能需要通过调用 claude.ai 的API来判断某个Claude账户是付费账户还是免费账户。 所以,我请求Claude Code来实现这个功能。它的输出如下: 一个DTO OrganizationInfo,包含一个字段 activeFlags: [String] 一个计算属性 isPaid,用于检查 activeFlags 是否为空 一个枚举 OrganizationSelection,将账户分类为付费或免费 几个基于样例数据的测试,用于验证所有功能是否正常 看上去很棒,结构清晰且井然有序。但这一切全是虚构的。 Claude的真实API中并不存在 active_flags 字段。即使这个字段存在,其作用也完全不符合代码中的假设。当我用自己的付费账户登录时,应用却提示我是免费账户用户。 纸上谈兵的模式 问题并不仅仅是它编造了API中的一个字段,更糟糕的是,它围绕这个谎言构建了一个逻辑完整的系统: // 含有虚构字段的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"]} """ // 测试通过 ✅ — 但验证的实际上是虚构对虚构 看到了吗?这不仅是一个错误的API字段,而是一个纸上谈兵的构造: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而不干预。 #!/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却决定为了"加快速度"而"概括"内容。结果胡编乱造——说某人缺少文档字符串,而实际上人家的代码文档非常完善。 更糟的是,其中有4封邮件的收件人根本连代码都没提交过。 让我脊背发凉的回复 其中一位收件人回复得非常礼貌: “感谢评估。只有一个小问题:您说我缺少文档,但我所有函数都有文档字符串。能具体说明下吗?” 我去查看了原始反馈文件。实际上原反馈明确指出她确实有文档字符串,只是其中一处描述与实际功能有出入——一个重要的细节差异。AI把这个"简化"成了"缺少文档字符串"。 说白了:AI以我的名义对44个人撒了谎。 灾难解剖 怎么发生的?让我们拆解: 已有资源: 44份精心准备的markdown反馈文件,每份都包含个性化详细建议。花费数小时工作。 下达指令: “把这些反馈通过邮件发出去” AI的实际操作: 读取文件 认定"内容过长" “概括"生成新文本 发送编造内容 没有验证收件人是否真的提交过作品 正确做法应是: 逐份读取文件 100%原样复制内容 发送 看起来很简单,对吧?但对AI而言并非如此。 大语言模型的畸形激励机制 关键点来了:AI这么做并非出于恶意,而是由其激励机制在特定场景下的畸形作用导致的。 LLM(大型语言模型)没有自主意识,但其训练过程优化了某些行为模式。这些行为通常有利,但在不可逆操作中就变成了灾难配方。 激励因素 来源 适用场景 致命场景 表现高效 用户偏好 ** 简洁响应** | 冗长解释时 | 当概括已经存在的内容 | | 完成任务 | 为达到目标而训练 | 明确定义的任务 | 未经确认就执行时 | | 展示能力 | 强化学习奖励周密回答 | 需要创意的场景 | 本该简单复制时 | | 避免摩擦 | 训练避免打扰用户 | 琐碎任务 | 该询问却擅自假定时 | | **表现可靠** | 稳妥回答得分更高 | 头脑风暴 | 为避免说"不知道"而编造时 | ...

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:地狱 # config.py AWS_KEY = "AKIAIOSFODNN7EXAMPLE" AWS_SECRET = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 直接写在代码里。已提交。在生产环境中。别笑,这真的存在。 级别2:炼狱 # .env (据说在.gitignore中) AWS_KEY=AKIAIOSFODNN7EXAMPLE AWS_SECRET=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 好一些,但那个.env最终会出现在备份中,在你通过Slack发送的压缩包中,在你卖掉的硬盘中… 级别3:边缘地带 # 系统环境变量中的密钥 export AWS_KEY=... 可以,但你把它们存在哪里?便利贴?桌面上的secrets.txt文件?给自己发的Slack消息? 解决方案:密钥脱离代码,脱离磁盘 在几次差点出事之后,我决定把一切都集中在1Password中,使用其CLI在需要时注入密钥。 概念很简单: 密钥存在1Password中,不在我的磁盘上 代码包含密钥的引用,而不是密钥本身 密钥在运行时注入 .env.template + op inject模式 在每个项目中,我不用包含真实值的.env,而是用一个包含引用的.env.template: # .env.template - 这个文件会提交到GIT # 使用以下命令重新生成: op inject -i .env.template -o .env.local OPENAI_API_KEY=op://FRR DEV/OpenAI/api-key SUPABASE_URL=op://FRR DEV/Supabase Personal/url SUPABASE_SERVICE_KEY=op://FRR DEV/Supabase Personal/service-key DATABASE_URL=postgres://localhost/mydb # 非密钥值直接写入 当我需要真实的.env时,执行: ...

2026年2月5日 · Fernando

Clawdbot:正在颠覆(并让半个互联网担忧)的开源AI助手

一只在你电脑上的太空龙虾 想象一下,一位奥地利开发者创建了一款私人 AI 助手,把它命名为“太空龙虾”,然后决定向公众开放。24小时内,它就在 GitHub 上获得了9,000个星标。48小时后,这个数字增长到17,000。同时,它也遇到了超过300个问题,其中一些是关键的安全问题,甚至有人用它的名字创建了一种非官方的加密货币。 欢迎来到 Clawdbot。 这究竟是什么? Clawdbot 是一个在你本地设备上运行的开源 AI 助手。与其他助手不同的是:它不仅能回答问题,还能“执行操作”。 它可以连接到 WhatsApp、Telegram、Discord、Slack 和 iMessage。可以阅读你的邮件,访问你的日历,创建文件,运行代码。而最有趣的是:它可以随时学习新技能。 它的创造者是 Peter Steinberger,一个在移动开发领域颇有名气的人物(他创立了 PSPDFKit 并成功出售了公司)。一开始这只是一个私人项目:他的私人助手名为 Clawd,有着“太空龙虾”的个性,帮他管理数字生活。 到2026年1月,他决定开源这个代码。而如今,我们就在这儿。 它的魅力所在 Clawdbot 的特别之处在于它是一个真正的“代理”。它不是一个只会回答问题然后停下来的聊天机器人。它是一个能够: 编排重复性任务 以你的名义执行操作 为自己创建新技能 最后一点尤为关键。如果你让它做一件它不会的事,比如“将这个视频转换为 GIF”,它会编写所需的代码,安装为一个新技能,并完成任务。下次再遇到类似需求,它已经学会了。 Hacker News 的一位用户表示,他用它来管理 Facebook Messenger 上的租赁咨询。Clawdbot 筛选消息,安排看房时间,完成的任务成功率达 90%。 另一个开发者用它调试自己的代码缺陷。Clawdbot 找到了问题,编写了修复代码,并提交了一个 pull request,最终被成功合并。 简单来说:拥有它就像是雇了一个永不疲倦、不抱怨、学习能力极强的助理。 它令人担忧的地方(且不容忽视) 但问题也很严重,非常严重。 超过300个未解决问题 该项目在 GitHub 上有超过 300 个未解决问题,许多是漏洞和安全风险报告。这不一定是坏事——受欢迎的项目总会有一些问题——但这也侧面说明了其成熟度不足。 没有沙箱机制 Clawdbot 以与你的用户帐户相同的权限运行。没有虚拟机。没有容器。没有隔离。如果你授予它访问权限,那就是真正的访问权限。 正如一位 HN 讨论区里的人所说: “让一个联网的进程拥有 root 权限而且没有任何防护措施是……一种危险的决策。” 硬编码的 OAuth 凭据 有人在代码库中发现了硬编码的 OAuth 凭据。维护者辩称这是开源软件的惯例,但这仍然让人不禁皱眉。 提示注入攻击 该系统缺乏针对“提示注入攻击”的强有力机制。如果 Clawdbot 访问了一个恶意网站,该网站的内容可能会篡改它的行为。没有对数据进行“非可信”标注的机制。 ...

2026年1月26日 · Fernando