gu-log SP-251 繁體中文翻譯版。原作:Thariq (@trq212)Know your unknownsEnglish
認識你的未知 · 實作前
← 所有範例

盲點掃描

在碰陌生的程式碼之前,請 Claude 挖出你不知道自己不知道的事——並告訴你怎麼針對每個缺口改進 prompt。

Prompt
我要在 Acme 加一個新的 SSO 登入提供者,但我從來沒碰過 auth 模組。幫我做一次盲點掃描:找出我在這部分程式碼裡的未知未知,解釋每一個,並告訴我怎麼改進 prompt 來讓你更好地做實作。
Claude 產出的成品
盲點掃描 · acme/services/auth · 掃描了 47 個檔案

未知未知:Acme auth 模組

掃描了 services/auth/(47 個檔案),2025 年 1 月以來有 214 個 commit 動過它,3 個進行中的 migration,2 次被 revert 的嘗試。以下是七件你本來會吃虧才學到的事。

你要求的

「加一個新的 SSO 提供者」——聽起來像:實作一個介面、註冊它、寫 redirect handler,搞定。乾淨的程式碼庫上,兩天的任務。

你實際走進的

一個正在做 session storage migration 的模組,有一個 provider 偷偷繞過大家以為一定會跑的 middleware,有一次被 revert 的嘗試就是做你現在要做的事,還有一個身份連結模型從 provider 介面根本看不到。

4 個地雷 2 個沒寫下來的慣例 1 個缺失的概念 1 次被 revert 的同樣任務嘗試(PR #2841)
盲點清單
地雷 01

Session 是雙寫的——你會先找到的那個 Redis store 是錯的

Acme 正在從 Postgres-backed session 遷移到 Redis(MIG-118,三月起停滯)。目前每次 session 寫入都走 SessionBridge.write(),它會同時寫到 RedisSessionStore 和舊的 pg_sessions 表。遷移從未完成,所以讀取仍然來自 Postgres。

為什麼會踩雷grep「session」會先找到 RedisSessionStore——它看起來是正統的,有最新的程式碼和最好的文件。直接寫它,你的登入在 dev 環境會看起來正常(那邊啟用了 Redis 讀取),但在 prod 會默默失敗,因為那邊讀取還是走 Postgres。
為新 provider 建立 session 時,全部走 SessionBridge——絕不直接碰 RedisSessionStore 或 pg_sessions。在寫任何 session 程式碼之前,先解釋 MIG-118 的讀寫分離。
地雷 02

SAML provider——你最明顯的範本——繞過了 auth middleware

除了 SAML 之外,所有 provider 都掛在 authPipeline 下面,裡面跑 rate limiting、audit logging 和 device-trust 檢查。saml/handler.ts 的 ACS endpoint 直接掛在 router 上,因為 2024 年 SP-initiated POST callback 打壞了 CSRF 層,後來沒人回頭修。有一行 // TODO(priya): fold back into pipeline 已經 14 個月了。

為什麼會踩雷SAML 是最近才加的 provider,所以它是最自然會複製的檔案。複製它,你的新 provider 就會沒有 rate limiting、沒有 audit trail——不會讓任何測試失敗,因為 contract test 不檢查 middleware 有沒有跑。
用 oauth/google.ts 當結構範本,不要用 saml/handler.ts——SAML 是故意繞過 authPipeline 的。確認新 provider 的 route 掛在 authPipeline 裡面,然後給我看掛載點。
歷史 03

有人已經試過這件事——而且被 revert 的原因到現在還成立

PR #2841(「新增 Okta OIDC provider」,2025 年 9 月)被 merge 九天後被 revert。revert commit 解釋了原因:Acme 的 callback URL 必須帶 tenant 後綴(/auth/callback/:provider/:workspaceSlug),這樣 workspace 範圍內的邀請連結才能撐過 redirect 來回。Okta 嚴格的 redirect-URI 比對拒絕了動態後綴,被邀請的使用者會落在錯誤的 workspace。大家同意的修法——用 AUTH_STATE_SECRET 簽名的 state 參數 workspace claim——在 revert 討論串裡設計好了,但從沒實作。

為什麼會踩雷現有程式碼裡沒有任何提示。你會重新實作 #2841 的做法,通過所有本地測試(單一 workspace 的 dev 環境),然後在第一個被邀請的使用者登入時在 production 重新發現這個 bug。
先讀 PR #2841 的 revert。實作它提議的簽名 state 參數 workspace claim,而不是 tenant 後綴 callback URL,並加一個測試驗證 workspace 範圍邀請流程。
缺失概念 04

身份和帳號是分開的——而 provider 介面把它藏起來了

Acme 把 usersidentities 分開:一個帳號可以有多個連結的身份(email+密碼、Google、SAML),用 identities 表串起來。provider 介面只回傳 profile;連結決策發生在 identity/linker.ts,用驗證過的 email 當 key。無法保證 email 已驗證的 provider(很多 SSO IdP 都不保證)必須設 requiresManualLink: true,否則 linker 會開心地把共用公司別名的兩個不同人合併在一起。

為什麼會踩雷照樣實作介面,一切都能動——直到某個客戶的 IdP 送來未驗證或被重新指派的 email,你的 provider 就會默默把陌生人的身份附加到現有帳號上。這是帳號接管漏洞,不是登入 bug。
在實作之前先帶我走一遍 identity/linker.ts。明確決定這個 IdP 有沒有保證驗證過的 email;如果沒有,設 requiresManualLink 並建立手動連結確認流程。
地雷 05

Token refresh 在一個 flag 後面,dev 開著、prod 關著

Refresh-token rotation 受 flags.yaml 裡的 auth.refresh_rotation 控制。dev 和 staging 預設 true,prod 預設 false(五月一次時鐘偏移事件後暫停 rollout)。flag 關掉時,prod session 剛好活 ACCESS_TOKEN_TTL(12 小時)然後硬過期——沒有 refresh 路徑。

為什麼會踩雷你寫的任何 refresh 邏輯都會在 dev 行為下測試,在 prod 變成死碼。更糟:如果你的 provider 假設會 refresh,prod 使用者每 12 小時會被登出一次,bug 報告會寫「SSO 隨機把我登出」,指向你的程式碼。
以 auth.refresh_rotation=false(prod 現實)來實作。讓 provider 能在硬性 12 小時過期下運作,另外註明如果 rotation rollout 恢復要改什麼。
慣例 06

註冊一個 provider 需要三個步驟,只有兩個能被發現

一個 provider 能編譯不代表它就上線了。它必須 (1) 在 providers/registry.ts 註冊,(2) 透過 migration 插入 auth_providers 表——admin UI 讀的是 DB,不是 registry——以及 (3) 加到 __tests__/provider-contract.spec.ts 的 fixture 清單,那裡會對每個註冊的 provider 跑共用 contract suite(state 驗證、replay 保護、logout fan-out)。如果 registry 和 fixture 清單不一致,CI 會失敗。

為什麼會踩雷跳過 migration,provider 能編譯、通過測試,但就是不會出現在任何 workspace 的 SSO 設定裡。有兩個工程師各自在這上面浪費了半天;沒有人的文件裡有寫。
三個註冊步驟都做:registry.ts、一個 auth_providers migration、provider-contract fixture 清單。給我看新 provider 的 contract suite 全綠才算完成。
地雷 07

登出不是一個函式呼叫——它是一個 event,而且 review player 在聽

Session revocation 會在內部 event bus 發布 auth.session.revoked。即時 review player 有自己的長效 playback grant,只有在消費到那個 event 時才會釋放。logout.ts 替現有 provider 處理這件事,但 SSO 單點登出(SLO)callback 走另一條路徑,必須自己發布 event。

為什麼會踩雷漏掉 event,登出「會動」——session 那列消失了——但任何開著 review 分頁的人會繼續對私人影片有完整 playback 存取,直到 grant TTL 過期(最多 24 小時)。對於一家賣 review workflow 給工作室的公司,這是資安事件,不是 bug。
處理 SLO:新 provider 的任何 revocation 路徑都必須發布 auth.session.revoked。加一個測試斷言 IdP 發起的登出會釋放 review player 的 grant。
改進後的 Prompt

以上全部,整合成你應該真正給我的 prompt

幫 Acme 加一個新的 SSO 登入提供者。盲點掃描的限制條件:

1. Session:所有 session 寫入都走 SessionBridge(MIG-118 沒做完;
   prod 還是從 pg_sessions 讀)。絕不直接碰 RedisSessionStore。
2. 範本:結構以 oauth/google.ts 為準,不要用 saml/handler.ts——
   SAML 是故意繞過 authPipeline 的。所有新 route 掛在 authPipeline
   裡面,給我看掛載點。
3. 歷史:先讀 PR #2841 的 revert。實作簽名的 state 參數 workspace
   claim(AUTH_STATE_SECRET),而不是 tenant 後綴 callback URL,
   並測試 workspace 邀請流程。
4. 身份連結:在寫程式碼之前先帶我走一遍 identity/linker.ts。如果
   這個 IdP 不保證驗證過的 email,設 requiresManualLink 並建立
   確認流程——不要自動連結。
5. Flag:以 auth.refresh_rotation=false(prod)來實作。Session
   12 小時硬過期;沒有 refresh 路徑。註明如果 rotation 上線要改
   什麼。
6. 註冊:三個步驟都做——providers/registry.ts、一個 auth_providers
   migration、provider-contract fixture 清單。給我看 contract
   suite 全綠。
7. 登出:每個 revocation 路徑都必須發布 auth.session.revoked,
   並加一個測試斷言 IdP 發起的登出會釋放 review player 的
   playback grant。

按這個順序做:linker 走讀 → provider skeleton → callback + state
claim → 註冊 → SLO + events → contract tests。走讀完之後先停下來
給我看計劃,再寫程式碼。

七句話是你今天早上寫不出來的——每一句都是別人花半天換來的。這就是盲點掃描的意義。