阿里云百炼异常扣费的一次排查和修复总结
近期在处理My-Notion项目时,突然发现阿里云账户出现欠费警报,这促使我必须深入排查异常扣费的原因。

看到账户欠费时,我最初猜测是某个AI功能调用量激增,或者RAG/Embedding服务在后台反复运行。
随着排查深入,发现问题的性质远比想象中复杂,但根源却相对简单。
账单显示,真正的费用来自阿里云百炼的大模型推理服务,模型调用集中在极短的时间窗口内,主要是glm-5.2的文本token消耗。结合项目代码和本地环境分析,我倾向于认为这是API Key明文暴露后导致的异常调用。
严格来说,现有证据无法100%证明谁获取了key,也无法直接确定攻击来源。但账单行为、模型名称、调用时间和项目配置已经足够说明:密钥管理和模型调用入口必须立即收紧。
本文详细记录了问题的定位过程、修复方法以及后续需要避免的隐患。
一、问题现象
阿里云费用中心显示账户欠费。
进一步查看账单后,费用来源基本排除了OSS、ECS等基础资源。真正的费用来源如下:
产品:大模型服务平台百炼
商品:百炼大模型推理
计费项:大模型文本消耗量
模型:glm-5.2
单位:千 tokens
非零的账单主要集中在一次短时间调用内:
input_token_cache
input_token
output_token
虽然金额不大,但问题的重点并非这次扣了多少钱,而是:
如果API Key泄露后未能及时发现,后续费用可能无限放大。
这正是我此次最担忧的一点。
二、初步判断:疑似API Key明文暴露
此次账单中的模型是glm-5.2。
而项目当时的主线模型配置并不应该直接使用这个模型。Web和Mobile的常规路径都有自己的模型列表,正常情况下不应随意将任意模型ID透传到DashScope。
但在排查代码时,我发现了两个风险点。
第一个风险点是模型解析逻辑过于宽松。
当请求体传入一个不在本地枚举中的模型时,旧逻辑会直接原样返回:
return model;
这意味着只要某个入口收到glm-5.2,服务端就可能直接将其转发给DashScope。
第二个风险点是services/ai这个独立AI服务仍然存在本地环境文件,并且历史上它的设计更像一个独立推理网关。如果这个服务被误部署、误启动,或者API Key出现在AI对话上下文、日志、截图、本地文件同步中,都可能形成泄露面。
因此我的判断是:
最可能的问题并非My-Notion主线业务主动消耗大量token,而是API Key明文暴露后,被外部或非预期调用链路所使用。
这个判断虽不等于完成了攻击溯源,但已足够指导修复工作。
三、立即处置:先止血
第一步是直接换掉阿里云API Key。
这是最重要的动作。一旦怀疑key泄露,就不应继续分析半天再决定是否更换。密钥一旦暴露,就应默认它已经不可控。
此次处理顺序如下:
- 轮换DashScope / 百炼API Key。
- 排查阿里云费用中心,确认费用来源。
- 检查项目里所有AI调用入口。
- 收紧模型白名单。
- 收紧独立AI服务鉴权。
- 验证Web、Mobile、CLI、MCP主链路。
换key只能止血,不能解决工程问题。真正的修复必须让类似问题以后更难发生。
四、核心修复:模型调用必须fail closed
此次最关键的代码修复是模型白名单。
现在项目的模型配置只允许以下几个模型:
kimi-k2.7-code
qwen3.7-max-2026-06-08
qwen3.7-plus
qwen3.7-plus-2026-05-26
默认模型为:
kimi-k2.7-code
旧逻辑是未知模型也可能被原样透传。
新逻辑改为:
只接受白名单模型。不在白名单内,直接返回400。不再透传未知模型到上游。
这个变化非常重要。
AI模型调用并非普通参数透传。模型ID直接决定计费对象、单价、token行为和功能能力。它必须像权限scope一样被服务端控制,而不能完全信任客户端请求体。
换句话说:
前端可以选择模型,但服务端必须决定哪些模型真的允许调用。
五、独立AI服务也加了防护
项目里还有一个services/ai目录。
现在Mobile主线已经切到Web路由:
Mobile -> Web /api/agent/stream
旧的/api/chat、/api/rag也只是兼容fallback,并且当前Mobile配置同样指向Web站点,并非独立services/ai。
也就是说,services/ai目前不是主线服务,更像一个备用部署方案。
但只要它还在仓库里,就不能让它保持一个容易被误用的状态。
所以这次也为services/ai增加了多层防护:
- 增加
AI_SERVICE_AUTH_TOKEN。 - 未配置token时,非health请求直接拒绝。
- 请求必须带Bearer token。
- 增加基础限流。
- 同样复用模型白名单,不允许任意模型ID透传。
这并非鼓励继续部署独立AI服务,而是防止它作为备用路径时变成新的风险入口。
如果未来确认不再需要services/ai,更彻底的做法是直接删除这个目录,同时清理Fly.io文档、workspace配置和相关脚本。
六、验证结果
修复后进行了多类验证。
AI / Web / Mobile / AI Service:
@notion/ai typecheck
@notion/web typecheck
@notion/mobile typecheck
@notion/ai-service typecheck
@notion/web build
@notion/ai-service build
CLI / MCP:
@mynotion/cli typecheck
@mynotion/mcp typecheck
@mynotion/cli test
@mynotion/mcp test
@mynotion/cli build
@mynotion/mcp build
真实链路E2E:
pnpm e2e:cli
pnpm e2e:mcp
pnpm e2e:mcp:client
其中CLI E2E还顺手暴露了一个测试脚本问题:CLI的prod endpoint已被设计成固定线上,不应因为本地保存配置而自动切换。E2E里临时PAT对应的是测试Machine API,所以脚本必须显式指定测试API URL。这个问题也已经修复。
最终结果如下:
- Web构建通过。
- Mobile类型检查通过。
- AI service类型检查和构建通过。
- CLI/MCP包测试通过。
- CLI真实create/fetch/update/search/export/import/archive/revoke/logout链路通过。
- MCP STDIO和真实MCP SDK Client调用链路通过。
七、这次的教训
此次问题让我重新确认了几条工程原则。
第一,API Key不能出现在任何会被复制、同步、上传、截图、喂给AI的地方。
本地.env虽然通常不会提交,但它仍然是高风险文件。如果将完整key贴进AI对话、日志、Issue、文档、截图,其风险与提交到仓库没有本质区别。
第二,LLM网关必须有服务端白名单。
模型ID、base URL、工具能力、是否启用联网、是否启用代码解释器,都不应完全由客户端决定。
第三,备用服务也要有默认安全状态。
一个“不再主线使用”的服务,如果还保留部署配置和启动脚本,就仍然是攻击面。它要么被删除,要么默认拒绝未授权请求。
第四,费用告警必须做。
代码防护只能降低风险,不能替代云平台侧的消费额度、预算告警和异常调用监控。尤其是大模型调用,token消耗非常容易在短时间内放大。
八、后续计划
此次已完成了止血和主链路修复。
后续我会继续完成以下几件事:
- 删除本地不再需要的明文key文件。
- 评估是否彻底移除
services/ai。 - 在阿里云侧设置百炼消费告警和额度限制。
- 检查历史文档、博客草稿、进度记录中是否出现过敏感key。
- 保持AI调用入口的白名单策略,避免再次回到任意模型透传。
结尾
这次虽然金额不大,但暴露了AI工程中一个看似微小却核心的安全问题——密钥与模型调用权限的管理,必须作为基础设施一样严格对待。修复的核心不只是换一个API Key,而是将整个调用链路改为更可控的形态:密钥轮换、模型白名单、服务端鉴权、基础限流、真实链路验证、云平台费用告警,缺一不可。对AI Native项目而言,能跑通模型调用只是第一步,真正重要的是,当模型调用涉及真实费用、真实数据和真实用户时,系统必须默认保守、边界清晰、可验证。