FR-076 · 需求索引 · 本頁由 build 掃資料夾生成

FR-076 License 簽發與驗證鏈資安掃描

狀態:四棒掃完;1572/1580/1581 驗收通過;1573/1583/1584 待派;1579/1582 暫緩 文件 4 份 handoff 1 份

用 Claude Code 的 claude-security plugin 掃 License 簽發與驗證鏈,找資安問題。 只掃不修——產出是問題清單,修正另外開卡。

§1

為什麼要做

產品的授權機制由兩端組成:license_center 負責簽照(私鑰在這裡), jedi-license-runtime 裝在主產品裡負責驗照。兩端從來沒有被系統性掃過。

這比 jedi-iam 更值得掃,因為攻擊者的動機明確且獲利直接:繞過授權等於免費使用整套產品。 而 jedi-iam 的漏洞多半要先有帳號或有網路位置。

涵蓋空白的證據:2026-07 的資安掃描(security-scan-2607)只掃主專案 BE 與 FE; FR-075 掃的是 jedi-common 與 jedi-iam。License 鏈完全沒被碰過。

§2

產品時程脈絡(決策者 2026-09-06 標記)

第一個對外版本是落地版(Host),SaaS 版機制要預留但不是第一版。裁決修正優先序時用這個判準: 只影響 SaaS 的問題(停權、多租戶競態)綁 SaaS 上線前;落地版也打得到的問題(驗章、解壓炸彈、 機器指紋)綁落地版出貨前。落地版一台機器一張照綁頂層客戶(D8),子租戶不另發照。

§3

首腦讀過程式碼後認為最值得看的幾點

  • 兩種信封格式共存verify_payload 同收 v2(format_version: 2)與 v1(明文 dict), 兩條路徑的檢查是否等價、能否降級攻擊
  • 三種憑證共用一支驗章與同一組公鑰(license/manifest/unlock_token), 且 payload_type 缺鍵時預設當 license——型別混淆的空間
  • 公鑰硬編三把(DEV/STG/POC):用 DEV 私鑰簽的照,POC 機器也會認。刻意還是疏漏?
  • 機器指紋 fallback 到 MAC:讀不到 /etc/machine-id 就用 uuid.getnode(), 攻擊者能否讓它變成可控值(FR-064 曾發生容器空檔導致指紋漂移)
  • license runtime 的端點自己不掛守門,靠宿主注入 decorator——有沒有漏掛的
  • license_center 是對外網頁後台:登入、CSRF、模板注入、API token, 攻擊面性質與其他三棒完全不同
§4

四棒切分

範圍 repo 檔數 卡號 狀態
L1 License 驗章核心(密碼學層) jedi-license-runtime 10 CM-1567 已完成 — 1 HIGH(範圍內)
L2 License 授權狀態與 API jedi-license-runtime 33 CM-1568 已完成 — 1 LOW(範圍內)+1 HIGH(人工發現)
L3 License Center 簽發核心與模組 license_center 32 CM-1569 已完成 — 1 HIGH+1 MEDIUM(範圍內)
L4 License Center 網頁後台 license_center 43 CM-1571 已完成 — 4 MEDIUM(範圍內)

L1 已完成(2026-09-06):55 分鐘一次跑完、零 stalled,面板 verified。 範圍內找到 1 條 HIGH——驗章前的無界解壓縮,任何登入者用 1.4MB 請求可讓後端配置 1GB 記憶體、 打掛 API(落地版單容器等於全產品停擺)。詳見 scan-L1-verification-core.md

「Opus 1M + 小範圍」組合驗證成立:對照 FR-075 用 Sonnet 跑 40 檔以上四棒全滅共燒 29.6 小時, L1 用 Opus 1M 掃 10 檔 55 分鐘一次跑完。L2~L4 照此設定跑,可平行。

L2 已完成(2026-09-06):33 檔 90 分鐘一次跑完、零 stalled,面板 verified。 掃描在範圍內只找到 1 條 LOW(跨租戶重複用照的競態),另 1 條是 L1 已報過的同一個洞(越界)。 但人工復核時另外發現 1 條 HIGH:被停權的租戶只要自己上傳一張舊照,唯讀就解除—— 停權旗標寫在照的列上,而換照會建新列且不繼承。詳見 scan-L2-license-state-api.md

L4 已完成(2026-09-06):43 檔(四棒最大)110 分鐘一次跑完、零 stalled,面板 verified。 範圍內找到 4 條 MEDIUM,無 HIGH,全部集中在兩處:線上開通端點/api/activation/activate, 唯一免認證的對外入口)與登入導轉。開通序號只有 32 bits 且防猜鎖定「用被猜的序號當 key」等於沒鎖, 可暴力猜到別人的照;防猜用的記憶體結構無上限且查詢時也寫入,免認證即可撐爆 worker; 登入後的 next 未驗證可用來釣管理密碼。同 repo 的後台登入早就做對了(存 DB、計 IP、 docstring 還寫明為什麼不能存記憶體),開通端點沒套用——是遺漏不是不懂。 詳見 scan-L4-web-backoffice.md

L3 已完成(2026-09-06):32 檔 129 分鐘一次跑完(1 次 agent stall 自動重試成功),面板 verified。 範圍內找到 1 HIGH + 1 MEDIUM,兩條是同一件事的兩面:開通序號只有 8 個十六進位字(32 bits), 而專門「把序號遮起來再寫 log」的那支函式遮罩取前 8 碼——序號剛好 8 碼,等於整串印進 journal。 兩處各自的假設都合理(logging 假設「前綴太短猜不出」、licensing 假設「夠亂不必更長」), 但沒有任何一處把兩個假設對起來。守門測試因為餵 18 字元 fixture,這個洞一直是綠燈。 另 3 條是研究員讀出範圍外的 web/ 層,與 L4 報過的完全對應(兩棒獨立掃描給出一致判定, 算是一次交叉驗證),不重複開卡。詳見 scan-L3-issuance-core.md

L3 順帶釐清一件事:L1 判 HIGH 的解壓炸彈(CM-1572)在 LC 這一側被面板 0-3 否決, 兩邊不衝突——LC 沒有任何 HTTP 端點會收外部照檔(verify_payload 只被本機 CLI 與 「讀自己 DB 自己簽的照」呼叫),沒有攻擊者可控輸入源。但 engine.py 兩邊同源, CM-1572 修的時候要確認兩邊都修到

§5

修正卡(L1/L2 復核後開)

卡號 修什麼 來源 嚴重度 狀態
CM-1572 驗章前無界解壓縮:有界解壓+長度預檢+host MAX_CONTENT_LENGTH,含 jedi-integrity 兩處同款 L1 F1,面板 3/3 HIGH ✅ ①② 驗收通過(套件 7091e04,首腦跑 62+90 測試綠);③ MAX_CONTENT_LENGTH 待決策者裁值
CM-1573 jedi-issue PAT 外洩處置:確認撤銷、清 Nexus 舊版與 dist、pyproject 排除 .env L1 越界 F2,面板 2/3 MEDIUM ⬜ 待派
CM-1579 停權可被租戶換照解除:方案 A 定案(停權移租戶層),SaaS 上線前修,不擋落地版 L2 人工發現,工具未報 HIGH(落地版低、SaaS 高) ⏸ 暫緩至 SaaS 前
CM-1580 跨租戶重複用照 TOCTOU:license_id 部分唯一索引(僅 is_current)+接 IntegrityError L2 F2,面板通過 LOW ✅ 驗收通過(套件 6cf1879+BE f3c58696,DEV 索引已建首腦查過;STG/POC 等令)
CM-1581 build_bundle.sh 公鑰守門路徑失效:FR-069 搬遷後檢查的檔不存在,改用 python 解析套件路徑 首腦復核 L1 公鑰議題時發現 小 fix ✅ 已修(4b527704),首腦 dry-run 驗過
CM-1583 LC 開通端點防猜失效+計數表無上限+log 遮蔽函式整串印出+補發復活舊序號:七項 L4 F2+F3+F4 + L3 F1+F2 + L3 runner 追出,面板各 3/3 MEDIUM→HIGH(log 洩憑證),客戶 BE 打得到 ⬜ 待派,第 ⑦ 項先問補發語意
CM-1584 LC 登入 next 參數開放導轉:urlsplit 擋外站 L4 F1,面板 3/3 MEDIUM ⬜ 待派
(建議併入 CM-1583) 開通序號熵只有 32 bits + 遮蔽函式整串印出:secrets.token_hex(16)、未兌換序號加到期、activation_code_digest 改印 keyed hash 或 license_id、補真實長度守門測試 L3 F1+F2,面板各 3/3 HIGH(F1)+MEDIUM(F2) ⬜ 待首腦裁定併卡或另開
CM-1582 客戶包公鑰白名單:出包按環境只編 PROD 鑰、DEV 鑰不進客戶包 L1 三環境硬編公鑰議題 綁正式 LC ⏸ 暫緩至正式 LC

三環境硬編公鑰的裁決(決策者 2026-09-06):現階段 POC、無外部客戶、三把鑰都在自己機器上,不急; 綁正式 LC 建立時一併處理。但首腦查到 build 守門本身壞了(檢查的路徑在 FR-069 搬遷後不存在, 現在每次出包都帶 --skip-prod-key-check 所以沒發現),這是小 fix 現在就修(CM-1581)。

L2 兩條的處置:掃描報的 F1 越界(common/engine.py 屬 L1 範圍)且與 L1 F1 同一條, 已由 CM-1572 涵蓋,不重複開卡。F2 在範圍內但掃描建議的全表唯一索引會擋掉正當的「換回舊照」流程, 報告內已改為部分唯一索引。人工發現的停權繞過建議優先於掃描報出的兩條——它不會被下一輪掃出來。

L1 三條發現的處置:F1 在範圍內開 CM-1572。F2 越界(jedi-issue)但屬憑證外洩,首腦用 unzip -l 確認 .env 仍在 0.0.14/0.0.15 wheel 內,獨立開 CM-1573。F3 越界(jedi-iam Turnstile)是第三次被獨立確認, 併入 FR-075 既有的 CM-1558 不另開。

首腦裁決待辦:三環境硬編公鑰(DEV 私鑰簽的照 POC 也認)——研究員讀過 public_keys.py 沒報, 可能判定為刻意設計。這在程式碼裡是明擺著的事實,由決策者直接裁定是設計還是疏漏,不等下一輪掃描。

「Opus 1M + 小範圍」組合驗證成立,L2/L3/L4 照同設定跑(三棒之間無衝突,可平行)。 但要注意研究員再次越界讀檔(與 FR-075 S7 同一模式):3 條面板存活發現裡只有 1 條在指派範圍內, 另 2 條分別指向 jedi-issue 與 jedi-iam。面板只驗「發現成不成立」、不驗「是否在範圍內」, 收報告時必須自己挑掉範圍外的。

§6

共同設定(FR-075 換來的教訓)

  • 主 session 一律 Opus 5 (1M context),不要用 Sonnet——FR-075 的 S4~S7 用 Sonnet 跑 40 檔以上全滅,四棒共燒 29.6 小時零產出。研究員繼承主 session 的模型與 context 上限, plugin 把研究員寫死 effort: xhigh,200K 撐不住就會被 workflow 的 180 秒 stall 門檻砍掉重派。
  • effort low、focus attack-surface,每棒控制在 40 檔以內
  • 啟動前驗 scope 檔數,對上卡片數字才跑
  • 報告要看 stamp 檔的 verification確認面板真的跑完,不要只信研究員自述
  • 零發現不等於乾淨——要分清「讀過但沒找到」與「根本沒讀到」
§7

🔴 私鑰紀律

license_center/keys/private.pem 是正式簽發私鑰,在工作目錄內、掃描 agent 讀得到 (已確認未入版控)。任何情況下都不可把私鑰內容、passphrase 或金鑰材料寫進報告、 commit、Notion 或任何落地檔案。 報告指涉金鑰時只寫路徑與行為描述,不寫值。 L3/L4 兩張子卡內有同樣的警語。

§8

相關

  • FR-075 — jedi-common/jedi-iam 掃描, 工具用法與成本教訓見其 scan-methodology.md
  • FR-062 — License 控管設計全文
  • FR-064 — 防竄改偵測,與 manifest/unlock_token 共用同一支驗章
§9

文件

以下全部由 build 掃資料夾產生,新增檔案重 build 即自動出現。標題連結指向渲染後的 HTML,md 連向源檔。

其他文件

文件 類型 標題 最後更新
scan-L1-verification-coremd 文件 L1 掃描結果:License 驗章核心(密碼學層) 2026-09-06
scan-L2-license-state-apimd 文件 L2 掃描結果:License 授權狀態與 API 2026-09-06
scan-L3-issuance-coremd 文件 L3 掃描結果:License Center 簽發核心與模組 2026-09-06
scan-L4-web-backofficemd 文件 L4 掃描結果:License Center 網頁後台 2026-09-06

交接與收口時間軸(handoff/,1 份)

由新到舊。每份是某一棒次交接當下的完整現況快照,看某個時間點「當時知道什麼」請從這裡進。

日期 文件 標題
2026-09-06 READMEmd FR-076 handoff
§10

Notion 卡

卡片內容(決策紀錄、驗收條件)以 Notion 為準,本頁只記座標。

關係 卡號 標題 狀態
母案 CM-1566 FR-076 License 簽發與驗證鏈資安掃描(4 棒,只掃不修)
子卡 CM-1567 L1 License 驗章核心(密碼學層,10 檔) 修正待驗證
子卡 CM-1568 L2 License 授權狀態與 API(33 檔) 修正待驗證
子卡 CM-1569 L3 License Center 簽發核心與模組(32 檔) 修正待驗證
子卡 CM-1571 L4 License Center 網頁後台(43 檔) 修正待驗證