範圍:12 檔/1,457 行(
core/plugins/detection.py、core/plugins/_host.py、檢測三支 DI 容器檔、infra/readmodel/detection/兩支查詢、生命週期接縫、任務型別申報、三支轉手檔)。common/code/detection_tools_error_code.py依首腦裁定不列入。 掃描工具:Claude Code 官方claude-securityplugin,effort low,focus 生產程式碼。 掃描基準 commit:aa23a63554ce(工作區有其他 session 的未提交改動)。 驗證章:verified(0 候選,面板 0 票)。只掃不修。
卡片說的「上游防線」——detection.py 第 337~340 行那組解壓上限——其實是沒接上的死設定:套件收下之後一次都沒讀過。 真正擋住上傳檔案的,是 DI 容器另外直接讀 Config 組出來的那一份;而網址那條路(總表第 116 項)在修正分支上補的上限,用的是套件寫死的數字,也不讀宿主設定。所以同一件事現在有三套數字,運維調環境變數只會改到其中一條路。這不是可以直接打的洞,但它就是「守了一半」會長出來的樣子(見 W3-1)。
工具這一棒 0 條候選。我開檔追卡片重點時另外查到一條:母公司刪除自己分享給子公司的檢測基準時,系統判斷「還有沒有人在用」會漏看子公司的引用,於是子公司正在用的基準可以被刪掉。根因是總表已登記的「資料庫隔離規則方向寫反」,這裡是它的一個新後果,不另計新項,但要列進那張修正卡的驗收項(W3-2)。
卡片其餘四點都查過了,都不成立,第 5 節逐條寫了理由。
弱點檢測功能本身寫在 jedi-detection 套件裡,FR-108 十四棒已經掃完。這一棒看的是主專案把套件接上產品的那一層:套件需要「登入檢查、權限檢查、加解密、查任務、寄通知、寫證據」這些東西,都由主專案遞給它。要問的不是「哪裡沒有權限檢查」,而是:主專案遞過去的守門零件對不對、齊不齊;套件以為主專案會守的地方,主專案真的有守嗎。
core/plugins/detection.py 被 FR-108 五棒引用過,但都只是查某一點時打開看一行。這一棒是第一次從頭讀到尾。
| # | 這是什麼問題 | 出事會怎樣 | 要先有什麼才會發生 | 在哪裡 | 嚴重度 | 來源 |
|---|---|---|---|---|---|---|
| W3-1 | 壓縮檔上限有三套數字,卡片點名的那一套根本沒接上 | 運維以為調了上限、實際只改到上傳那一條路;網址那條路永遠是寫死的數字。調緊的時候,會變成一條路緊、一條路鬆 | 有人去改 DETECTION_PROFILE_* 環境變數 |
core/plugins/detection.py:322(build_config,死設定)di_containers/detection_tools/detection_tools_containers.py:152(實際生效的那一份) |
不是漏洞:設定分岔,會長成漏洞的地方 | runner 開檔,未經投票 |
| W3-2 | 母公司刪自己分享出去的基準時,看不到子公司正在用 | 子公司任務綁的基準版本被刪掉,下次派工失敗,稽核軌跡裡的那份基準也跟著消失 | ① 母公司把基準設成「分享給子樹」② 子公司的任務綁了它 ③ 母公司管理員按刪除 | infra/readmodel/detection/detection_profile_usage_query.py:128(find_refs);根因在資料庫隔離規則 |
低:沒有洩漏,是資料完整性;而且刪的人本來就是基準的擁有者 | runner 開檔+DEV 唯讀查證,未經投票 |
白話說明
客戶可以上傳一包檢測規則(壓縮檔),也可以填一個網址讓系統去抓。為了防止有人丟「解壓炸彈」(很小的壓縮檔,解開後幾十 GB,把伺服器記憶體吃光),系統對壓縮檔設了三道上限:最多幾個檔、解開後最多多大、壓縮比最多幾倍。
我追了這組數字從哪裡來、被誰用,結果有三套:
| 哪一套 | 數字(檔數/解開後大小/壓縮比) | 誰在用 |
|---|---|---|
① 主專案設定檔 config/config.py:194-200(讀環境變數) |
10,000/500MB/200 倍 | 上傳檔案那條路(DI 容器 detection_tools_containers.py:152-158 直接讀它,組出驗證器交給套件) |
② core/plugins/detection.py:337-340 遞給套件的 DetectionConfig |
讀①,讀不到才用 20,000/500MB/100 倍 | 沒有人。套件全部原始碼只有宣告這四個欄位的那一處(plugin/contract.py:82-85),沒有任何讀取點 |
③ 套件內建預設值 common/archive_limits.py:23-25(修正分支) |
10,000/500MB/200 倍,寫死 | 網址那條路(修正分支 profile_extractor/inspec.py:404 的 ArchiveBombGuard(archive_size=len(raw)) 沒傳上限,直接吃預設值) |
卡片問的三件事,答案如下:
Config 是類別屬性,就算環境變數沒設,getattr 也一定拿得到值(os.getenv 自己就帶預設值)。真正會生效的預設值是①的 10,000/500MB/200 倍,設定檔註解說明了取值理由(現有規則包約 123KB、解開 876KB),這組數字是合理的。只是②的後備值(20,000、100 倍)跟①③都不一樣,同一件事就有三組數字。DETECTION_PROFILE_*,設定檔註解寫明它刻意跟「源碼包上傳」那組上限(DETECTION_UPLOAD_*,100MB/五萬檔)分開,因為兩者大小差兩個數量級。detection.py:338-340」:這個指路指錯了地方,那幾行是死設定。要沿用的話,來源是 config/config.py:194-200。但這組數字是照「一份檢測規則包」的大小訂的,拿去管 Excel 合不合適,要由開 Excel 修正卡的人自己判斷,不是可以直接照抄的全域常數。為什麼算「會長成漏洞的地方」
現在三套的數值剛好一致(①跟③都是 10,000/500MB/200),所以目前兩條路擋得一樣緊。問題出在有人調整的時候:
DETECTION_PROFILE_MAX_ENTRIES 調緊成 2,000 → 上傳那條路變 2,000,網址那條路還是 10,000。調緊的人會以為全擋住了,其實只擋了一半。detection.py 的 build_config(),以為改這裡就能調上限 → 改了沒有任何效果,也不會有錯誤訊息。這跟總表第 116 項(網址那條路原本完全沒有上限)是同一個病的延續:第 116 項的修法是把上限補到網址那條路上,但補的是寫死的數字,沒有接到宿主的設定。
⚠️ 兩份套件副本現況不同:feature/review 這邊的套件副本(jedi-python-package/jedi-detection),網址那條路還是完全沒有上限(inspec.py:361-369 沒有任何防炸彈檢查),也就是第 116 項在這個分支還沒修。有接上限的是修正分支 fix/security-b1 的副本。本機 venv 目前以 editable 模式指向修正分支的那一份。
建議修法(只建議、不動手)
DetectionConfig 的四個上限真的用起來:register() 用它組出 ArchiveLimits,上傳驗證器跟網址解析器都吃同一份,不再各讀各的。detection_tools_containers.py:152-158 另外組驗證器的那段,也刪掉 detection.py:337-340 的 getattr 後備值(直接讀 Config.DETECTION_PROFILE_*),整件事只剩一份數字。場景
母公司(例如集團總部)的管理員在「檢測基準庫」建了一份基準,設成「分享給子樹」(FR-094 的 SHARED),底下的子公司就能拿來用。某家子公司的顧問建了一個檢測任務,綁上這份基準。之後母公司管理員覺得這份基準過時了,在基準庫按「刪除」。
系統刪除前會先查「這份基準還有沒有人在用」,有人用就回 409、不給刪。但這次它回「沒人在用」,就刪下去了。 子公司任務綁的那一版連同控制項一起消失,子公司下一次按「開始掃描」會拿到「找不到基準」;如果這個任務已經掃過,稽核軌跡裡指向的那份基準也不在了。
為什麼會查不到
「還有沒有人在用」這件事,是 detection_profile_usage_query.py 用一支 SQL 查三張表:任務綁定表 config.job_execution_detection_tools、派工單 compliance.agent_tasks、執行紀錄 compliance.detection_executions。這支查詢用的是當前操作者的身分,所以受資料庫隔離規則(RLS)限制。
這三張表的隔離規則,正是總表已登記的那條「方向寫反」(FR-108 D1 F3,模組報告 M03 第 7 條):它把登入者的公司路徑 /1/102/ 切成 {1, 102},只認「路徑上的祖先」,不認「底下的子公司」。所以母公司(102)的 session 看不到子公司(152)的綁定紀錄。
我在 DEV 開了唯讀交易、切成 cm_app 身分核對(最後 ROLLBACK,沒有寫入任何東西):
app_tenant_allowed_for_session(152) → 看得到。兩者方向相反。卡片原本擔心的情境不成立:卡片擔心「平台管理員刪公版基準時,以為沒人在用」。平台管理員是最上層公司(路徑只有一段 /1/)的成員,資料庫會給他超級管理員旗標,三張表的規則第一個條件就直接放行,所以他看得到全站的引用。公版(SYSTEM)的刪除只有平台管理員做得到,因此公版這條路沒有問題。真正漏掉的是「母公司刪自己的 SHARED 基準」這條路,這條路在 FR-094 加入分享功能之後才出現。
為什麼判「低」、而不是「中」
app_tenant_allowed_for_session),這條會跟著消失。處理建議
_in_use_message 會列出專案名)。母公司本來就看得到子樹的專案(projects 表用的是正規的子樹規則),所以這不是新的洩漏,但修的人應該知道會有這個變化。沒有實際做的事:我沒有在 DEV 真的建一份 SHARED 基準、讓子公司綁定、再從母公司刪除。上面的推論是「讀程式+讀資料庫規則+唯讀驗證規則的判斷結果」得出的。手測步驟見第 8 節。
| 卡片重點 | 結論 | 怎麼查的 |
|---|---|---|
platform_admin_check/scope_writable_check 走「模組級設定」,會不會在程序啟動時就被固定成某個人的身分 |
不會 | 啟動時交給套件的是函式本身(detection.py:315-316),套件存在模組變數裡(common/guard.py:26-30)。每次呼叫時,函式才去讀 get_user_context()(登入者身分存在「每個請求各自一份」的變數裡),viewer_is_platform_admin()(jedi_iam/authz/platform.py:15)與 assert_scope_writable()(common/authz/sharing.py:25)都是當下才讀身分。沒接上時套件會直接拋例外,不會預設放行(guard.py:39-45) |
CryptoAdapter 沒有「誰能呼叫解密」的限制 |
設計如此,不重報第 85 項 | 這是一個薄轉接(detection.py:129-140),只負責把宿主的 Fernet 金鑰物件遞給套件。「誰能觸發解密」由呼叫它的 service 決定,第 85 項(測試連線沒有守門)的缺口在套件的測試連線那一支,不在這個零件 |
detection_profile_usage_query.py 會不會因為隔離規則只看得到自家引用,讓平台管理員刪公版時以為沒人在用 |
平台管理員這條不成立;但查出母公司刪 SHARED 那條=W3-2 |
見 4.2 |
detection_job_notify_query.py 會不會把別家公司的人拼進收件人 |
不會 | 這支查詢只在遠端代理程式回報結果時執行,那時的身分是用 tenant_context() 設成派工單所屬公司的機器身分(jedi_remote_agent/.../agent_task_service.py:113-120,公司編號由伺服器從派工單查出,不信任請求內容)。收件人來源三張表都有隔離規則:task_assignees/project_participants 都要求「所屬專案看得到」,projects 用的是正規的子樹規則。唯一沒有隔離規則的 workflow_execution_control_mapping 只拿來串出 project_id,接著用那個 id 去查 projects 時還是會被規則過濾。這條路拼不進別家的人 |
detection_lifecycle_listener.py |
不重報 FR-077 R3 | 37 行純轉呼叫,沒有判斷邏輯 |
| AI 儀表板那條路(④) | 不適用 | di_containers/dashboard_apis/ 下沒有任何檢測相關的申報 |
| 註解或契約說…… | 追到另一側的結果 |
|---|---|
detection.py 檔頭:「認證與三軸授權守門由宿主給」 |
✅ 給齊了。auth_required=jwt_required()(:302);授權、能力點、平台管理員三支 adapter(:303-305)都呼叫 common.authz 的正式守門(實際 import 驗證五支函式都存在)。套件 routing.py:166-180 把 auth_required 包在每一條路由上;缺少必要接線時 register() 會拒絕掛載 |
detection.py:131-134:「本 port 缺了就是明文落庫」 |
✅ 有給(:306),而且這一項被列在「缺了拒絕掛載」的必要接線裡 |
detection_orchestration_containers.py:45-47:「task_lookup 缺了,派工三道守門全部無從進行」 |
✅ 有給。TaskLookupAdapter.get_task 查的 job_executions 有隔離規則;查無時照契約回 None,由套件決定回 404 |
套件編排服務(修正分支):「任務被指派人或專案 manager 才可操作(assert_job_operator)」 |
⚠️ 這個方法在本分支的宿主上不存在。修正分支的套件呼叫 self._wf_svc.assert_job_operator(...)(8 處),但 feature/review 的 app/flow_engine/service/workflow_execution_service.py 只有 assert_project_participant(:126),assert_job_operator 只存在於 BE 的修正分支 worktree。這不是破口,缺方法只會拋 AttributeError(回 500、不會放行)。但本機 venv 以 editable 指向修正分支的套件,feature/review 的 BE 在本機跑起來時,檢測的「開始掃描」等 8 支操作應該會全部 500。只要兩邊分支一起合併就沒事,這裡是提醒驗收環境要對齊 |
task_type_declaration.py 啟動掛鉤「對檢測任務自動派第一次掃描」 |
✅ 守門在下一層。掛鉤呼叫 orchestration.start_execution(),那支方法一進去就先檢查任務操作權(detection_orchestration_service.py:196/修正分支 :202),權限檢查不會被掛鉤繞過 |
同一套接線五支並排、有沒有哪支少了(對照盤點檔 §3 ②的並排表):檢測是唯一「逐項手寫」、不吃 host_defaults() 的一支,但登入、能力點、授權、身分四道門都有給,而且多給了平台管理員與分享守門,沒有少給的。
守門次數對公開方法數:
| 檔 | 守門字眼 | 公開方法 | 說明 |
|---|---|---|---|
core/plugins/detection.py |
10 | 20 | 守門字眼集中在四支 guard adapter;其餘是資料轉接,守門在套件的 route/service 層 |
core/plugins/_host.py |
3 | 5 | host_defaults() 給能力點守門;lazy()/di()/_HostServiceProxy 都是呼叫當下才解析,沒有「存下第一個實例」的問題 |
| 兩支 readmodel 查詢 | 0 | 各 2 | 查詢層本來就不該有守門,隔離靠資料庫規則(就是 W3-2 出事的那一層) |
| 生命週期接縫、任務型別申報 | 0 | 3/1 | 見上表 |
| 三支轉手檔 | 0 | 0 | 純 re-export,沒有邏輯 |
工具那一層:「0 條」是真的 0 條,但只代表快速掃過一輪沒撿到東西。 研究員與密鑰專項各派 1 支、都回來了,兩支都回報空清單,面板因此 0 票,驗證章是 verified。但研究員沒有交代它讀了哪些檔(工具的 coverage.research 是空的),所以不能說 12 支檔都讀完了。
runner 那一層:W3-1 與 W3-2 沒有經過三人面板投票,是我自己開檔核對的。
profile_max_* 的所有出現處,確認只有宣告沒有讀取;三套數字都開檔逐行核對過。pg_policies,用唯讀交易以 cm_app 身分驗證規則的判斷結果(最後 ROLLBACK);用 pg_has_role 確認 cmmgr 是 cm_app 的成員。沒有實際跑一次「建分享基準→子公司綁定→母公司刪除」的完整流程。feature/review 副本與修正分支副本的行號不同,報告裡都標了是哪一份。不保證完整:用的是最快的掃描檔位(effort low,沒有威脅建模和廣度掃描)。套件本體不在範圍內,只在追接線時讀了相關段落。
W3-2
in_use: false(應該要是 true)。W3-1
DETECTION_PROFILE_MAX_ENTRIES 設成 50,重啟 BE。feature/review 上完全沒有上限。| 項目 | 數值 |
|---|---|
| 掃描範圍 | 12 檔/1,457 行 |
| 基準 commit | aa23a63554ce(工作區 dirty,有平行 session 改動) |
| 檔位 | effort low,focus 生產程式碼 |
| 研究員 | 派 2 支(研究 1+密鑰專項 1),回 2 支,零失敗、零重跑 |
| 原始候選 | 0 條 |
| 投票 | 0 票(沒有候選) |
| 驗證章 | verified(CLAUDE-SECURITY-REVISION-aa23a63554ce-dirty.json) |
| 工具 run ID | wf_1783d39b-c1f |
| 耗時 | 約 16 分鐘(2 個 agent,約 20.6 萬 token) |
| 工具產出原始報告 | CLAUDE-SECURITY-20260923-130633/(未入版控) |
| runner 自行查證 | 兩份套件副本逐檔 grep、DEV 唯讀查 pg_policies/tenants/pg_roles、唯讀交易驗規則判斷 |
detection.py:338-340」,但那幾行是死設定。建議改指 config/config.py:194-200,並註明那組數字是照檢測規則包的大小訂的。feature/review 的 BE 搭修正分支的套件,檢測操作會因為缺 assert_job_operator 回 500(第 6 節)。驗收 FR-114 修正或手測本棒時,要注意兩邊分支要一致。