一个工具请求的 OAuth 作用域,为何被其他所有工具悄悄继承了?
设计文档规定每个工具都拥有独立的 OAuth 令牌,并被限制在该工具所需的最小权限范围内。而实现代码则使用 (user_id, provider) 作为键(key)来存储令牌。在 v1 版本发布当天,这两个表述都是成立的,因为当时每个提供商(provider)恰好只有一个工具。当针对同一提供商的第二个工具上线时,设计文档依然成立,但存储层却在悄无声息中使其失效了。
六个月后,一次安全审查将一起事故追溯到了那行模式(schema)定义。一个日历读取工具通过日程描述中的提示词注入(prompt injection)被攻破,并成功调用了用户主日历上的 events.delete 接口。读取工具从未被授予过该作用域(scope),但写入工具被授予了。令牌存储层并没有区分它们。
这种故障模式在于,基于每个提供商(per-provider)的键结构会在共享同一提供商的工具之间悄悄累积权限——这也让人们在架构上意识到:OAuth 作用域是令牌(token)的属性,而不是工具(tool)的属性。
