一次阿里云百炼异常扣费的排查与修复总结

作者:袖梨 2026-07-03

最近 My-Notion 项目里遇到了一次阿里云账户欠费问题。

一次阿里云百炼异常扣费的排查和修复总结

一开始看到账户欠费时,我的第一反应是:是不是某个 AI 功能调用量太大,或者 RAG/Embedding 在后台反复跑了。

排查后发现,问题没有那么复杂,但性质更值得警惕。

账单里真正产生费用的是阿里云百炼的大模型推理服务,模型调用集中在很短的时间窗口内,主要是 glm-5.2 的文本 token 消耗。结合项目代码和本地环境来看,我更倾向于判断这是一次 API Key 明文暴露后导致的异常调用。

严格说,现有证据不能 100% 证明是谁拿到了 key,也不能直接证明攻击来源。但从账单行为、模型名称、调用时间和项目配置看,这已经足够说明:密钥管理和模型调用入口必须收紧。

这篇文章记录一下这次问题是怎么定位的、怎么修复的,以及后续需要避免什么。

一、问题现象

阿里云费用中心显示账户欠费。

进一步看账单后,费用来源基本可以排除 OSS、ECS 这类基础资源。真正的费用来自:

产品:大模型服务平台百炼商品:百炼大模型推理计费项:大模型文本消耗量模型:glm-5.2单位:千 tokens

非 0 的账单主要集中在一次短时间调用里:

input_token_cacheinput_tokenoutput_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 泄露,就不应该继续分析半天再决定要不要换。密钥一旦暴露,就应该默认它已经不可控。

这次处理顺序是:

  1. 轮换 DashScope / 百炼 API Key。
  2. 排查阿里云费用中心,确认费用来源。
  3. 检查项目里所有 AI 调用入口。
  4. 收紧模型白名单。
  5. 收紧独立 AI 服务鉴权。
  6. 验证 Web、Mobile、CLI、MCP 主链路。

换 key 只能止血,不能解决工程问题。真正的修复必须让类似问题以后更难发生。

四、核心修复:模型调用必须 fail closed

这次最关键的代码修复是模型白名单。

现在项目的模型配置只允许这几个模型:

kimi-k2.7-codeqwen3.7-max-2026-06-08qwen3.7-plusqwen3.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:clipnpm e2e:mcppnpm 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 工程里的安全问题,很多时候不是复杂攻击,而是一个 key、一个默认开放的接口、一个过于宽松的透传逻辑。

修复这个问题不只是换一个 API Key,而是把调用链路改成更可控的形态:

密钥轮换模型白名单服务端鉴权基础限流真实链路验证云平台费用告警

对 AI Native 项目来说,能跑通模型调用只是第一步。真正重要的是:当模型调用涉及真实费用、真实数据和真实用户时,系统必须默认保守、边界清晰、可验证。

相关文章

精彩推荐