认识你的未知 · 实现前
← 全部示例

盲点扫描

在碰不熟悉的代码之前,让 Claude 挖出你还不知道自己不知道的事——并告诉你如何针对每个缺口改进提示词。

提示词
我要给 Acme 新增一个 SSO 认证提供方,但我从没碰过 auth 模块。请做一次盲点扫描:找出我在代码库这一部分的未知之未知,逐条解释,并告诉我如何针对实现更好地向你发起提示。
Claude 生成的内容
blindspot-pass · acme/services/auth · 已扫描 47 个文件

未知之未知:Acme auth 模块

已扫描 services/auth/(47 个文件),自 2025 年 1 月起有 214 次提交涉及该模块,3 个迁移进行中,2 次尝试被回滚。以下是七件你本会以惨痛方式才学到的事。

你的原始需求

「新增一个 SSO 提供方」——听起来像是:实现接口、注册、写重定向处理器,完事。在干净的代码库里,两天就能搞定。

你实际要面对的

一个会话存储正处在迁移中途的模块,有一个提供方悄悄绕过了大家都以为会跑的中介件此前针对同一任务的尝试已被回滚,还有一个从提供方接口根本看不出来身份关联模型

4 个地雷 2 条不成文约定 1 个缺失概念 1 次针对同一任务被回滚的尝试(PR #2841)
盲点一览
地雷 01

会话被双写——你先搜到的 Redis 存储是错的那个

Acme 正从 Postgres 会话迁移到 Redis(MIG-118,自三月起停滞)。每次会话写入目前都经 SessionBridge.write(),同时扇出到 RedisSessionStore 和旧版 pg_sessions 表。迁移从未完成,因此读取仍来自 Postgres。

为何会踩坑搜索「session」会先冒出 RedisSessionStore——它看起来像标准实现,代码最新、文档最好。直接往它写,开发环境登录看似正常(那里启用了 Redis 读取),生产环境却会静默失败,因为读取仍走 Postgres。
为新提供方创建会话时,一切都要经 SessionBridge 路由——绝不要直接写 RedisSessionStore 或 pg_sessions。在写任何会话代码之前,先解释 MIG-118 的读/写分离。
地雷 02

SAML 提供方——你显而易见的模板——绕过了 auth 中介件

除 SAML 外,所有提供方都挂在 authPipeline 下,它会跑限流、审计日志和设备信任检查。saml/handler.ts 把 ACS 端点直接挂到路由器上,因为 2024 年 SP 发起的 POST 回调弄坏了 CSRF 层,之后没人回头处理。有一条 14 个月前的 // TODO(priya): fold back into pipeline

为何会踩坑SAML 是最近新增的提供方,所以自然会被当作要复制的文件。照抄它,你的新提供方就会在没有限流、没有审计轨迹的情况下上线——任何测试都不会失败,因为契约测试并不断言中介件是否存在。
以 oauth/google.ts 为结构模板,不要用 saml/handler.ts——SAML 有意绕过 authPipeline。确认新提供方的路由挂载在 authPipeline 内,并给我看挂载点。
历史教训 03

有人已经试过——被回滚的原因至今仍然成立

PR #2841(「Add Okta OIDC provider」,2025 年 9 月)曾合并,九天后被回滚。回滚提交说明了原因:Acme 回调 URL 必须带租户后缀(/auth/callback/:provider/:workspaceSlug),这样工作区范围的邀请链接才能在重定向往返中保留。Okta 严格的 redirect-URI 匹配拒绝了动态后缀,被邀请用户落进了错误的工作区。大家在回滚讨论里达成一致的修复方案——用 AUTH_STATE_SECRET 签名的 state 参数工作区声明——设计了但从未实现。

为何会踩坑当前代码里没有任何线索。你会重新实现 #2841 的做法,通过所有本地测试(单工作区开发环境),然后在生产环境第一个被邀请用户登录时再次踩到同一个 bug。
先读 PR #2841 的回滚。实现它提出的带签名的 state 参数工作区声明,而不是带租户后缀的回调 URL,并为工作区范围的邀请流程加测试。
缺失概念 04

身份和账户是两回事——提供方接口把它藏起来了

Acme 把 usersidentities 分开:一个账户可以关联多个身份(邮箱+密码、Google、SAML),在 identities 表里联结。提供方接口只返回 profile;关联决策发生在 identity/linker.ts,以已验证邮箱为键。无法保证已验证邮箱的提供方(许多 SSO IdP 做不到)必须设置 requiresManualLink: true,否则 linker 会把共享企业别名的两个不同人愉快地合并到一起。

为何会踩坑天真地实现接口,一切看起来都正常——直到某客户的 IdP 发来未验证或已转派的邮箱,你的提供方就会悄悄把陌生人的身份挂到现有账户上。这是账户接管漏洞,不是登录 bug。
实现前先带我过一遍 identity/linker.ts。明确判断该 IdP 是否保证已验证邮箱;若不保证,设置 requiresManualLink 并搭建手动关联确认路径。
地雷 05

令牌刷新受特性开关控制——开发开着,生产关着

刷新令牌轮换由 flags.yaml 里的 auth.refresh_rotation 控制。开发/预发默认 true,生产默认 false(五月时钟偏移事故后暂停 rollout)。开关关闭时,生产会话恰好活 ACCESS_TOKEN_TTL(12 小时)然后硬过期——没有刷新路径。

为何会踩坑你写的任何刷新逻辑都会按开发环境行为测试,在生产却是死代码。更糟的是:若你的提供方假设会发生刷新,生产用户每 12 小时就会被登出,bug 报告会写「SSO 随机把我登出」,矛头指向你的代码。
按 auth.refresh_rotation=false(生产实况)实现。让提供方适配 12 小时硬过期;无刷新路径。另注轮换 rollout 恢复后会变什么。
约定 06

注册提供方要三步,只有两步能搜到

提供方能编译并不等于上线。必须 (1) 在 providers/registry.ts 注册,(2) 通过迁移写入 auth_providers 表——管理 UI 读的是数据库,不是 registry——以及 (3) 加入 __tests__/provider-contract.spec.ts 的 fixture 列表,该套件会对每个已注册提供方跑共享契约(state 校验、重放防护、登出扇出)。registry 与 fixture 列表不一致时 CI 会失败。

为何会踩坑跳过迁移,提供方能编译、测试通过,却根本不会出现在任何工作区的 SSO 设置里。两位工程师各自因此浪费了半天;这事不在任何人的文档里。
完成三步注册:registry.ts、auth_providers 迁移、provider-contract fixture 列表。在新提供方上跑通契约套件再收工。
地雷 07

登出不是函数调用——是事件,review player 在监听

会话撤销会在内部事件总线上发布 auth.session.revoked。实时 review player 持有自己的长期播放授权,只有消费该事件时才会释放。logout.ts 为现有提供方处理了这件事,但 SSO 单点登出(SLO)回调走另一条路径,必须自行发布该事件。

为何会踩坑漏掉事件,登出「看起来成功」——会话行已删——但任何开着 review 标签页的人仍能完整播放私有视频,直到授权 TTL 到期(最长 24 小时)。对向工作室卖 review 工作流的公司来说,这是安全事件,不是普通 bug。
处理 SLO:新提供方里任何撤销路径都必须发布 auth.session.revoked。加测试断言 IdP 发起的登出会释放 review player 的授权。
改进后的提示词

把上文全部折叠进你真正该给我的那条提示词

给 Acme 新增一个 SSO 认证提供方。来自盲点扫描的约束:

1. 会话:所有会话写入经 SessionBridge(MIG-118 未完成;生产仍从 pg_sessions 读)。绝不直接碰 RedisSessionStore。
2. 模板:结构以 oauth/google.ts 为准,不要用 saml/handler.ts——SAML 绕过 authPipeline。所有新路由挂载在 authPipeline 内,并给我看挂载点。
3. 历史:先读 PR #2841 的回滚。实现带签名的 state 参数工作区声明(AUTH_STATE_SECRET),不用租户后缀回调 URL;测试工作区邀请流程。
4. 身份关联:编码前带我过 identity/linker.ts。若 IdP 不保证已验证邮箱,设 requiresManualLink 并建确认路径——勿自动关联。
5. 特性开关:按 auth.refresh_rotation=false(生产)实现。会话 12h 硬过期;无刷新路径。另注轮换上线后会变什么。
6. 注册:三步全做——providers/registry.ts、auth_providers 迁移、provider-contract fixture 列表。契约套件全绿。
7. 登出:每条撤销路径须发布 auth.session.revoked;加测试断言 IdP 发起登出时 review player 播放授权被释放。

按此顺序:linker 走读 → 提供方骨架 → 回调 + state 声明 → 注册 → SLO + 事件 → 契约测试。走读结束后先停,给我看计划再写代码。

七句话,今早你还写不出来——每一条都是用别人半天的代价换来的。这就是盲点扫描的意义。