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

时间:2026-07-04 08:57:47 来源:互联网

近期在处理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泄露,就不应继续分析半天再决定是否更换。密钥一旦暴露,就应默认它已经不可控。

此次处理顺序如下:

  1. 轮换DashScope / 百炼API Key。
  2. 排查阿里云费用中心,确认费用来源。
  3. 检查项目里所有AI调用入口。
  4. 收紧模型白名单。
  5. 收紧独立AI服务鉴权。
  6. 验证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增加了多层防护:

  1. 增加AI_SERVICE_AUTH_TOKEN
  2. 未配置token时,非health请求直接拒绝。
  3. 请求必须带Bearer token。
  4. 增加基础限流。
  5. 同样复用模型白名单,不允许任意模型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。这个问题也已经修复。

最终结果如下:

  1. Web构建通过。
  2. Mobile类型检查通过。
  3. AI service类型检查和构建通过。
  4. CLI/MCP包测试通过。
  5. CLI真实create/fetch/update/search/export/import/archive/revoke/logout链路通过。
  6. MCP STDIO和真实MCP SDK Client调用链路通过。

七、这次的教训

此次问题让我重新确认了几条工程原则。

第一,API Key不能出现在任何会被复制、同步、上传、截图、喂给AI的地方。

本地.env虽然通常不会提交,但它仍然是高风险文件。如果将完整key贴进AI对话、日志、Issue、文档、截图,其风险与提交到仓库没有本质区别。

第二,LLM网关必须有服务端白名单。

模型ID、base URL、工具能力、是否启用联网、是否启用代码解释器,都不应完全由客户端决定。

第三,备用服务也要有默认安全状态。

一个“不再主线使用”的服务,如果还保留部署配置和启动脚本,就仍然是攻击面。它要么被删除,要么默认拒绝未授权请求。

第四,费用告警必须做。

代码防护只能降低风险,不能替代云平台侧的消费额度、预算告警和异常调用监控。尤其是大模型调用,token消耗非常容易在短时间内放大。

八、后续计划

此次已完成了止血和主链路修复。

后续我会继续完成以下几件事:

  1. 删除本地不再需要的明文key文件。
  2. 评估是否彻底移除services/ai
  3. 在阿里云侧设置百炼消费告警和额度限制。
  4. 检查历史文档、博客草稿、进度记录中是否出现过敏感key。
  5. 保持AI调用入口的白名单策略,避免再次回到任意模型透传。

结尾

这次虽然金额不大,但暴露了AI工程中一个看似微小却核心的安全问题——密钥与模型调用权限的管理,必须作为基础设施一样严格对待。修复的核心不只是换一个API Key,而是将整个调用链路改为更可控的形态:密钥轮换、模型白名单、服务端鉴权、基础限流、真实链路验证、云平台费用告警,缺一不可。对AI Native项目而言,能跑通模型调用只是第一步,真正重要的是,当模型调用涉及真实费用、真实数据和真实用户时,系统必须默认保守、边界清晰、可验证。