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

FR-109 問卷套件(jedi-survey)資安掃描

✅ 兩棒掃完並收口;發現已全數處理(2026-10-01 同步)——14 條(M05-1~14)全部已修、隨 1.21.0 出貨,無裁定不修項;淨新增 12 條(母卡 CM-1880、子卡 CM-1881~1882 一次建完連號)。套件正式碼 162 檔,剔除 81 支無邏輯檔後兩棒 81 檔:V2 填答全鏈(31,✅ 2026-09-17 掃完,7 條全過)+ V1 建題全鏈與守門殼插件(50,✅ 2026-09-18 掃完,7 條全過、其中 5 條新增 2 條與 V2 重疊)。兩棒皆已首腦驗收,arc 收口,淨新增 12 條登記進跨 arc 總表第 89~100 項+§3.2 第 24 項。本 arc 的核心問題已答:FR-048 那道守門只涵蓋寫入路徑,讀取路徑全無守門——填答側 0/8,建題側同型複製。

狀態:✅ 已完成 文件 2 份

這是資安掃描系列的一個 arc。 跨 arc 的結論、修正卡狀態、還沒開卡的待辦與覆蓋率,在跨 arc 總表:security-scan-consolidated/。本頁只講這一支。

🔴 一頁看完

兩棒都掃完、也都通過首腦驗收,這個 arc 收口了:V2(填答)7 條、V1(建題)7 條,合計淨新增 12 條(V1 有 2 條跑到填答側、與 V2 重疊,由 V2 收口)。登記落點:跨 arc 總表第 89~100 項,另加 §3.2 第 24 項(不算資安、但確實是程式錯誤)。

§1

結論(三句話)

  1. 讀取路徑幾乎是零守門,而且兩半都一樣——填答側 8 條讀取路徑一條都沒有守門(0/8),建題側的問卷討論列表同型複製(只掛登入、查詢條件全選填,送空白請求就把全公司討論撈回來)。2026-07 補的那道門只裝在「寫」的路上。
  2. 四個網址入口的欄位驗證被 apply=False 關掉,前端送什麼後端就收什麼——塞一個 is_delete 就繞過「非空資料夾不可刪」的守門,或蓋掉內部開關叫出系統刻意隱藏的資料夾。這是與讀取守門無關的第二個獨立成因,其他 20 支套件要搜一遍同樣寫法。
  3. 跨客戶隔離實測是正常的——開卡時懷疑「資料庫隔離的關聯鏈可能斷掉」,兩棒都用開發資料庫唯讀實測推翻:政策會遞迴套用各表自己的規則,鏈收斂到問卷的客戶欄位;正式環境的服務也是走受隔離的帳號連資料庫。所以這 12 條的影響範圍是「同一客戶內部跨專案、跨部門」,不會跨客戶。
§2

細部說明

兩棒共同的病根:這個套件把「你有沒有權限做這件事」跟「你能不能碰這一筆資料」搞混了,而且兩半都一樣。 填答側 8 條讀取路徑 0 守門;建題側同型複製——討論列表只要登入就能撈光全公司留言,有「改問卷」權限的人能改寫任何人的留言而且改完還掛原作者的名字。能力點只管動作、不管物件,這是兩棒共同的病根。

V1 另有一個獨立成因:四個路由上的 @use_kwargs(..., apply=False) 讓框架完全跳過驗證,那行等於裝飾品——於是前端送什麼後端收什麼,可以塞 is_delete 繞過刪除守門、可以蓋掉內部開關叫出系統隱藏資料夾。詳見 V1 報告。

V2 結論一句話:2026-07 補的那道門只裝在「寫」的路上,「讀」的路一扇門都沒裝。 填答側 14 條 route 裡,6 條寫入路徑全部有守門(6/6),8 條讀取路徑一條都沒有(0/8)——任何登入帳號(不必被指派、不必參與專案)都能讀走同一客戶內所有問卷的答案與歷史,空白請求就回全部。另有「還原歷史不驗那個版本是不是這份問卷的」與「socket 房間想進哪間進哪間」兩條。詳見 V2 報告。

開卡時的兩大嫌疑,一中一不中:讀取類無守門 ✅ 命中(F1~F4);RLS join 鏈斷裂 ❌ 實測推翻——連 DEV 用唯讀查詢驗過,資料庫執行政策子查詢時會遞迴套用各表自己的政策,鏈確實收斂到 surveys.tenant_id,隔離正常。所以七條的影響範圍是「同客戶內跨部門」,不是跨客戶。

問卷功能分兩半:建題(管理者建問卷、分頁、題目、資料夾,可從 Excel 匯入、複製、發佈)與填答(稽核任務指派某人填、多人同時填、有歷史版本可還原、可從 Excel 匯入答案、可在題目底下討論)。建題靠「有沒有買問卷功能」+四顆能力點守;填答靠「你是不是被指派的人或專案管理者」守——後者是 2026-07 FR-048 補的洞,補之前任何登入者可改任何人的填答(守門檔頭自己這樣寫)。

§3

首腦開卡時已開檔核出的(未經三位檢查員投票,寫進卡當錨點)

  • 填答側讀取類端點沒有任何歸屬守門:TaskSurveyRoute.get、TaskSurveysAnswersRoute.get(讀答案)、歷史清單/明細三支——route 只掛登入,service 也沒呼叫 FR-048 那兩支守門 helper。任何登入者拿 task_survey uid 就能讀別人的填答與歷史。與跨 arc 總表 🅰 組同型,是 V2 最大嫌疑。
  • 討論的改刪不驗本人:survey_discussion_service.py:78 delete_discussion(uid) 連 user 都不收。與 CM-1630 同型。
  • RLS 靠 join 反查:15 張表全開 RLS 各 4 條規則(FR-094 CM-1773 已做),但只有 surveys/survey_folders 有租戶欄,其餘 13 張靠 policy 內 EXISTS(... JOIN ...) 反查——首腦截到的 question_answers policy 只驗「題目存在且掛在某頁」,看不到往上到租戶的條件。join 鏈斷了就等於「有列就放行」。兩棒都要驗。
  • socketio 房間:fill_survey_socketio_handler.py:59-103 用 room 名(task_survey_uid)取答案快照廣播,有沒有驗身分是 V2 重點。
  • 守門殼是正面案例:缺 license_guard/capability_required 拒絕掛載;但它是 21 支裡唯一用模組級全域存接線的(已知取捨)。
  • 建題側 survey_route.py 讀取只掛授權、寫入七支全掛能力點;另四條 route 檔(folder/page/question/discussion)的能力點掛法要逐支對。

V1 掃完後的回填(四條 route 共 21 個端點已逐支對完):寫入端一支不漏、沒有掛錯的;缺口在討論列表(只掛登入 → V1-1)。兩點觀察不算漏洞:reorder/move 掛 create 而非 update 是四檔一致的刻意選擇;新增留言不掛能力點是合理的(填答者要能留言)。守門殼的 auth_required 疑慮已排除——REQUIRED_WIRING 四項含它,缺任一項當場拒絕掛載。翻譯表隔離鏈條完整但脆弱:規則只驗父列存在,靠父表自己的規則傳遞隔離,父表規則一旦放寬四張翻譯表會一起破且無跡象。

總進度表

棒 卡 範圍 檔數 狀態 首腦驗收
V2 CM-1881 任務問卷/填答/patch/checkpoint/歷史還原/答案 Excel 匯入/socketio/FR-048 守門 helper 全部呼叫點 31 ✅ 掃完(2026-09-17,報告)|7 條全過(2 嚴重 5 中等),24 票全投出、驗證章 verified ✅ 已驗 09-17(淨新增 7 條→跨 arc 總表第 89~95 項;首腦認同第 ⑦ 項「隔離沒壞」的推翻結論)
V1 CM-1882 問卷/資料夾/分頁/題目/討論五條 CRUD、Excel 匯入、複製搬移發佈、api 守門殼與 route 表、plugin、migration 50 ✅ 掃完(2026-09-18,報告)|7 條全過(5 中等 2 輕微),24 票全投出、兩輪驗證章皆 verified;拆兩輪各 25 檔跑 ✅ 已驗 09-18(淨新增 5 條→跨 arc 總表第 96~100 項,另 §3.2 第 24 項;2 條越界至填答側=第 89/90 項重複不計;runner 人工推翻的三個懷疑首腦復核後維持)

一次只派一棒(可與別套件的棒用另一帳號並行)。掃描版本基準:jedi 套件 repo c80207e(branch feature/review)。

⚠️ 剔除的 81 支:空 __init__、mapper、純資料 entity、model、dto——工具讀了不會有產出;query entity 保留(決定空條件行為)。主專案接線 10 檔(core/plugins/survey.py、三顆 DI 容器、infra/survey/adapters.py、survey_handler.py、儀表板申報)另棒。

需求討論紀錄

§4

為什麼先派 V2

總表 §0.5 排這支的理由就是「驗補上去的檢查是否涵蓋所有入口」,那個檢查在填答側。V1 建題側是能力點守門的形狀,與其他套件同型,價值較低。

§5

為什麼按「建題/填答」切、不按目錄

domain/service/、infra/repository/ 各 12~13 支混了兩條鏈。按鏈切,V2 從 route 到 repo 完整看到「誰能動填答」,V1 完整看到「誰能動問卷本體」。守門殼放 V1 是因為它與 route 表在同一層。

§6

共同設定

項目 值
主 session 模型 Opus 5 (1M context),不要用 Sonnet
effort low
scanRoot ~/Projects/Jedicogy/module/jedi-python-package/jedi-survey
派工節奏 一次只派一棒,驗收完才派下一棒
§7

🔴 前幾個 arc 換來的紀律(本系列必照做)

掃完要逐項回頭核工單的「重點看什麼」——快篩模式下工具常只咬住最顯眼的檔(task_survey_service.py 612 行)。越界一律標,人工七~八項才是主體。修正卡當時不開(決策者 09-17 裁:等全部掃完分類後統一開);後續已統一派工並全數修完(CM-2034/2060/2194/2251、FR-114.1-4a,1.21.0 出貨)。報告格式抄 FR-077 R2a。

§8

相關座標

  • 首腦手冊:.claude/skills/security-scan-lead/SKILL.md
  • 跨 arc 總表:docs/features/security-scan-consolidated/
  • 設計:FR-049 問卷流程規則、FR-049.1 顯示條件
  • 補洞來源:FR-048 統一授權守門(docs/analysis/2026-07-07-unified-auth-guard-design.md);隔離來源:FR-094 CM-1773
  • 套件路徑:~/Projects/Jedicogy/module/jedi-python-package/jedi-survey/
  • branch:jedi 套件 repo 與 BE repo 都在 feature/review(2026-09-17 開卡時實查)
§9

文件

以下全部由 build 掃資料夾產生,新增檔案重 build 即自動出現。標題連結指向渲染後的 HTML,md 連向源檔。

證據與盤點

文件 類型 標題 最後更新
scan-V1-authoring-chain / md 盤點證據 V1 檢查結果:建題全鏈——問卷/資料夾/分頁/題目/討論+Excel 匯入+守門殼與插件(jedi-survey) 2026-09-18
scan-V2-answering-chain / md 盤點證據 V2 檢查結果:任務問卷填答全鏈(jedi-survey) 2026-09-17
§10

Notion 卡

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

關係 卡號 標題 狀態
母案 CM-1880 FR-109 jedi-survey 問卷套件資安掃描(兩棒 81 檔,只掃不修) —
子卡 CM-1881 V2 填答全鏈:任務問卷/填答/歷史還原/socketio/FR-048 補洞驗證(31 檔) 修正待驗證
子卡 CM-1882 V1 建題全鏈:問卷/資料夾/分頁/題目/討論+Excel 匯入+守門殼與插件(50 檔) 修正待驗證