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

FR-095 任務平台(jedi-task-platform)資安檢查

🟡 已開卡待派(2026-09-14)。母卡 CM-1779;子卡 CM-1780~1786 七張一次建完連號。套件 163 檔+宿主 54 檔=七棒 217 檔,按「可利用門檻」排序:P1 → H1 → P2 → H2 → P3 → P4 → P5,一次只派一棒。決策者 2026-09-14 裁四支合併套件解除暫緩、第一支掃這支;detection/remote-agent 維持暫緩。

狀態:🟡 進行中 文件 0 份

🔴 一頁看完

尚未開掃,首腦讀碼疑點如下。

這支套件管的是「誰是這個專案的人、他是什麼角色、哪個任務指派給誰」——系統每次要判斷「你在這個專案能做什麼」,最後都是來查它管的表。前面 FR-086(檔案)、FR-088(流程引擎)找到的「只驗身分不驗歸屬」問題,修法都是「補一行去問任務平台」;如果任務平台這一層自己有洞,前面所有修法都建在沙上。

首腦親自開檔核對過、確認屬實的六個疑點(未經工具面板驗證,行號可直接打開):

  1. 五支「讀名冊」的 API 只驗登入,專案編號由呼叫端自己填participant/app/service/project_participant_service.py:43-45 直接 get_all(**kwargs))。登入+知道任一專案編號就能讀走別家客戶的成員名單。這是本專案「讀端點漏守門」第五次出現。
  2. 六支成員服務都是「宿主有注入角色服務才檢查、沒注入就跳過」,而且真的有一支沒注入di_containers/flow_engine/project_participant_containers.py:113-117ProjectGroupParticipantService 時沒傳 participant_role_service,套件那支檔 grep 零守門)。DI 漏一條線=該服務靜默全開,不報錯不留 log。目前它沒掛 route,但 AI 儀表板讀得到。
  3. 更新/刪除時「撈不到既有紀錄就 return」,守門寫在 return 之後process_participant_service.py:55-56task_assignee_service.py:213-214)。拿一個不存在的編號就不會被檢查。
  4. 批次完成任務預設把呼叫者當 managerapp/flow_control/service/job_batch_complete_service.py:50-56 is_manager = True,註解自陳「沿用舊守門『context 不齊就跳過檢查』」)。這正是跨 arc 總表第 59 條當時寫「未核對」的那個點。
  5. 四處 enforce_role=False 主動繞過套件守門project_service.py:460/467/474module_frame_service.py:242),而它們依賴的上游守門 update_project :373-375 又是同款「有注入才檢查」條件式。
  6. 7 支成員查詢直接曝給 AI 儀表板、無歸屬檢查di_containers/dashboard_apis/participant.py:19-64)——用自然語言問 AI「列出專案 X 的成員」就能拿名冊,與總表第 8/10 條同型、位置不同。

資料庫層實況(DEV 唯讀實查):這支套件管的 11 張表沒有一張隔離是開的,9 張連租戶欄都沒有。套件自己的 migration 檔頭寫「沒有 RLS 是刻意的,租戶隔離由專案層擋」——但 projects 表的隔離也是關的,前提已被總表第 43 條推翻。與 FR-086「四層全空」同一形狀。

§1

這在做什麼(白話)

產品裡每一個稽核專案都有成員:管理者、稽核員、審核者、只能看的人。每個任務(例如「檢查某條控制項」)會指派給某個成員去做。「任務平台」這支套件(jedi-task-platform)就是管這些的:誰是成員、他是什麼角色、哪個任務指派給誰、任務怎麼啟動、任務下面的留言

它分三個子模組:participant(成員與角色、任務指派,10 條對外 API)、task(任務留言/匯入匯出/批次完成/執行啟動/儀表板,10 條 API)、project(專案資料存取,沒有 API)。

這個 arc 要回答三個問題:① 有沒有辦法讀到或改掉別家客戶(或別的專案)的成員名冊與任務指派?② 守門是不是「有接線才檢查、沒接線就放行」——接漏一條就整段開放?③ 這 11 張表在資料庫層有沒有隔離兜底?

§2

為什麼掃它

  • 從未掃過,而且是每一輪掃描都會回頭指到的地方——「這個人是不是這個專案的人」這句話的答案在這支套件裡。
  • 決策者 2026-09-14 裁定:四支合併套件(task-platform/log/asset/system-core)解除暫緩,第一支掃 jedi-task-platform。detection/remote-agent 維持暫緩。
  • 規模(attack-surface 口徑,2026-09-14 git ls-files 實數):套件 195 檔(tests 外全部)/8,551 行;宿主接線 git grep -l jedi_task_platform 命中 63 檔/約 13,850 行,另 3 檔(common/authz/project.pycommon/authz/workflow.pydi_containers/dashboard_apis/participant.py)不 import 套件、手動帶入。
  • 守門的形狀是「宿主有給我守門函式我才檢查」:套件 participant 那一半沒有能力點守門(participant/api/guards.py:10-12 CAPABILITIES 空 tuple),入口只有宿主注入的 jwt_required;六支成員服務的寫入方法全是 if enforce_role and self._participant_role_service: 才檢查。「套件拿到什麼守門、什麼身分」的唯一答案在宿主的 core/plugins/di_containers/
§3

授權判定怎麼流(各子卡「已知背景」共用)

  • 守門函式在宿主 common/authz/project.py(120 行):assert_project_manager :16-30/assert_project_role_fallback :33-57/assert_project_role :60-98/assert_project_participant :101-120。守門函式本身沒有 fail-open(查不到角色就 raise),沒有拿租戶代替專案、沒有 super_admin 短路。
  • 角色查詢在套件 participant/app/service/participant_role_service.py:33-83,三層往上找:project_control_participantscontrol_group_participantsproject_participants → None。
  • 真正的 fail-open 不在守門函式,在呼叫端的條件包裝(上方疑點 2/3/4/5)。
  • 接線:宿主 core/plugins/participant.py:90-102 adapter → 套件 participant/common/guard.py:26-52 configure(),未接線時 :45-49 raise 不放行。task 那一半的三道守門在 core/plugins/task.py:89-98,與 jedi-compliance-audit 共用。
§4

資料庫層實況(DEV,2026-09-14 唯讀查,首腦親查)

表(compliance schema) 隔離開關 規則 有租戶欄 筆數 備註
projects ❌ 關 4 226 已登記總表 §3.1 第 5/43,FR-094 CM-1768 接走
project_participants ❌ 關 0 757 總表第 43 條提過一句,FR-094 未收
task_assignees ❌ 關 0 10,743 本 arc 新增實況,FR-087/094 都沒盤
job_executions ❌ 關 0 11,788 總表第 43/46 條,FR-094 CM-1770 接走
job_execution_comments ❌ 關 0 2,829 本 arc 新增實況
job_execution_devices ❌ 關 0 3,016 本 arc 新增實況
job_execution_org_units ❌ 關 0 3,019 本 arc 新增實況
job_execution_surveys ❌ 關 0 0 本 arc 新增實況
control_group_participantsprocess_participantsproject_control_participantsproject_group_participants ❌ 關 0 0~11 套件 migration 001-participant-tables.sql:16-21 自陳「沒有 RLS 是刻意的,租戶隔離由專案層擋」——projects 的隔離是關的,前提已被總表第 43 條推翻

11 張表沒有一張隔離是開的,9 張連租戶欄都沒有。「隔離開關」=RLS(就是「每個客戶只能看自己資料」的資料庫隔離機制);「有租戶欄」=這張表有沒有一欄寫著資料屬於哪個客戶——沒有的話資料庫層想擋也擋不了,只能靠程式檢查。

§5

怎麼切七棒(按「可利用門檻」排序,不按嚴重度;跨 repo 必分棒)

scanRoot 兩個:套件=~/Projects/Jedicogy/module/jedi-python-package/jedi-task-platform/jedi_task_platform;宿主=BE repo 根。

順序 repo 檔數 為什麼獨立一棒/為什麼排這 對應疑點
1 P1 專案成員新增/移除/角色指派+守門入口+掛載契約 套件 39 門檻最低:登入+自填 project_id 就能讀成員名冊;common/guard.py 是所有守門單一入口,要與六支 service 同棒才看得出誰有接誰沒接 1、2(套件側)、3
2 H1 專案 CRUD+成員同步+接線(plugin/DI) 宿主 35 「套件拿到什麼守門、什麼身分」的唯一答案在 core/plugins/;AI 儀表板繞守門;四處 enforce_role=False 2(DI 側)、5、6
3 P2 任務指派+六張參與者表資料存取+migration 套件 35 repo 查詢有沒有 project 範圍條件、表無租戶欄,放同棒一眼看完 1(task-assignees 那支)、3
4 H2 任務 CRUD/匯入匯出/批次完成/執行查詢 宿主 19 數千行大檔;批次完成 fail-open 是第 59 條的未核對點;匯入三步只有最後一步有守門 4
5 P3 任務留言/匯入匯出 route/執行啟動+三道守門殼 套件 32 task 半 10 條 route 與守門殼一次看完;任務留言 GET 無歸屬(與第 51 條是不同留言系統)
6 P4 任務領域模型+任務型別登記簿+四張 job_execution_* 套件 20 task_type_registry.py 是「拔一行某型別就消失」總開關;四張表資料庫實況已查
7 P5 專案 CRUD+人員指派剩餘領域層 套件 37 收尾棒;projects 是套件唯一帶租戶欄的表,驗證「租戶隔離由專案層擋」前提

一次只派一棒。合理停損點在第 4 棒之後(P1/H1/P2/H2 共 128 檔已涵蓋全部門檻最低的點),但沒跑完七棒不能宣稱「jedi-task-platform 掃過了」。

接縫common/authz/project.py 同時放進 H1 與 H2(刻意重疊,重複報由首腦挑掉);P2/P3 可越界讀 participant/common/guard.py 但不報(那是 P1 的);H2 可讀 app/flow_engine/service/workflow_execution_service.py 交叉參照但不報(FR-088 H4 已掃)。

檔數核對(2026-09-14 開卡時 git ls-files 逐檔驗證存在):

  • 套件 195 檔 → 五棒 163 檔,未入棒 32 檔全是 __init__.py(25 支 0 行、7 支 1~19 行純 re-export)。P1 的 39=brief 列的 30+participant/app/dto/ 下 9 支非 task_assignee 的 DTO;P4 的 20=brief 列的 20(task/domain/**task/infra/<strong> 其餘全是 0 行 __init__.py,不硬湊);P5 的 37=project/ 13+participant/domain/ 未入 P2 的 24,排除 0 行 __init__.py。沒有任何一棒超過 40,未觸發搬移規則。**
  • 宿主 grep 命中 63 檔 → 兩棒 unique 53 檔+手動帶入 3 檔。命中但未入棒 13 檔:9 檔是 FR-088 H4 已掃(可讀不報);di_containers/flow_engine/workflow_excution_containers.py 已補進 H1(它 import 套件的 TaskAssigneeDomainServiceTaskAssigneeRepoImpl 建 provider,屬 DI 接線);其餘 4 檔——api/project/serializers/project.pyconfig/app_modules.pycore/plugins/asset.pyscripts/deliverables/plugin_metrics.py——只是註解或 docstring 提到套件名,不入棒。
§6

進度

面板 發現 報告
P1 專案成員新增/移除/角色指派+守門入口+掛載契約 CM-1780 39
H1 專案 CRUD+成員同步+接線(plugin/DI) CM-1781 35
P2 任務指派+六張參與者表資料存取+migration CM-1782 35
H2 任務 CRUD/匯入匯出/批次完成/執行查詢 CM-1783 19
P3 任務留言/匯入匯出 route/執行啟動+三道守門殼 CM-1784 32
P4 任務領域模型+任務型別登記簿+四張 job_execution_* 表 CM-1785 20
P5 專案 CRUD+人員指派剩餘領域層 CM-1786 37
§7

修正卡

不開。依決策者裁定(只掃不修、全部掃完再一起排修正順序),發現登記進跨 arc 總表 §3。

§8

座標

  • 套件:~/Projects/Jedicogy/module/jedi-python-package/jedi-task-platform/jedi_task_platform/(scanRoot 指此子目錄,與 FR-086 B1/FR-088 同做法)
  • 宿主接線:core/plugins/participant.pycore/plugins/task.pydi_containers/flow_engine/di_containers/dashboard_apis/participant.pyapp/flow_control/api/flow_control/infra/flow_control/common/authz/project.py
  • 相關前案:總表 §3.1 第 5/10/43/44/45/46/51/59 條與 §2.9 🅰 組(各子卡「不要重報」段有清單);FR-086(四層全空的檢查清單);FR-088 H4(9 檔已掃可讀不報);FR-094(projectsjob_executions 隔離由 CM-1768/1770 接走)
  • 首腦手冊:.claude/skills/security-scan-lead/SKILL.md
  • 跨 arc 總表:security-scan-consolidated/
  • 現況:FR-075/handoff/security-scan-STATE.md
  • branch:BE repo 與 jedi 套件 repo 都在 feature/FR-075(2026-09-14 查)。不切 branch。
§9

Notion 卡

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

關係 卡號 標題 狀態
母案 CM-1779 FR-095 掃任務平台(jedi-task-platform:專案成員、任務指派、任務執行)資安掃描(七棒 216 檔,只掃不修)
子卡 CM-1780 P1 專案成員新增/移除/角色指派+守門入口+掛載契約(39 檔,jedi 套件) Not started
子卡 CM-1781 H1 專案 CRUD+成員同步+接線 plugin/DI(35 檔,BE repo) Not started
子卡 CM-1782 P2 任務指派+六張參與者表資料存取+migration(35 檔,jedi 套件) Not started
子卡 CM-1783 H2 任務 CRUD/匯入匯出/批次完成/執行查詢(19 檔,BE repo) Not started
子卡 CM-1784 P3 任務留言/匯入匯出 route/執行啟動+三道守門殼(32 檔,jedi 套件) Not started
子卡 CM-1785 P4 任務領域模型+任務型別登記簿+四張 job_execution_* 表(20 檔,jedi 套件) Not started
子卡 CM-1786 P5 專案 CRUD+人員指派剩餘領域層(37 檔,jedi 套件) Not started