U13b 檢查結果:主專案組裝表七支(di_containers/)

U13b 檢查結果:主專案組裝表七支

檢查日期 2026-09-26|對應卡片 CM-2167(母卡 CM-2152)|檢查範圍 7 個檔案 996 行|工具 run ID wf_21bd999c-982(報告目錄 CLAUDE-SECURITY-20260926-024832)|驗證章 verified

§1

🔴 一句話結論

這七支組裝表沒有漏接任何安全檢查零件,淨新增 0 條。 工具報了 4 條,三人面板都是 3:0 通過,但全是總表已經登記過的舊帳(第 188、192、65 項,以及 CM-1631 金鑰外洩)。四條沒有一條是組裝表本身寫錯:研究員順著組裝表追進它組出來的服務,在服務裡撿到的。

卡片要我回答的三件事:

卡片問的 答案
總組裝表(containers.py)有沒有漏登記哪個容器 沒有漏。全專案 36 個子組裝表逐一比對,36 個都有登記;每個子組裝表向外要的零件(共 107 個插槽)只缺 1 個,是一個宣告了卻從來沒人用的空插槽,無害
登記清單(哪些程式要自動拿零件)有沒有漏掛,漏掛的網址會怎樣 沒有漏掛。全部 62 支用到「自動拿零件」的程式,60 支在清單上,另外 2 支是自己在說明文字裡寫了範例、實際不需要掛。就算真的漏掛,實測結果是一呼叫就報錯(500),不會靜默改用別的東西
建構子有「預設是空」的守門零件,組裝表沒接時是拒絕還是放行 七支範圍內只有摘要報告的兩支服務屬於「沒接就放行」的寫法,但組裝表有接,我把組裝表實際建起來,確認拿到的是真零件。比對腳本在這七支標出的 6 處「可選未接」,逐一核過全都不是守門零件
§2

這一棒在檢查什麼

系統啟動時,主專案要把幾百個「零件」(資料服務、權限檢查、授權檢查……)組起來,交給每個網址入口使用。負責「誰拿到哪個零件」的就是這批組裝表(DI 容器:Dependency Injection,依賴注入,意思是程式不自己建零件,由組裝表統一分配)。

這類問題的危險在於安靜:組裝表少接一個安全檢查零件,服務不會報錯,只會把那道檢查跳過去。之前在合規稽核套件、SSP 套件都發生過。

檔案 行數 組裝的是什麼
di_containers/containers.py 397 總組裝表。所有子組裝表在這裡登記,並互相接線
di_containers/cloud_integration/cloud_integration_containers.py 310 雲端硬碟整合(U4~U6 那三棒的服務)
di_containers/license/license_containers.py 84 商務授權檢查(U1 的 license.py 從這裡拿零件)
di_containers/support/support_containers.py 82 系統診斷包(U10/U11)
di_containers/project/project_summary_report_containers.py 48 專案摘要報告(U9)
di_containers/setup/setup_containers.py 41 首次安裝精靈(U3)
di_containers/identity/identity_containers.py 34 審計欄位補暱稱用的名冊
§3

卡片「重點看什麼」逐條回答

1. 總組裝表有沒有漏登記容器

沒有。 我寫了一支小腳本,把每個子組裝表宣告「我需要外面給我哪些零件」的插槽,和總組裝表實際塞進去的東西逐一配對(包含後面那些「回頭補接」的寫法,因為有些容器互相需要,只能先登記一個再回頭補)。

  • 全專案 di_containers/ 底下 36 個子組裝表,36 個都在總組裝表登記,沒有孤兒。
  • 子組裝表向外要的插槽共 107 個,只有 1 個沒接:di_containers/associations/associations_containers.py:45 的 job_execution_container。全專案搜尋,沒有任何地方用到這個插槽,是一個宣告了卻從沒用過的殘骸,無害(順手刪掉即可,不必開卡)。
  • 補充:沒接的插槽如果真的被用到,實測會直接報錯(「Dependency ... is not defined」),不會悄悄給空值。所以「漏登記」在這套寫法下會變成一眼就看得到的錯,不是安靜的缺線。

2. 登記清單有沒有漏掛程式(config/di_modules.py 那份清單)

總組裝表另外有一份「哪些程式要自動拿零件」的清單。漏掛的程式不會在啟動時報錯,會拿到一個佔位物件。

  • 我用清單本身的產生邏輯跑出 77 支,再搜尋全專案 api/ app/ common/ core/ config/ 底下真的用到「自動拿零件」的程式 62 支。只有 2 支不在清單上:
    • common/authz/license.py:只在檔頭說明裡寫了一段範例,自己不需要自動拿零件(它是在呼叫當下從容器直接取,:191、:197)。
    • config/socketio_namespaces.py:同樣只是說明文字提到。
  • 漏掛會發生什麼(實測):做一個沒掛上的假入口去呼叫,拿到的是佔位物件,一使用就 AttributeError(使用者看到 500)。不會靜默改用預設值、也不會跳過檢查。
  • U13a 已經從「清單順序」那面核過同一份檔案,結論一致。

3. 授權檢查的零件對不對得上(license_containers.py)

對得上。 組裝表給 LicenseGuard 的三個零件,就是它建構子要的三個(common/authz/license.py:153-154),沒有多、沒有少、沒有預設值可以偷懶。

卡片擔心的「兩條接線路徑只給一邊」也核過:租戶目錄(用來判斷「子公司繼承母公司的授權」)在組裝表這邊有給(license_containers.py:59),另一條網址用的接線在 core/plugins/license.py:334 也有給,兩邊都接上了。套件那端如果沒拿到租戶目錄,會記一行警告並退回「只看自己的授權」(jedi-license-runtime license_tenant_resolver.py:44),所以這個零件漏接屬於安靜型缺線,但目前兩邊都有接。

另外兩個「預設是空」的參數也看過:套件裡授權服務的「停權紀錄」零件預設是空、空的時候當作「沒人被停權」(套件原始碼自己註明是刻意不擋),但組裝表有接(license_containers.py:74),不觸發。

4. 雲端硬碟、診斷包、摘要報告、安裝精靈四支,對照各業務棒的發現

  • 雲端硬碟(U4~U6):我把組裝表實際載入、逐一核對每個零件的建構子,沒接的只有三個資料存取物件的 session 參數。那是留給測試塞假連線用的,正式執行時從目前的交易自動取,不是缺線。U5/U6 查到的「初始化資料夾不檢查專案歸屬」(第 192 項)是服務本身就沒寫檢查,不是組裝表沒接;同一張表裡需要檢查的 drive_project_verify_service,專案成員零件有接(cloud_integration_containers.py:308)。加密金鑰(:109)沒設的話,建構時直接報錯(fernet_crypto.py:8-11),不會退回沒加密。
  • 診斷包(U10/U11):三支服務的零件全部有接。兩支對外服務的守門是寫在程式裡、不靠零件(require_platform_admin(),diag_bundle_export_service.py:74、recent_error_app_service.py:46),組裝表沒辦法讓它們放行。U10 已確認這組容器只接了兩條網址,與我看到的一致。
  • 摘要報告(U9):這是七支範圍裡唯一一處「零件沒接就放行」的寫法(下一節詳述),但有接。
  • 安裝精靈(U3):SetupWizardService 的七個零件全部是必填,沒有預設值,組裝表七個都有給,少一個啟動時就會報錯。

5. 帶著 DI 比對腳本的結果來看

比對腳本(di-wiring-audit.md)標紅的 3 處,都不在這七支裡(在 auth_containers.py 與 oscal_containers.py),屬於 U13a/首腦串接縫的範圍。我還是順手開了套件原始碼看一眼,給首腦參考:

腳本標紅 我看到的 判斷
TenantProvisioningService.default_role_name(auth_containers.py:269) 預設值是字串 "System Manager"(jedi-iam tenant_provisioning_service.py:47),是「新公司預設建哪個角色名稱」 不成立。參數名含 role 被腳本誤判,其實是設定值,不是守門零件
MetadataCloneService.role_repo(oscal_containers.py:177) 沒給時套件自己建一個(metadata_clone_service.py:54,role_repo or RoleRepoImpl()),用途是複製 OSCAL 文件裡的「角色」欄位資料 不成立。這是資料存取物件,不是權限檢查;沒接也不會變空
OscalIoService.role_repo(oscal_containers.py:206) 同上(oscal_io_service.py:199) 不成立,理由同上

比對表「可選未接:預設 None」那節裡屬於這七支的 6 處,逐一核過:

位置 缺的參數 是什麼 判斷
cloud_integration_containers.py:97 TenantDriveIntegrationRepoImpl.session 測試用的假連線入口,正式執行自動取目前交易 不是缺線
cloud_integration_containers.py:131 DriveFolderMappingRepoImpl.session 同上 不是缺線
cloud_integration_containers.py:132 DriveSyncJobRepoImpl.session 同上 不是缺線
identity_containers.py:31 IdentityContext.tenant_resolver 把「公司編號」補成公司名稱,只影響顯示 不是守門;沒接時回空、畫面顯示編號(套件刻意設計)
identity_containers.py:31 IdentityContext.org_unit_resolver 把「部門編號」補成部門名稱,只影響顯示 同上
support_containers.py:54 DiagLogReader.basename 日誌檔名,預設 app.log 設定值,不是缺線

我另外用程式實際載入這六支子組裝表、逐一比對建構子(不靠靜態腳本),結果與腳本完全一致,沒有腳本漏掉的項目。

§4

「零件沒接就放行」:摘要報告那一處

project_summary_report_service.py:40 的寫法是:專案成員檢查零件如果是空的,直接放行。同樣的寫法在 project_summary_report_history_service.py:57 也有。

  • 現況:組裝表有接(project_summary_report_containers.py:38、:46),我把容器實際建起來,兩支服務拿到的都是真零件。反過來把那個插槽拿掉,容器直接報錯,不會給空值。所以今天不會觸發。
  • 為什麼還是寫出來:這種寫法是「組裝表某天被改漏,檢查就安靜消失」的形狀,正是這一棒要找的東西。U9 報告第 112 行已經講過同一件事、結論相同。我不另立項,但建議修第 65 項時順手把 is None 改成「沒接就拒絕」,一行的事。
  • 附帶:這支服務的全專案建構點只有組裝表這一處,沒有別的程式繞過組裝表自己 new 一個(全 repo 搜尋 ProjectSummaryReportService( 零命中,只有測試)。
§5

工具報的逐條

四條都是 3:0 通過,全部是舊帳,不另計。

工具編號 嚴重度 說的是什麼(白話) 位置 總表對應
F1 高 Google 硬碟授權完成的回呼頁,不用登入就能打,網址裡的錯誤訊息被原樣塞進頁面程式碼,可以插入攻擊程式偷走登入權杖 api/cloud_integration/routes/google_drive_integration_route.py:120 第 188 項(U4 已記,建議先轉 FR-114)
F2 中 Google 硬碟應用程式的密鑰,原文出現在 12 支已進版控的對話紀錄檔,而且跟目前 .env 用的是同一把 docs/conversation-history/2026-04-21-google-drive-integration/...part-05-of-14.md:8157 等 12 檔 CM-1631(檔數 12 與 FR-113 O2 重數相同)
F3 中 雲端硬碟「初始化資料夾」不檢查專案是不是你的,可以把別家公司的專案結構建進自己的硬碟,再反過來塞假證據 app/cloud_integration/service/drive_sync_admin_service.py:125 第 192 項(U5/U6 已記,總表評高、決策者 09-25 已裁要修)
F4 中 摘要報告的單筆、清單、歷史四支讀取不檢查是不是專案成員,同公司任何人都讀得到 app/project_summary_report/service/project_summary_report_service.py:79 第 65 項(主線 :78-86 仍未補,修正線 CM-2037 在處理)

現況(2026-10-01):本棒各條後來的處理結果如下(過程紀錄保留,不改)。

  • 本棒淨新增 0 條,工具報的 4 條全為舊帳(總表第 188/192/65 項與 Google 密鑰):第 188 項 ✅ 已修(CM-2200)、第 192 項 ✅ 已修(CM-2201)、第 65 項 ✅ 已修(CM-2179)、Google 密鑰 ✅ 已修(CM-2051,原卡 CM-1631 作廢)。

面板否決 1 條(0:3):雲端硬碟權杖的加密金鑰原文出現在 10 支對話紀錄檔。外洩是真的,但面板認為光有這把金鑰拿不到任何東西,還要有資料庫存取權。這把金鑰已在 CM-1631 裡(10 檔),而且總表已記下「跟 CM-1608 資料庫密碼串起來就能讀客戶硬碟」這條串接風險。所以面板否決不影響現有登記。

嚴重度差異:F3 工具評中、總表第 192 項評高。總表是 U6 在 DEV 實際跑通跨公司寫證據後定的,維持總表的高。

§6

可信度分兩層

第一層:工具正式報告(有三人面板背書)

  • 驗證章 verified:5 個候選、15 票全投完、零漏投,4 條 3:0 通過、1 條 0:3 否決。研究員 2 派 2 回,failed 0。
  • 修訂章 CLAUDE-SECURITY-REVISION-222a13f80868-dirty.json。-dirty 是因為工作區有平行線未 commit 的改動,這批改動都不在這七支檔裡。
  • 這是快篩檔(low),只有一位主研究員加一道密鑰掃描。工具擅長抓「寫錯的邏輯」,不擅長抓「應該有卻沒有的零件」。這一棒工具的四條產出全落在範圍外,對組裝表本身沒有提供任何判斷,所以本棒的主要結論靠第二層。

第二層:runner 自己開檔與實測(未經三人面板)

  • 七支檔逐支通讀。
  • 自寫腳本比對「子組裝表要的插槽 vs 總組裝表給的」(36 個容器、107 個插槽)。
  • 自寫腳本比對「用到自動拿零件的程式 vs 登記清單」(62 支 vs 77 支)。
  • 實際載入六支子組裝表,逐一比對建構子參數(結果與比對腳本一致)。
  • 實測三件:①摘要報告容器接上時拿到真零件、拿掉插槽時直接報錯;②沒掛清單的入口拿到佔位物件,一呼叫就 500;③比對腳本標紅三處都開了套件原始碼核對。
  • 打折處:總組裝表整張建不起來。本機 import 時 flow_control_containers.py:33 找不到 jedi_compliance_audit.app.service.project_current_ssp_service,因為目前虛擬環境指向修正線工作樹,套件版本與主線對不上。所以「授權檢查/診斷包/雲端硬碟在總組裝表裡拿到真零件」這一半是讀碼加子容器單獨實測推出的,不是整張總表端到端實跑。這件事跟 U5、U6 回報的「本機 BE 起不來」是同一類環境問題,不影響本棒結論,但請首腦知道。
§7

範圍外讀了什麼(只讀不報)

config/di_modules.py、core/app_factory.py:275-300(組裝與登記的時機)、common/authz/license.py、core/plugins/license.py、全部 36 支子組裝表的插槽宣告、摘要報告/診斷包/安裝精靈/雲端硬碟相關服務的建構子,以及 jedi-license-runtime、jedi-common(IdentityContext)、jedi-iam(TenantProvisioningService)、jedi-oscal-v2(兩支 role_repo)的原始碼。

§8

執行概況

項目 內容
範圍 7 檔/996 行(`git ls-files
工具 claude-security 0.11.0,effort low、focus attack-surface
run ID wf_21bd999c-982(報告目錄 CLAUDE-SECURITY-20260926-024832)
掃描版本 222a13f8086847570815e247891453cc19dde1f2(dirty,平行線改動不在範圍內)
耗時 約 36 分鐘(02:48~03:25 UTC)
agent 17 派 17 回,failed 0
候選/票數 5 候選、15 票全投,4 條 3:0 通過、1 條 0:3 否決
驗證章 verified
淨新增 0(工具 4 條全為舊帳:第 188、192、65 項、CM-1631)
順帶觀察(不開卡) associations_containers.py:45 有一個從沒用過的空插槽,可順手刪;摘要報告「零件沒接就放行」建議修第 65 項時一起改成拒絕