FR-079 · 需求索引 · 本頁由 build 掃資料夾生成
✅ 兩棒皆掃完並驗收(2026-09-09);發現已全數處理——公告改刪驗歸屬、發送範圍過濾、儀表板旁路等已修(FR-114.1-5a/CM-2026、CM-2038,1.21.0 出貨);公告部門關聯表未開租戶隔離與原廠系統殼帳號裁定不修;版控殘留金鑰與密碼已清(CM-2048)
這是六個資安掃描 arc 之一。 六個 arc 合起來的結論、修正卡狀態、還沒開卡的待辦與覆蓋率地圖,在跨 arc 總表:
security-scan-consolidated/——要問「總共掃出什麼、還有哪些要修」看那份,本頁只講這一支。
這是第五個資安掃描 arc,目標是「公告」這條鏈。 兩棒都已掃完(B1 2026-09-08、B2 2026-09-09),結果見下方進度段。
公告是系統裡「一則訊息要發給哪些人看」的功能:管理員發公告、指定要發給哪些部門,其他人在自己的畫面上看到。這個 arc 要回答的是——有沒有人可以看到不該看的公告、改別人的公告、或發公告給不屬於自己的部門。
全套件掃描排名把 jedi-bulletin 歸在「讀取展示型、攻擊面小」那組(29 檔、847 行),看起來風險最低。但那個評估只看了套件那半邊。
首腦這次讀完程式碼發現:套件本體確實只有沒有守門的 CRUD,而**「誰能看到哪一則公告」的判定整個在主專案這邊**,且判定邏輯出乎意料地繞——部門過濾的開關是呼叫端自己傳的、查詢條件用「物件欄位當白名單」動態組裝、單筆讀取完全不做範圍檢查。這是一條被低估的鏈。
| 棒 | 卡 | 範圍 | 規模 | scanRoot |
|---|---|---|---|---|
| B2 宿主接線 | CM-1610 | api/ app/ domain/ infra/ di_containers/ 五處 + dashboard 註冊檔 |
33 檔/899 行(實質 23 檔) | BE repo |
| B1 套件本體 | CM-1611 | jedi_bulletin/ + harness/ |
29 檔/847 行(實質 16 檔) | jedi monorepo 的 jedi-bulletin/ |
分兩棒的唯一理由是工具的 scanRoot 只能指一個目錄,而這條鏈天然橫跨兩個 repo。檔數各自都在 30 檔尺內(2026-09-08 實測邊界),不是因為量才切的。與 FR-078(N1 套件 30 檔/N2 宿主 18 檔)完全同構。
建議先跑 B2——首腦標出的十個重點有八個落在那半邊。兩棒可平行(不同 repo、互不依賴),但額度考量下串行較穩。
這些是讀過程式碼後認為最容易出事的地方,是給研究員的思考起點不是檢查清單。工具不會照這份清單走——FR-076 L2 的 HIGH 就是 runner 照這種清單自己追出來的。
@require_capability,列表與單筆讀取只有 @jwt_required()。與已修的 CM-1585/CM-1589 同形狀,是本專案已知會重犯的病。get_bulletin(uid) 只吃一個 uid,不看部門、不看建立者、不看 enable、不看發布與過期時間。列表那條費心做的部門過濾在這條路上完全繞過。auth 是 request body 裡的布林值,不是伺服器判定的;else 分支對「沒有部門的使用者」註解明寫「不過濾、顯示全部公告」。_gen_filters() 在 query_entity.__dict__ 上跑迴圈、用 hasattr(model, field) 決定要不要變成 SQL 條件,而 BulletinQueryEntity.__init__ 收 **kwargs、serializer 是 apply=False 手動 load()。呼叫端的鍵能不能變成查詢條件要實查。update_bulletin 拿到 uid 直接改,delete_bulletin 連 user 參數都沒有。跨租戶最終只靠 RLS 擋,而 bulletins 表的 RLS 現況未經查證(migration 註解已明指其 INSERT policy 與其餘三支不對稱)。org_units 的部分更新會清空公告的可見範圍。CM-1588 同類,該案已確認為真 bug。content 是完全不驗證的 Raw 欄位——寫入讀出原樣進出,DB 是無長度上限的 Text。FE 渲染方式要實查再下結論。公告是「發給很多人看」的內容,XSS 受害面比一般欄位大。dashboard_apis/bulletin.py 把 get_bulletins 註冊給儀表板呼叫,不經過 route 層守門。bulletin_service.py 最後一行。CLAUDE.md 的「repo session 必須 lazy」鐵則相關。另有五個方法各自 new 一份 repo 繞過注入、update() 第 53 行檢查了參數而非查詢結果(查無此筆會 AttributeError)。generate_uuid 的實作要人工看一眼——公告 uid 若不是密碼學等級隨機,重點 ② 那條無守門的單筆讀取影響就放大。這是跨棒依賴:B1 的結論直接影響 B2 該條的嚴重度判定,B1 卡已要求「即使工具沒報也要人工看一眼並寫進報告」。plugin.py 檔頭有 60 行極詳盡的自我辯護文字(自稱跨 jedi 套件 import 0 處、os.getenv 0 處、「本來就是乾淨的」)。這正是工具已知盲點「會被程式碼註解說服」的高危形狀——FR-075 的 S2 就是八個檔零發現,但 signed_token.py 的真洞被 docstring 寫成「刻意設計」,研究員讀到就接受了。
這個自陳是那一棒(P3.5 / CM-1471)的自我宣稱,不是驗證過的事實,正好要在本 arc 查證。驗收時首腦要確認 runner 有獨立結論,而不是複述檔頭。
沿用前四個 arc 的教訓,逐條都有代價:
low——單一研究員掃全範圍+三人面板;medium 會展開成矩陣派 40+ 個無共用 context 的 agentattack-surface——跳過 tests/dist/docs,並附帶跑一次密鑰專項(FR-078 六條裡有四條是它掃出來的,免費收益)/claude-security 帶 disable-model-invocation: true,指令交決策者親手打| 棒 | 卡 | 狀態 | 報告 | 首腦驗收 |
|---|---|---|---|---|
| B2 宿主接線 | CM-1610 | ✅ 掃完(2026-09-09) | scan-B2-host-wiring.md |
✅ 已驗(09-09);發現已處理(見 SUMMARY 總表 M17-1~11:已修 9、裁定不修 2) |
| B1 套件本體 | CM-1611 | ✅ 掃完(2026-09-08) | scan-B1-package-core.md |
✅ 已驗(09-08) |
F12(JWT 簽章金鑰散在版控 31 個檔)→ 降為衛生類,併入 CM-1607,DEV 金鑰不更換。
首腦驗收時原主張把它從 MEDIUM 升到 HIGH(實測與現行 .env 一致、已上 origin/main)。決策者問「這個值安裝時是不是動態產生?如果目前已經有,那就不用修」——這一問推翻了判級:scripts/installer/install.sh:1252 每套安裝各自跑 openssl rand -base64 32,外流的只是我們開發機那把,不是任何客戶的。影響從「所有客戶安裝」縮成「我們自己的 DEV」,與 CM-1608「自己的測試機」同判準。版控殘留字串仍要清,但性質是打掃不是堵漏。
F15(installer 每套部署種同一組原廠 super admin 密碼)→ 裁定不修(總表 M17-7)。 這是原廠系統殼帳號,密碼只有原廠知道、不交給客戶,用於原廠支援;客戶日常管理員由客戶在設定精靈自建自設密碼。與 F12 相反——JWT 金鑰動態產生了,這個沒有;本 arc 不開卡不派工。
教訓:判密鑰外洩的嚴重度,第一問是**「客戶端用的是不是同一把」**,不是「這把是不是活的」或「散在幾個檔」。沒查 installer 有沒有動態產生就談嚴重度,會系統性高估。已寫進 STATE 教訓段。
十條發現全數 3/3 全票通過面板,verification.status: verified、unreviewed_candidate_sites: 0、38 票、候選 13 去重 11(另有 1 條 Turnstile 測試金鑰被 0:2 否決)。範圍內 4 條、密鑰專項範圍外 6 條,無 HIGH 以上。
範圍內四條(全在 app/bulletin/service/bulletin_service.py):
dashboard_apis/bulletin.py 註冊的 get_bulletins 不經 route 層守門,且呼叫端不傳 auth 使部門過濾永遠不生效 → 任何登入者可讀全租戶公告(含草稿、過期),且前三筆原文送第三方 LLM。即母卡重點 ⑧+②,本棒最重要的發現@require_capability,delete_bulletin(uid) 連 user 參數都沒收;連帶 :165 無條件清空部門關聯(重點 ⑤+⑥)範圍外六條:F12 JWT 簽章金鑰(34 處,與 .env 一致)、F15 installer 每套部署同一組原廠 super admin 密碼、F1 cmmgr(BYPASSRLS)密碼、F2 四家 LLM API 金鑰(九檔,5746cef1 只刪了 .env.test 沒動這些)、F3 Nexus+MinIO、F14 管理員密碼當 os.getenv 預設值。
🔴 重點 ① 有硬結論:這是漏守門不是設計如此。 bulletin.read(capability id 3)存在於 seed(04-seed-core.sql:218)、綁在 route 7 上且為 ALL 規則(:359)、系統設計文件明文寫了判定規則(generate_permission_system_docx.py:471-479)——但該字串在整個 Python 程式碼裡除文件產生腳本外一次都沒出現。與 CM-1585/CM-1589 同型。
🔴 重點 ⑩ 有母卡沒預期到的發現:bulletins RLS 齊全(四 policy,INSERT 的不對稱方向是更嚴格、屬設計);但 bulletin_org_units 整張表沒有 RLS、也沒有任何 policy。影響有限(只存兩個整數)但意味著「公告發給哪些部門」的關聯不受租戶隔離保護。
❌ 兩項本棒未觸及,不可當作乾淨:重點 ⑦(content 的 Raw 欄位儲存型 XSS,需開 FE repo 查渲染方式才能下結論)、重點 ⑨(審計欄位 subquery 是否繞過 users 表 RLS)。
過程教訓(值得記):前後撞兩次額度上限,第二次的 4 條被 dropped without a verdict(無下一輪可交棒),以 resumeFromRunId 續同一 run 補完 5 票。併檔時踩到工具邊界情況——同 shard 的兩份結果會靜默擇一保留,且 finding ID 互相衝突意義不同,差點把 3 條(含重點 ③⑤)吃掉。修法與判準寫在報告的「中斷與續跑」段。
工具零發現——2 條候選(_gen_filters 空轉的過濾條件、harness 明文密碼)皆被三人面板 0/3 否決,verification.status: verified、6/6 票全數投出、42 分鐘跑完。這是本 arc 第二次拿到面板完整跑完的紀錄。
人工核對十個重點另找到三條(未經面板,可信度見報告):update() 檢查錯對象查無此筆會 AttributeError(LOW,bulletin_repo_impl.py:56,一個字的修正)、無 user context 時建立公告會崩潰而非寫入 NULL(LOW,bulletin_service.py:50/62)、update() 無歸屬檢查(INFO,屬 B2 範疇)。三條都因「套件版 service 沒有任何呼叫者」而目前打不到,故未開修正卡;反悔條件寫在報告末段。
🔴 重點 ⑧ 的答案(跨棒依賴,直接影響 B2):generate_uuid 用 uuid.uuid4(),122-bit 密碼學隨機、不可預測(common/utils/common_util.py:4-5)。B2 重點 ② 那條「單筆讀取不做範圍檢查」的嚴重度應據此下調但不歸零——列舉不可行,但 uid 會出現在列表回應與網址上,「事後仍能讀到已移出可見範圍的公告」仍成立。
plugin.py 檔頭 60 行自陳經獨立 grep 查證屬實(跨 jedi 套件 import 0 處、os.getenv 0 處)——是自己跑 grep 得到的結論,不是採信檔頭。
移交 B2 的線索:① 主專案自己那支 _gen_filters() 要獨立查是否有同樣的空轉問題(套件端空轉無害,主專案端空轉=時間窗過濾沒生效);② 主專案的 BulletinQueryEntity 收不收 <strong>kwargs 要實查(套件端不收,是 fail-closed);③ bulletins 表 RLS 四條 policy 已於 DEV 唯讀查證齊全,INSERT 那條的「不對稱」方向是更嚴格**、屬設計非缺陷。
掃描當時(2026-09-08)決策者裁定修正卡先不派、由 PM 統一安排;09-21 起改照內化版問題總表(docs/security-report/M17-bulletin.md)派工。結果:公告相關 4 條(改刪驗歸屬、儀表板旁路、直開網址、列表過濾)已修(FR-114.1-5a/CM-2026、CM-2038,1.21.0 出貨);關聯表未設租戶隔離與原廠系統殼帳號裁定不修;版控殘留金鑰、密碼、對話紀錄已清並換發(CM-2048)。
.claude/skills/security-scan-lead/SKILL.mddocs/features/FR-075-2609-jedi-package-security-audit/handoff/security-scan-STATE.md../FR-078-2609-notification-security-scan/scan-N2-host-wiring.mdfeature/FR-075,不要切以下全部由 build 掃資料夾產生,新增檔案重 build 即自動出現。標題連結指向渲染後的 HTML,md 連向源檔。
| 文件 | 類型 | 標題 | 最後更新 |
|---|---|---|---|
| scan-B1-package-core / md | 盤點證據 | B1 掃描結果:jedi-bulletin 套件本體 | 2026-09-08 |
| scan-B2-host-wiring / md | 盤點證據 | B2 掃描結果:公告鏈宿主接線(主專案) | 2026-09-09 |