e062d4959b37(branch feature/FR-075,工作區 dirty)claude-security plugin,effort low、focus attack-surfaceverified(三個獨立檢查員對 10 條候選各投一票,30 票全數投出,10 條全部 3:0 通過;檢查員把 5 條的嚴重度往下調)掃完了,範圍內找到 2 個真問題,最嚴重的是「任何登入者丟一份特製的流程圖給『檢查流程圖』這支 API,就能把後端一條工作程序卡住兩分鐘,連打四次整個系統對所有客戶停止回應」。 另一條是流程範本的「讀」沒有掛能力點,同租戶內任何登入者都能拿到全部範本的完整流程圖。開卡七個疑點裡四個成立、兩個確認沒問題、一個查出了機制但根因還要補查。範圍外撈到 8 條密碼金鑰進版控,全是舊案。
現況(以 docs/security-report/M06*.md 為準):F7(檢查流程圖 API,M06-4)→ ✅ 已修(FR-114.1-8);F10(範本讀取,M06-11)→ ✅ 已修(CM-2035)。範圍外的憑證項(CM-1607/1608/1629/1631 系列):檔案殘留已清(CM-2048/2049/2051),密碼換發排在正式環境上版前。
管理者可以在畫面上建立、修改、複製、發布自己公司的稽核流程範本,也能看原廠內建的範本;畫流程圖時系統會即時幫他「檢查這張圖合不合規則」。這一棒查的是這組功能的 13 支檔:誰能看誰的範本、存進去的流程圖有沒有先檢查、原廠範本會不會被客戶改掉、「檢查流程圖」這個功能會不會被拿來打垮伺服器、每輪稽核凍結的那份副本為什麼沒標客戶。
這是本 arc 唯一資料庫隔離是開的一組(flow_templates 表,DEV 實查開關開、4 條規則),所以它同時是「隔離開了之後應用層還剩什麼洞」的對照組。
| # | 這是什麼問題 | 出事會怎樣 | 要先有什麼才打得到 | 在哪裡 | 怎麼修 |
|---|---|---|---|---|---|
| F7 🟡 MEDIUM(研究員報 HIGH,檢查員 3:0 降 MEDIUM) |
「檢查流程圖合不合規則」這支 API **任何登入者都能打、丟進去的流程圖沒有大小上限、而檢查器算法是「每個分岔點都把全部連線掃一遍」 | 一份 40MB、塞 20 萬個分岔點與 20 萬條連線的流程圖,會讓一條後端工作程序算到 120 秒逾時被殺掉;後端預設只有 4 條工作程序,連打 4 次整個系統對所有客戶停止回應**,重複送就能一直維持。整段還包在資料庫交易裡,會同時佔住資料庫連線池 | ① 任一個有效登入帳號(不需要任何流程範本權限) ② 請求在 50MB 以內(CM-1587 裁的全站上限,足夠塞幾十萬個節點) ③ 全站沒有請求頻率限制(首腦 grep 確認無 flask-limiter 或同類) |
入口 api/flow_engine/routes/flow_template_route.py:130-144(只掛 jwt_required)schema api/flow_engine/serializers/flow_template.py:28-29(fields.Str(required=True),無長度)service app/flow_engine/service/flow_template_app_service.py:143-157慢的點:jedi-flow-engine common/utils/bpmn_topology_validator.py:168 與 :271 兩處 next((f for f in sequence_flows if …)) 線性掃描 |
① 端點補 require_capability("flow_template.create"/"flow_template.update"),與同檔寫入端點一致;② schema 的 bpmn_xml 加 validate.Length(max=…)(例如 2MB);③ 驗證器開頭把 sequence_flows 建成 {@id: flow} 字典,兩處 next(...) 改查字典;④ 節點總數設上限(例如 5,000 個直接回 bpmn_too_large) |
| F10 ⚪ LOW(研究員報 MEDIUM,檢查員 3:0 降 LOW) |
流程範本的「列表」與「單筆」兩支讀取 API 只驗登入、沒掛 flow_template.read 能力點——而這個能力點確實存在、也綁在「流程範本管理」選單上,等於「前端選單擋住、API 完全敞開」 |
同租戶內任何最低權限的帳號(例如只有 viewer 角色、沒被授予流程範本權限的人),用 curl 就能列出並讀走該租戶全部範本+所有內建範本的完整流程圖:階段設計、哪個角色負責、哪些條件會跳過缺失改善。跨租戶讀不到(資料庫隔離開著) | ① 該租戶內任一可登入帳號 ② 未被授予 flow_template.read |
api/flow_engine/routes/flow_template_route.py:29-45(列表)、:58-66(單筆);同檔 :20-26 只定義了 create/update/delete 三個 decorator |
比照 api/flow_control/routes/project_route.py:41 的 @require_capability("project.read"),加一個 _flow_template_read = require_capability("flow_template.read", …) 掛到兩支 get。若產品決定「範本人人可看」,就把能力點與選單綁定一併移除,不要留「DB 說要管、API 不管」兩套真相 |
工具設了 focus 就會額外跑一次「找密碼金鑰」的專項,掃全 repo 不受 13 檔限制。8 條逐一對照:
| # | 內容 | 對應既有卡/總表條目 | 位置是新的嗎 |
|---|---|---|---|
| F1 | POC 資料庫密碼寫死在文件產生腳本 docs/system-design/scripts/generate_db_schema_docx.py:34 |
CM-1608/CM-1629 | 否(H2、H1 都撈過) |
| F2 | 三家 LLM API 金鑰進對話紀錄 | CM-1607 | 否 |
| F3 | Google Drive OAuth 密鑰進對話紀錄 | CM-1631 | 否 |
| F4 | JWT 簽章金鑰進對話紀錄(與 .env 現值相同) |
CM-1607(FR-079 F12 已裁 DEV 金鑰不換) | 否 |
| F5 | GitLab 個人權杖進對話紀錄 | CM-1607 | 否 |
| F6 | POC 資料庫超級使用者密碼寫在 docs/analysis/2026-05-28-poc-db-migration-plan.md:73 |
總表 §3.1 第 2 條已列同一位置 | 否 |
| F8 | Google Drive 權杖加密金鑰進對話紀錄(與部署 env 同值) | CM-1631 | 否 |
| F9(LOW) | 開發/測試帳號密碼寫在 docs/claude/memory/reference_dev_login.md:11 |
CM-1608(決策者已裁 blsadmin 密碼測試機專用不換) | 是——這支檔是 2026-09-06 決策者裁「memory 入版控」後跟著進來的,憑證被工作過程寫進版控的第四種形狀(前三:對話紀錄/Trivy 報告/腳本 default) |
結論:不開新卡。 F9 的位置併入 CM-1608 清理清單(把密碼改成「查 .env」,帳號留著)。
這是什麼問題(白話)
畫流程圖的編輯器每次拖一條線,前端就會把整張圖送到後端問「這樣畫合不合規則」。這支 API 三件事都沒做:沒檢查你有沒有流程範本的權限(任何登入者都能打)、沒限制送進來的圖多大(只受全站 50MB 上限)、而且檢查器裡有兩處是「對每個分岔點,把全部連線從頭掃一遍」的寫法——圖越大,計算量是乘法成長,不是加法。
出事會怎樣
工具描述的攻擊:產生一份約 40MB 的流程圖,放 20 萬個分岔點、每個帶一條指向不存在編號的出線,再放 20 萬條連線。後端對每個分岔點掃 20 萬條連線 → 約 400 億次比對,永遠算不完,直到 120 秒逾時那條工作程序被殺掉重啟。預設 4 條工作程序,開 4 個連線就全佔滿;整段包在 @transaction 裡,資料庫連線池(10+溢位 20)也一起被佔,沒被打到的功能也受影響。50MB 的 XML 展開成 Python dict 還會放大成數百 MB 記憶體,多發併行可能直接把容器打到 OOM。
要先有什麼才打得到
flow_template 任何能力點api/ app/ core/ config/ common/ 無 flask_limiter/Limiter()在哪裡(首腦逐一開檔核對)
api/flow_engine/routes/flow_template_route.py:130-144:FlowTemplateValidateRoute 的 method_decorators = [jwt_required()],沒有 require_capability;docstring 自陳「FE editor inline warning 拖 sequenceFlow 時 debounce 呼叫」api/flow_engine/serializers/flow_template.py:28-29:bpmn_xml = fields.Str(required=True),無 Lengthapp/flow_engine/service/flow_template_app_service.py:143-157:validate() 直接 BpmnTopologyValidator(rules).validate(bpmn_xml),@transaction 內common/utils/bpmn_topology_validator.py:168(_check_start_stage)與 :271(_check_gateway_conditions):flow = next((f for f in sequence_flows if f.get("@id") == fid), None)——每個出線都線性掃全部連線main.py:241-243:GUNICORN_WORKERS 預設 4、GUNICORN_TIMEOUT 預設 120;config/config.py:105:MAX_REQUEST_BODY_MB 預設 50為什麼是 MEDIUM 不是 HIGH
檢查員 3:0 把研究員報的 HIGH 降為 MEDIUM。首腦同意:這是「讓服務停擺」不是「拿走資料」,且要先有一個登入帳號。但它與 P1 F1(惡意流程圖卡死執行緒)、FR-082 CM-1638(AI 聊天無上限)是同一組病——「一個帳號讓全站不回應」在總表 §2.9 已獨立成 🅹 資源耗盡組,PM 可依產品定位整組升級。
怎麼修
見總覽表四步。與 P1 F1 的差別:P1 是「存進去之後讀的人炸」,這條是「不用存、打一次就炸」——門檻更低,修法也更簡單(加能力點+長度上限兩行就把門檻拉回「要有範本權限+圖要小」)。第三、四步(驗證器改字典、節點上限)是治本,可與 P1 F1 的修正卡合成一張「BPMN 輸入強固化」。
這是什麼問題(白話)
系統定義了一個叫 flow_template.read 的權限(「檢視流程範本」),前端的「流程範本管理」選單就是靠它決定要不要顯示。但後端提供範本清單與單筆內容的兩支 API 沒有檢查這個權限,只檢查有沒有登入。
出事會怎樣
同租戶內一個沒被授予這個權限的員工(前端看不到選單),直接打 API 就能列出全部範本編號、再逐一讀走每份的完整流程圖——包含哪個階段由哪個角色負責、哪些閘道條件會讓流程跳過缺失改善階段。跨租戶讀不到:首腦 DEV 實查 flow_templates 的 select 規則是「超級管理員 OR 內建範本 OR 本租戶」,隔離開關開著。
在哪裡(首腦核對)
api/flow_engine/routes/flow_template_route.py:29-45 FlowTemplatesRoute.get、:58-66 FlowTemplateRoute.get:method_decorators = [jwt_required()],無能力點:20-26:_flow_template_create/_update/_delete 三個 decorator,沒有 _readscripts/init/04-seed-core.sql:306(id 117 flow_template.read)、scripts/sql/2026-05-12-flow-template-permissions.sql:19api/flow_control/routes/project_route.py:41/97/145 @require_capability("project.read")為什麼是 LOW
檢查員 3:0 從 MEDIUM 降 LOW:影響侷限同租戶、內容是流程設計不是個資或憑證、且與 CM-1585/1589(讀端點漏守門)同型且已有修法可抄。首腦同意,但要記一筆:這是本專案「讀取端點漏守門」第四次出現(CM-1585 角色、CM-1589 部門/租戶/使用者、FR-079 公告、FR-081 意見回饋、現在流程範本),總表 §0「反覆出現的模式」那條要把次數更新。
怎麼修
一行 decorator 掛到兩支 get;或做產品決策把能力點拔掉。不要維持現狀——「DB 說要管、API 不管」的兩套真相會讓下一個人以為有守門。
| # | 卡片疑點 | 結論 | 核對 |
|---|---|---|---|
| 1 | 🔴 建立/更新範本時不解析 XML | ✅ 成立,但影響面比卡片想的小 | create()(:69-84)、update()(:87-108)、duplicate()(:159-178)確實都不解析;publish()(:110-120)才解析+拓撲檢查。但宿主側的 mapper 不解析(infra/flow_engine/mapper/flow_template_mapper.py 無 BpmnUtils),列表也不帶 XML——所以壞 XML 存在 flow_templates 時,讀它不會炸、發布會被擋。真正會炸的是 P1 F3 那條:套件側 workflow_templates 的 mapper 每讀必解析。兩張表兩種行為,宿主這張是安全的那個。 記錄,不另計 |
| 2 | 🔴 validate 端點任意 XML 解析 |
✅ 成立 → F7 | 而且比卡片預期的更糟:不只「無次數限制」,還有算法平方級與 @transaction 佔連線池 |
| 3 | 寫入有能力點、讀取只有登入 | ✅ 成立 → F10 | RLS 四條規則首腦實查:select 允許 is_super_admin OR is_builtin OR 本租戶;update/delete 額外要求 tenant_id IS NOT NULL(內建範本 tenant 掛 root 1)。平台管理員(root 租戶)看得到全部是合法的「上層看下層」(FR-087 已證) |
| 4 | 內建範本守門 | ✅ 確認沒問題 | _guard_builtin_writable 四處呼叫(:90/:112/:126/:138)涵蓋 update/publish/unpublish/soft_delete;create 不會碰內建、validate 不寫、duplicate 是刻意複製到自己租戶。沒有漏掉的寫入路徑 |
| 5 | 🔴 凍結副本不帶租戶 | ⚠️ 機制找到了,但根因要補查(不用重掃);且 FR-087 的描述要修正 | 機制:套件範本鏈(entity/mapper/repo/service)零 tenant_id 處理,租戶靠 jedi-common db_mw.py:64 set_tenant_info_before_insert 在 flush 前從 get_user_context().tenant_id 自動填——沒有 user context、或 context 的 tenant_id 是 None 就留空。DEV 實查修正 FR-087 §5.2 的說法:191 筆無租戶的裡面,只有 5 筆是輪次快照(provider='snapshot'),186 筆是 Billows-Official 的母版(module-frame 匯入建的);建立者 jediadmin(租戶 158、非超管)119 筆、blsadmin(租戶 102、超管)72 筆,時間 2026-07-27~08-09;186 筆母版裡 127 筆仍連著執行紀錄。回填時要分兩類處理,不能一律當「凍結副本」。根因候選:那段時間 module-frame 匯入路徑的 user context tenant_id 為 None(FR-069.16 之前 X-Tenant-ID 處理不同)——要補查該路徑當時怎麼組 context,一個 git log -L 就能答,不用重掃 |
| 6 | 列表 ilike 通配字元 |
✅ 記錄,不算問題 | flow_template_repo_impl.py:44 ORM 綁參數,%/_ 未跳脫只影響搜尋範圍,12 筆的表掃全表無感 |
| 7 | DTO 回傳範圍 | ✅ 確認沒問題 | flow_template_dto.py:10-21 只有 uid/name/description/is_builtin/status/bpmn_xml(詳情才帶)/created_user+nickname/updated_user+nickname/時間,無敏感欄位,審計欄位有照 CLAUDE.md 規範補 nickname |
第一層「這兩條存在嗎」→ 可信。 首腦逐行開檔、DEV 實查 RLS 規則與 gunicorn/body 設定,與工具描述一致;30 票全投、兩條皆 3:0。
第二層「這 13 檔只有這兩條嗎」→ 不可宣稱。 30 票裡 24 票投在範圍外的憑證重複案,真正投在 13 檔的只有 6 票;無逐檔閱讀帳本;沒有 runner 的人工那一輪(這棒沒交付,人工核對全由首腦做,首腦只追了卡片七項與工具兩條,沒逐行讀 13 檔)。疑點 5 的根因待補查。
| 項目 | 數值 |
|---|---|
verification.status |
verified |
| 候選/去重 | 10/10 |
panel_votes |
30(10×3,全投出) |
panel_quorum_findings |
10(全 3:0) |
| 嚴重度調降 | 5 條(F1/F2/F7/F8 HIGH→MEDIUM、F10 MEDIUM→LOW) |
| 研究員 派出/回報 | 2/2 |
unreviewed_candidate_sites |
0 |
| 範圍檔數 | 13(stamp scope 與卡片 scope 逐檔相同,首腦比對) |
| 耗時 | 8,186 秒(約 2 小時 16 分) |
| 產物 | CLAUDE-SECURITY-20260913-021915/ |
| runner 交付 | ❌ 四件皆無,首腦補寫 |
.env」。