FR-095 · 需求索引 · 本頁由 build 掃資料夾生成
✅ 已掃完(2026-09-15)。三棒 P1/H1/P2 共 109 檔,首腦逐棒驗收;發現登記總表 §3.1 第 60~71+§3.2 第 17~23,已全數修好、刪除或退役,隨 1.21.0 出貨。
第 1 棒 P1 掃完了。結論:任何一個能登入的帳號,送一個「什麼條件都不填」的空請求,就能把全公司所有客戶的專案成員名冊一次撈光(DEV 實測 751 筆,含專案編號、誰是管理者、成員的登入帳號與暱稱)——連「哪個專案」都不必知道。系統只檢查「你有沒有登入」,沒檢查「你是不是這個專案的人」;查詢條件又全部非必填,程式把「沒條件」當成「不用過濾」,直接回整張表。開卡時首腦以為要知道一個專案編號才讀得到,實際比想的更嚴重。控制項層的名冊(哪個群組、哪個控制項指派給誰)另兩支 API 同一個病,用遞增的整數編號就能猜。這五張表在資料庫層的隔離(讓每個客戶只看到自己資料的機制)全是關的,沒有第二道防線。登入帳號外洩可拿去做針對性密碼攻擊,「誰是哪個專案管理者」可拿來挑社交工程目標。 修法:兩處補一行「你是不是這個專案的成員」檢查+把專案編號改成必填,與前面 FR-086/FR-088 同一套修法。
runner 另外人工追出三條工具沒報的:第六支成員服務整支沒守門(主系統的接線少傳了一個角色服務,服務就靜默全開);流程參與者的守門寫在 return 後面、它宣稱的兜底方法根本不存在(現況是 500 不是放行,「靠一個 bug 擋另一個 bug」);寫入成員時只驗你是不是這個專案的管理者,不驗你填的群組/控制項編號屬不屬於這個專案。卡片七項疑點全答:五成立、一排除(「查不到人回空值」只影響畫面顯示,不影響授權)、一大致健康。 首腦逐條開檔核對,無改判。
📄 P1 報告
第 2 棒 H1 掃完了。結論:主專案這一側最大的洞不在接線,而在「專案摘要報告」——清單與歷史版本共四支讀取功能只檢查有沒有登入、沒問「你是不是這個專案的人」,同一個檔案的新增/修改/刪除/還原都有問、只有讀取漏掉。清單那支送一個空請求就整批回來,內容是稽核結論全文。這條要看時序:掃描當時(下午 3 點的版本)這兩張表沒開資料庫隔離,任何客戶的登入者都撈得到其他客戶的稽核結論,工具因此評次嚴重;掃完 4 小時後平行進行的 FR-094 第 9 棒(CM-1800)把隔離開了,跨客戶那一半被資料庫擋住,現在剩「同客戶內不是這個專案的人也讀得到」,首腦據此降為中等——缺口本身沒修,只是外洩範圍縮了一級。另外兩條:稽核輪次選單帶別人的專案編號就能看那個專案的輪次清單(同模組其他功能都有守門、只有它漏);客戶的租戶管理員改稽核範本時可以把原廠公版的適用控制項清單整個改掉,因為那兩句是直接下 SQL、繞過資料庫隔離,之後每個客戶用公版開的新專案都會複製到被竄改的範圍。
接線本身健康:轉接器直接委派主專案的守門、沒有吞例外;六支成員服務的組裝只有 P1 已登記的那一支漏注入;四處 enforce_role=False 裡三處靠 update_project 的條件式守門(P1 第 62 條同型)、一處靠 route 層功能權限擋住不是漏洞。專案啟動時的成員同步刻意繞過套件守門直接寫表——合理(建專案的人就是管理者),但這是主專案第五處不經套件守門動名冊的地方,登記備查。卡片七項疑點工具只碰到一項的一半,其餘首腦自答。 ⚠️ 這棒的 runner 掃完沒交報告、沒回寫,四件交付由首腦補寫(全計畫第五次)。
📄 H1 報告
第 3 棒 P2 掃完了。結論:任務指派清單這支 API 跟 P1 的成員名冊是同一個病——只驗登入、八個查詢條件全部非必填,送一個空請求就把全庫 12,494 筆任務指派整張表撈出來,裡面有每筆指派給誰、那個人的登入帳號與暱稱、誰是審核者。第二個問題在寫入端:新增任務指派時,系統只檢查「你填的專案編號」你是不是那個專案的管理者,卻不檢查「你填的任務」真的屬不屬於那個專案——自己開一個專案當管理者,就能把別人專案的任務登記成自己的指派。這條要看時序:runner 掃描當時(9/14 下午 3 點)六張成員表的資料庫隔離全部是關的,掃完 4 小時後平行進行的 FR-094 第 9 棒(CM-1800)把六張表隔離全部打開,首腦驗收時複核(9/15 凌晨 4 點)確認六張表隔離都已經是開的——所以原本「隔離全關」這件事不算新發現,改記為「掃描之後事實已經變了」,後續要不要重新出貨基線交給 FR-094 那條線收尾。另外首腦更正了自己開卡時的一個誤判:原本以為「批次指派」這支功能前端沒人呼叫,runner 核對後發現前端其實有呼叫、頁面是活的,只是後端恆回空、按下去什麼都不會發生——這是功能失效,不是資安問題,建議另開卡處理。
📄 P2 報告
開掃前首腦讀碼的疑點與背景(三棒已逐條回答,結果見上方各棒與總表):
這支套件管的是「誰是這個專案的人、他是什麼角色、哪個任務指派給誰」——系統每次要判斷「你在這個專案能做什麼」,最後都是來查它管的表。前面 FR-086(檔案)、FR-088(流程引擎)找到的「只驗身分不驗歸屬」問題,修法都是「補一行去問任務平台」;如果任務平台這一層自己有洞,前面所有修法都建在沙上。
首腦親自開檔核對過、確認屬實的六個疑點(未經工具面板驗證,行號可直接打開):
participant/app/service/project_participant_service.py:43-45 直接 get_all(**kwargs))。登入+知道任一專案編號就能讀走別家客戶的成員名單。這是本專案「讀端點漏守門」第五次出現。di_containers/flow_engine/project_participant_containers.py:113-117 建 ProjectGroupParticipantService 時沒傳 participant_role_service,套件那支檔 grep 零守門)。DI 漏一條線=該服務靜默全開,不報錯不留 log。目前它沒掛 route,但 AI 儀表板讀得到。process_participant_service.py:55-56、task_assignee_service.py:213-214)。拿一個不存在的編號就不會被檢查。app/flow_control/service/job_batch_complete_service.py:50-56 is_manager = True,註解自陳「沿用舊守門『context 不齊就跳過檢查』」)。這正是跨 arc 總表第 59 條當時寫「未核對」的那個點。enforce_role=False 主動繞過套件守門(project_service.py:460/467/474、module_frame_service.py:242),而它們依賴的上游守門 update_project :373-375 又是同款「有注入才檢查」條件式。di_containers/dashboard_apis/participant.py:19-64)——用自然語言問 AI「列出專案 X 的成員」就能拿名冊,與總表第 8/10 條同型、位置不同。資料庫層實況(DEV 唯讀實查):這支套件管的 11 張表沒有一張隔離是開的,9 張連租戶欄都沒有。套件自己的 migration 檔頭寫「沒有 RLS 是刻意的,租戶隔離由專案層擋」——但 projects 表的隔離也是關的,前提已被總表第 43 條推翻。與 FR-086「四層全空」同一形狀。
產品裡每一個稽核專案都有成員:管理者、稽核員、審核者、只能看的人。每個任務(例如「檢查某條控制項」)會指派給某個成員去做。「任務平台」這支套件(jedi-task-platform)就是管這些的:誰是成員、他是什麼角色、哪個任務指派給誰、任務怎麼啟動、任務下面的留言。
它分三個子模組:participant(成員與角色、任務指派,10 條對外 API)、task(任務留言/匯入匯出/批次完成/執行啟動/儀表板,10 條 API)、project(專案資料存取,沒有 API)。
這個 arc 要回答三個問題:① 有沒有辦法讀到或改掉別家客戶(或別的專案)的成員名冊與任務指派?② 守門是不是「有接線才檢查、沒接線就放行」——接漏一條就整段開放?③ 這 11 張表在資料庫層有沒有隔離兜底?
git ls-files 實數):套件 195 檔(tests 外全部)/8,551 行;宿主接線 git grep -l jedi_task_platform 命中 63 檔/約 13,850 行,另 3 檔(common/authz/project.py、common/authz/workflow.py、di_containers/dashboard_apis/participant.py)不 import 套件、手動帶入。participant/api/guards.py:10-12 CAPABILITIES 空 tuple),入口只有宿主注入的 jwt_required;六支成員服務的寫入方法全是 if enforce_role and self._participant_role_service: 才檢查。「套件拿到什麼守門、什麼身分」的唯一答案在宿主的 core/plugins/ 與 di_containers/。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_participants → control_group_participants → project_participants → None。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 共用。| 表(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_participants/process_participants/project_control_participants/project_group_participants |
❌ 關 | 0 | 無 | 0~11 | 套件 migration 001-participant-tables.sql:16-21 自陳「沒有 RLS 是刻意的,租戶隔離由專案層擋」——但 projects 的隔離是關的,前提已被總表第 43 條推翻 |
11 張表沒有一張隔離是開的,9 張連租戶欄都沒有。「隔離開關」=RLS(就是「每個客戶只能看自己資料」的資料庫隔離機制);「有租戶欄」=這張表有沒有一欄寫著資料屬於哪個客戶——沒有的話資料庫層想擋也擋不了,只能靠程式檢查。
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 |
一次只派一棒。
接縫:P2 可越界讀 participant/common/guard.py 但不報(那是 P1 的)。
| 棒 | 卡 | 檔 | 面板 | 發現 | 報告 |
|---|---|---|---|---|---|
| P1 專案成員新增/移除/角色指派+守門入口+掛載契約 | CM-1780 | 39 | ✅ 12 票全投出,4 條全 3:0 通過(驗證章 unverified 係 findings-refused:渲染路徑前綴問題,非面板失敗) |
2 真問題(工具 4 條去重)+人工追加 3 條(P1-3/P1-4/P1-5);首腦 2026-09-14 驗收通過、無改判,登記總表 §3.1 第 60~64+§3.2 第 17~19 | scan-P1-participant-membership-and-guard.md |
| H1 專案 CRUD+成員同步+接線(plugin/DI) | CM-1781 | 35 | ✅ verified,24 票全投,8 條全 3:0 通過,無拒收 |
4 真問題(工具 8 條:2 條鏡像、3 條舊案重複)+人工備查 1 條(H1-5);首腦 2026-09-14 驗收、1 條 HIGH→MEDIUM(CM-1800 時序),登記總表 §3.1 第 65~67+§3.2 第 20;⚠️ runner 未交付四件,首腦補寫 | scan-H1-project-crud-and-wiring.md |
| P2 任務指派+六張參與者表資料存取+migration | CM-1782 | 35 | ✅ verified,6 票全投,2 條全 3:0 通過,無退件 |
2 真問題(工具 4 條去重)+人工追加 4 條(P2-3~P2-6);最嚴重「空請求撈光全庫 12,494 筆任務指派」;⚠️ 卡片「批次端點 FE 無呼叫者」經核對有誤,FE 實際有呼叫、頁面是活的(功能靜默失效,建議另開卡);首腦驗收:P2-3 改判事實變更(CM-1800 已開) | scan-P2-task-assignee-and-participant-tables.md |
核 stamp(首腦親開 CLAUDE-SECURITY-20260914-033458/CLAUDE-SECURITY-REVISION-df85e350a191.json):
| 欄 | 值 |
|---|---|
status |
unverified,reason_kind: findings-refused——「unverified」的第三種型態:面板完整跑完,但渲染器退掉 F1/F4(研究員寫的檔案路徑多帶一層 jedi_task_platform/ 前綴,對不上掃描根目錄)。與「面板全滅」(FR-077 R1)、「撞額度續跑」(FR-078 N2)都不同。判準:unverified 要看 reason_kind;findings-refused = 面板有效、只是渲染丟檔,以 runner 手寫報告為準、正式產物少的那幾條要人工補回。 runner 這棒處理正確 |
candidates / candidates_deduped |
4 / 4 |
panel_votes |
12(4×3,全投) |
panel_quorum_findings |
2(正式產物 .jsonl 只含 F2/F3,F1/F4 被退但票已投完) |
researchers_dispatched / returned |
2 / 2 |
unreviewed_candidate_sites |
0 |
revision |
套件 df85e350,dirty: false |
| scope | 39 檔(與卡片對上) |
逐條開檔核對(不信 runner 自述):
| 條 | 嚴重度(首腦裁) | 核對 |
|---|---|---|
| P1-1 查專案成員名冊零守門+條件全非必填→空請求全表(工具 F1+F2) | HIGH(面板投 MEDIUM/HIGH/MEDIUM 取 MEDIUM,runner 升 HIGH,首腦維持 HIGH:門檻只要登入、不需任何編號、跨客戶 751 筆含登入帳號與角色,與 FR-086「登入即可跨客戶取檔」同級;面板降級理由「資料敏感度中等」,但依決策者「可利用門檻」判準應為高) | ✅ project_participant_service.py:43-45 get_all(ProjectParticipantQueryEntity(**kwargs)) 一行到底(開卡時已核);serializer 兩欄無 required;AI 儀表板 dashboard_apis/participant.py:19-29 同路徑 |
| P1-2 控制項層名冊兩支零守門,整數編號可遞增猜(工具 F3+F4) | MEDIUM(同意) | ✅ project_control_participant_service.py:142/:167 零守門,:59/:103/:136 寫入有;runner 標「UserEnrichmentMixin 遮蔽效果未核」屬誠實標記 |
P1-3 第六支服務 project_group_participant_service.py 全檔零守門(人工) |
MEDIUM(同意;無 route 直通,AI 儀表板可讀) | ✅ 開卡時已核:DI project_participant_containers.py:113-117 未傳 participant_role_service,套件檔 grep 零命中 |
P1-4 流程參與者守門寫在 return 後+宣稱的 validate 不存在(人工) |
MEDIUM(同意;現況是 500 不是放行,「靠一個 bug 擋另一個 bug」) | ✅ 首腦親核:ProcessParticipantDomainService 全檔 27 行 7 個方法,validate_participant_user_is_exist 只存在於 ProjectParticipantDomainService:45/:51;process_participant_service.py:92/:113 呼叫的方法不存在→AttributeError。_assert_manager_for_process :55-56 return 在 assert_project_manager 之前 |
P1-5 寫入成員只驗 project_id 的 manager,不驗 group_id/control_id 屬不屬於該專案(人工) |
LOW~MEDIUM(同意;要先是某專案 manager) | ✅ control_group_participant_service.py:82 assert_project_manager(role_service, project_id, group_id=group_id),全檔 grep 無 group→project 歸屬檢查 |
| 排除:卡片疑點 ⑤「查不到人回空 dict 會不會反向放行」 | — | ✅ 同意排除:user_directory.py 七處降級只影響顯示欄位,授權走 guard.py,無交集 |
越界檢查:4 條候選全在 39 檔內,密鑰專項零撈獲,無範圍外發現。runner 對照既有卡正確(總表第 43 條講表無隔離、本棒講 API 無守門,不重複)。
scope 漂移:掃描版本 df85e350 到套件 HEAD 9a0b489,participant/ 零 diff。H1(下一棒)scope 自開卡 commit 747bd1be 到 BE HEAD 零 diff,可直接派。
首腦改判:無。唯一與面板不同的是 P1-1 維持 HIGH(理由見上表)。
非資安附帶發現(登記總表 §3.2 第 17~19):三支 service 各一支恆回 (0, 0) 的 _resolve_ids_from_uids stub(*_by_uid 方法永遠查 project_id=0,補了守門用 uid 路徑測會假通過);ProcessParticipantService 宿主接了沒人用(workflow_execution_service.py:85/:113 收進來零呼叫);process_participants 表 DEV 0 筆且改/刪路徑必 500,功能等於不能用、沒人回報過。
新增待裁(已進總表 §6 A 組):六支成員服務「有注入守門服務才檢查權限」這種寫法要不要整組改掉——改成「沒注入就拒絕啟動」是治本但要動 project_service.py 與 module_frame_service.py 四處內部呼叫;維持條件式只補 P1-3 那支注入是最小改動但形狀留著會再長回來。
FE 契約風險(未定論):修 P1-1 要把 project_id 改必填,首腦 grep FE src/ 找 project-participants(非 menu)呼叫點零命中——可能 FE 用別的常數名或沒直接呼叫;開修正卡時要再查一次,不可直接信「零命中=安全」。
登記總表 §3.1 第 60~64。
runner 掃描完成(stamp 19:05)但交付四件(報告/commit/Notion append/狀態)全部沒做,首腦依手冊第三節第 3 點補寫報告並回寫五處。以下是首腦親核的結論。
核 stamp(首腦親開 CLAUDE-SECURITY-20260914-070510/CLAUDE-SECURITY-REVISION-739a0a61d92e-dirty.json):
| 欄 | 值 |
|---|---|
status |
verified(工具確認完整,無拒收原因) |
candidates / candidates_deduped |
10 / 8 |
panel_votes |
24(8×3,全投) |
panel_quorum_findings |
8 |
researchers_dispatched / returned |
2 / 2 |
unreviewed_candidate_sites |
0 |
duration_s |
14,400(4 小時) |
| scope | 35 檔(與卡尾補正段對上) |
revision |
BE 739a0a61,dirty: true(平行 session 未 commit 改動在工作區;H1 scope 內檔案 dirty 狀態首腦查為零) |
| 掃描版本→HEAD 的 scope diff | 只有 app/module_frame/service/module_frame_service.py +5 行(平行 session 補「改 scope 欄位」守門 assert_scope_writable),不影響本棒任何結論 |
逐條開檔核對(首腦親做):
| 工具條 | 白話 | 首腦判定 | 核對 |
|---|---|---|---|
| F1+F2(HIGH) | 專案摘要報告清單 POST /api/1.0/project-summary-reports 只驗登入、查詢無範圍條件 |
範圍內、新、HIGH→MEDIUM(理由:CM-1800 時序,見報告 §1) | ✅ infra/project_summary_report/repository/project_summary_report_repo_impl.py:43 只過濾 is_delete;app/project_summary_report/service/project_summary_report_service.py:53/:73 兩支讀取無 _require_participant,同檔 :100/:111/:138/:164 有;route api/project_summary_report/routes/project_summary_report_route.py:72 只 jwt_required。DEV 實查(19:23):compliance.project_summary_reports RLS 已開、4 條規則、無 tenant 欄、43 筆——是 CM-1800 走 EXISTS 繞 projects 補的;掃描版本 739a0a61(15:03)早於 CM-1800(18:52),掃描時未開 |
| F6(MEDIUM) | 摘要報告歷史紀錄兩支讀取帶任意 report_uid 即可讀 | 範圍內、新、MEDIUM(與 F1 併一條) | ✅ project_summary_report_history_service.py:26/:31 無檢查,同檔 :51 revert 有;project_summary_report_histories RLS 同樣 CM-1800 後已開 |
| F4(MEDIUM) | 稽核輪次選單 GET /grc/project/<project_uid>/assessment-plans/menu 只驗登入不驗專案成員 |
範圍內、新、MEDIUM | ✅ project_service.py:487-491 直接把 project_uid 送 domain service,全路徑無 assert_project_participant;同模組其他專案端點都有。projects 表 RLS DEV 已開(FR-094 CM-1768),只擋跨客戶,同客戶內跨專案仍可讀 |
| F8(MEDIUM) | 租戶管理員改「模組框架」帶 include_controls,可改原廠公版(scope=SYSTEM)的適用控制項——兩句 raw SQL 繞過 RLS |
範圍內、新、MEDIUM | ✅ module_frame_service.py:120-122 → resource_library_app_service.py:403/:411 raw SQL;module_frames_select policy 明文放行所有租戶讀 SYSTEM 列(02-schema.sql:24284);DEV 實查 oscal.profile_imports/oscal.ssp_implemented_requirements RLS 關、0 規則、無 tenant 欄。掃後平行 session 補的 5 行只守「改 scope 欄位」,控制項清單那條沒守 |
| F5(MEDIUM) | AI 儀表板專案清單強制 is_admin=True |
重複:總表 §3.1 第 10 條(project_service.py:224) |
— |
| F3(HIGH) | 每套安裝同一組原廠 super admin 密碼(scripts/init/06-admin.sql:41) |
重複:總表 §3.1 第 3 條(FR-079 F15,決策者裁先記錄) | — |
| F7(MEDIUM) | 五支腳本與 memory 檔含 admin/blsadmin/blsit 明文密碼 | 重複:CM-1608(決策者裁測試機專用、只做衛生) | — |
範圍外:F3/F7 是密鑰專項撈到的 scope 外舊案;F5 在 scope 內但已登記。4 條新、3 條重複、0 條首腦否決。
卡片「重點看什麼」逐條回應(首腦自答,工具只碰到 ③ 的一半):
| 卡片疑點 | 結論 |
|---|---|
① DI 漏注入角色服務=靜默全開(project_participant_containers.py:113-117) |
✅ 屬實(P1 已登記 §3.1 第 62 條,本棒是宿主側同一件事,不另計)。首腦另 grep 六支 service 的 DI 建構:project_participant_containers.py:84/:94/:102/:110+task_assignee_containers.py:66 共 5 處傳了 participant_role_service,ProjectGroupParticipantService 是唯一漏的;沒有別的容器另建同款服務 |
② 7 支成員查詢曝給 AI 儀表板無歸屬檢查(dashboard_apis/participant.py) |
✅ 屬實,工具未報(注意力被 F5 那支 project 查詢吸走)。P1 報告 §6 對照表已逐支登記;本棒登記為 §3.1 第 60/61 條的 AI 儀表板路徑,不另計;7 支明細見 H1 報告 §6 |
③ 四處 enforce_role=False+update_project 條件式守門 |
✅ 屬實。module_frame_service.py:242 上游守門首腦補查:所在方法 start_project_from_module_frame 方法內零守門,但 route module_frame_route.py:117-118 掛 @jwt_required()+@require_capability("module-frame.create")——有功能權限守門(能建範本專案的人才能從範本開專案),且開專案的人自動成為該專案 manager 是設計(FR-048 D5)。:242 那處 enforce_role=False 有上游守門,不是漏洞,記為「靠 route 層能力點擋住」 |
| ④ 接線 adapter 有沒有吞例外/把查不到轉放行 | ✅ 沒有。core/plugins/participant.py:90-102 _ProjectRoleGuardAdapter.assert_manager 直接委派 common.authz.project.assert_project_manager,無 try/except;註解自陳「canonical 留主專案是刻意的,同一支守門被 flow_control 與 task_survey 共用 8 處」 |
| ⑤ 守門函式本身 | ✅ P1 已核,無 fail-open、無 super_admin 短路;本棒不重審 |
| ⑥ 專案啟動時的成員同步 | ⚠️ 設計自陳、記錄不判:project_start_app_service.py:85-88 註解「直接用 domain service(lean)寫 participant,不走 full ProjectParticipantService」——刻意繞過 app service 層守門直接寫表;:164-170 建立者不在名單時自動補一筆 manager。合理(建專案的人就是 manager),但這是「第五處繞過套件守門」,與 ③ 同型,登記 §3.2 第 20 非資安備查 |
⑦ 跨模組 resolver(ssp_project_resolver.py、drive_project_verify_service.py) |
✅ 前者 docstring 自陳會解析「current user 在該 project 內的角色」(:4、:111 is_writable);後者以 tenant_id 為參數查 project_participant_domain_service(:73-87)。兩支都是內部 resolver 非入口,呼叫端守門歸各自 route。無新發現 |
首腦改判:F1+F2 HIGH→MEDIUM(CM-1800 時序)。其餘無。
非資安附帶發現(登記總表 §3.2 第 20):專案啟動時成員同步刻意繞過套件守門直寫 domain(project_start_app_service.py:85-88),與第 62 條同型備查。
新增待裁:無新增——H1-5 與 P1 已登記的 §6 A 組「有注入才守門要不要整組改」是同一個決策,改的時候六處繞過點都要重新接,已在該條備註。
交付狀況:runner 未交付四件(全計畫第五次:C1/S7/B2/H3/H1)。報告由首腦補寫(scan-H1-project-crud-and-wiring.md),Notion CM-1781 append 補寫段+狀態「修正待驗證」、母卡 CM-1779 append 驗收表,run ID wf_9b402a56-704(首腦從 workflow 目錄找到,目錄時間 19:07 與 stamp 19:05 吻合)。
登記總表 §3.1 第 65~67+§3.2 第 20。
核 stamp(首腦親開 jedi-task-platform/jedi_task_platform/CLAUDE-SECURITY-20260914-130805/CLAUDE-SECURITY-REVISION-ca60cfd8efc7.json):
| 欄 | 值 |
|---|---|
status |
verified,無拒收 |
candidates / candidates_deduped |
4 / 2 |
panel_votes |
6(2×3,全投);panel_quorum_findings 2(兩條全 3:0) |
researchers_dispatched / returned |
2 / 2 |
unreviewed_candidate_sites |
0 |
duration_s |
8,107(2 小時 15 分) |
| scope | 35 檔(與卡片對上) |
revision |
套件 ca60cfd8,dirty: false |
| scope 漂移 | 掃描版本→套件 HEAD 347ce49:participant/+migrations/ 零 diff |
逐條開檔核對(首腦親做):
| 條 | 白話 | 首腦判定 | 核對 |
|---|---|---|---|
| P2-1(工具 F1,MEDIUM) | 查任務指派清單 API 只驗登入、八個條件全選填,空請求撈光 12,494 筆(含被指派人登入帳號與暱稱、誰是審核者) | ✅ MEDIUM 維持 | ✅ task_assignee_service.py:69-71;serializer :4-12 八欄全選填 |
| P2-2(工具 F2,工具原評 HIGH→面板 MEDIUM) | 新增指派用自填 project_id 判管理者,不驗 task_uid 屬不屬於該專案 |
✅ MEDIUM 同意面板 | ✅ :144/:148/:164 三行間無比對 |
| P2-3(人工) | 六張表隔離全關、出貨基線未含 | 改判:事實已變更。runner 掃描時(9/14 15:08)屬實;FR-094 第 9 棒 CM-1800(commit 18:53、DEV 19:17 套入)已把六張表 RLS 全開。首腦 9/15 04:05 DEV 唯讀複核:六張表全 relrowsecurity=t、各 4 條 policy。不登記為新發現,出貨基線是否重產歸 FR-094 收口 |
✅ DEV 實查;schema_migrations 有對應 migration 19:17 紀錄 |
| P2-4(人工) | 六支資料存取層沒有一支自己加專案範圍條件,是 P2-1/P1-1/P1-2 的共同根因 | ✅ 登記為根因(MEDIUM),修法二選一交決策者 | ✅ 六支 repo 皆未 override get_all/get_one,走共用底層 base_repository_impl.py:512 |
| P2-5(人工) | task_assignees.project_id 不是任務真正所屬流程,DB 無法用外鍵擋 P2-2;基線有擋這件事的觸發程序但 DEV 未掛 |
✅ MEDIUM 維持,與 P2-2 併一條說明「無 DB 兜底」 | ✅ DEV 實查:pg_trigger 0 筆、函式存在未掛,通知 FR-093 |
| P2-6(人工,非資安) | 批次指派端點恆回空,但 FE TaskSetupView.vue:292 實際在呼叫,功能靜默失效 |
✅ 登記非資安一條,更正首腦開卡時「FE 無呼叫者」的判斷(首腦當時 grep 用錯 glob 沒命中) | ✅ 首腦重 grep 命中 :292 |
| 四支「宿主零取用」方法 | get_task_assignee_menu/get_task_assignees_with_inherits/get_user_task_queue/delete_by_project_id 零守門、無 route |
✅ 登記備查,與 P1 §3.2 第 18 同型 | runner 對照表 |
疑點① update_task_assignee 撈不到就跳過守門 |
現況靠後面 validate_assignee_is_not_exist 拋 404 兜底,與 P1-4 同型但這邊 validate 確實存在 |
✅ 登記一條 LOW,修法:撈不到就直接拋 404,不依賴後面那支 | ✅ :213-216 |
越界檢查:4 條候選全在 35 檔內,密鑰專項零撈獲,無範圍外發現。0 條首腦否決;1 條改判(P2-3 事實變更);1 條更正首腦自己的開卡判斷(P2-6)。
首腦改判:P2-3 由「新發現」改判為「事實已變更」,理由見上表。
新增待裁(已進總表 §6 A 組第 15 項):「送空條件就回整張表」這個共用底層行為要不要在底層改掉——這是 P1 成員名冊、P2 任務指派、FR-086 檔案清單第三次撞到同一個根因,選在底層加「完全不帶條件就拒絕」(一次擋掉但要盤點全站刻意查全表的地方)還是逐支 API 補「專案編號必填+成員檢查」(改動可控但同型問題會再長出來)。
交付狀況:runner 交付四件齊全(報告、commit ddd52ed9、Notion append 含 run ID wf_ae470f56-685、狀態「修正待驗證」),全計畫第八次「兩句都寫」做滿。
登記總表 §3.1 第 68~71+§3.2 第 21~23。
發現登記進跨 arc 總表 §3,由 FR-114 修正線統一修好,隨 1.21.0 出貨。
~/Projects/Jedicogy/module/jedi-python-package/jedi-task-platform/jedi_task_platform/(scanRoot 指此子目錄,與 FR-086 B1/FR-088 同做法)core/plugins/participant.py、core/plugins/task.py、di_containers/flow_engine/、di_containers/dashboard_apis/participant.py、app/flow_control/、api/flow_control/、infra/flow_control/、common/authz/project.pyprojects/job_executions 隔離由 CM-1768/1770 接走).claude/skills/security-scan-lead/SKILL.mdsecurity-scan-consolidated/FR-075/handoff/security-scan-STATE.mdfeature/FR-075(2026-09-14 查)。不切 branch。以下全部由 build 掃資料夾產生,新增檔案重 build 即自動出現。標題連結指向渲染後的 HTML,md 連向源檔。
| 文件 | 類型 | 標題 | 最後更新 |
|---|---|---|---|
| scan-H1-project-crud-and-wiring / md | 盤點證據 | FR-095.2 H1:專案 CRUD+成員同步+接線 plugin/DI——資安掃描報告 | 2026-09-14 |
| scan-P1-participant-membership-and-guard / md | 盤點證據 | FR-095.1 P1:專案成員新增/移除/角色指派+守門入口+掛載契約——資安掃描報告 | 2026-09-14 |
| scan-P2-task-assignee-and-participant-tables / md | 盤點證據 | FR-095.3 P2:任務指派+六張參與者表資料存取+migration——資安掃描報告 | 2026-09-14 |
卡片內容(決策紀錄、驗收條件)以 Notion 為準,本頁只記座標。