範圍:8 檔/850 行(
core/plugins/asset.py、di_containers/asset/asset_containers.py、di_containers/dashboard_apis/device.py、common/util/system_asset_snapshot.py、core/plugins/issue.py、di_containers/dashboard_apis/feedback.py、common/code/feedback_code.py、接縫檔core/plugins/_host.py)。 掃描工具:Claude Code 官方claude-securityplugin,effort low,focus 生產程式碼。 掃描基準 commit:aa23a63554ce(工作區有其他 session 的未提交改動)。 驗證章:verified。只掃不修。
工具抓到的兩條(設備清冊誰都看得到、意見回饋誰都能改能刪)都是總表第 43、45 項的舊案,修法也已經寫好了(CM-2029/CM-2030),卡在「修好的套件還沒發新版」:修正分支上的資產套件和議題套件,版號跟正式版一模一樣(1.1.0/1.2.0),所以照現在的出貨流程打包,客戶拿到的還是沒修的版本。開發機上看起來已經修好,是因為開發機直接載入修正分支的原始碼,把這個落差蓋住了。
卡片點名要回答的「資產和議題為什麼沒有授權模組守門」,答案是刻意的、不是漏接:這兩個功能都在「基礎包」裡,每一張授權都包含它們,守不守結果都一樣(詳見第 5 節)。
另外,我讀程式時自己查到一條工具沒報、卡片也沒點名的:AI 儀表板查詢設備列表時,指定的容器名字寫錯了(device_container,實際叫 asset_container),這條查詢每次被叫都會出錯。這是功能壞掉,不是資安問題,但修正分支上也沒改到(第 6 節第 1 條)。
「資產」是設備清冊與資訊系統清冊;「議題」是右上角的意見回饋,以及它背後可以接 GitHub/GitLab 的問題單功能。兩個功能都已經搬成獨立套件,主專案這邊只留一支接線檔,負責把「怎麼驗登入、怎麼驗權限、目前是誰在操作」這幾樣守門零件交給套件,由套件自己掛在每一條網址上。
全專案只有資產、公告、議題三支是「整包吃 host_defaults() 預設守門」的寫法。所以這一棒要回答的不是「哪裡沒寫守門」,而是:
另外,AI 儀表板查設備、查意見回饋那兩條路,走的是另一個入口,不經過網頁那層的守門,要個別確認。
| # | 這是什麼問題 | 出事會怎樣 | 要先有什麼才打得到 | 該補檢查的位置 | 嚴重度 | 跟總表的關係 |
|---|---|---|---|---|---|---|
| W6-1 | 意見回饋的列表、查看、修改、刪除、刪附件五條網址,出貨版只驗「有沒有登入」 | 任何一個員工都能看到全公司的意見回饋,還能改掉或刪掉別人送出的回饋和附件;有接 GitHub/GitLab 的話,刪回饋會連帶關掉外部那張問題單 | 同一客戶的任何一個登入帳號 | 套件側 jedi_issue/api/routes/feedback_route.py 各條路由的裝飾器(修正分支已補,出貨版沒有);宿主側只要換版號 |
中 | =第 45 項,既有案、不另計。本棒補上「為什麼修好了還是沒進出貨版」 |
| W6-2 | 設備與資訊系統的七條讀取網址,出貨版只驗「有沒有登入」 | 管理員把某人的「查看設備」權限拿掉,那人只是選單看不到;直接打網址,照樣拿到整張設備清冊(主機名稱、IP、作業系統版本)與資訊系統負責人 | 同一客戶的任何一個登入帳號 | 套件側 jedi_asset/api/routes/device_route.py、information_system_route.py 的讀取路由(修正分支已補,出貨版沒有) |
中 | =第 43 項,既有案、不另計。同上 |
| W6-3 | 修正分支沒有進版號,出貨版鎖的還是舊版 | 上面兩條(還有同一批修正裡其他幾條)在客戶端全部沒有生效,但開發機手測會以為修好了 | 不需要任何攻擊動作——正常打包就會發生 | pyproject.toml:65(jedi-asset==1.1.0)、:77(jedi-issue==1.2.0) |
流程問題,不是單一漏洞;影響面比 W6-1/W6-2 加起來還大 | 新的。總表沒登記,建議首腦查一次「FR-114 修好的卡,套件側是不是都卡在同一個地方」 |
第 4 節兩條是經過三人面板投票的;W6-3 是我從面板理由順藤摸瓜核對出來的,沒有經過投票(見第 7 節)。
場景
一個一般員工,角色上沒有任何意見回饋相關的權限,所以側邊選單根本看不到「意見回饋管理」。他直接呼叫「列出回饋」的網址,拿到全公司每一筆回饋的編號;再拿其中一筆去呼叫「修改」或「刪除」,就能把同事送出的回饋內容改掉,或整筆刪掉(連附件一起)。如果公司有開 GitHub/GitLab 整合,刪回饋還會同時把外部那張問題單關掉。資料庫的租戶隔離只把範圍限制在同一家客戶裡。
為什麼會這樣
接線檔 core/plugins/issue.py 交給套件的守門零件是 host_defaults():登入檢查(jwt_required())加上能力點檢查工具(require_capability)。零件是給齊的。問題出在套件那側:出貨版的議題套件(1.2.0)只在「匯出」那一條用了能力點檢查,其他六條只掛了登入檢查,修改/刪除的服務方法也沒有「這筆是不是你的」比對。接線檔自己的說明也寫得很清楚(core/plugins/issue.py:21-25:「七條 route 裡只有匯出掛能力點……BE 沒守」)。
修法已經寫好了:CM-2030 在套件修正分支 fix/security-b1(commit 23c2ee69)上,把六條都補了能力點,改和刪也補了作者比對。但這個修正沒有發新版,見 W6-3。
嚴重度為什麼是中不是高:需要先有一個登入帳號,而且只能碰到同一家客戶的資料;受害的是意見回饋(不是稽核證據或帳號權限)。
場景
管理員把某個操作人員的「查看設備」權限關掉。那個人的側邊選單看不到設備頁,但他把網址貼上去(或直接呼叫 POST /devices、GET /devices/menu),照樣拿到整家客戶的設備清單:主機名稱、IP 位址、作業系統與版本,還有資訊系統清冊和每套系統的負責人。這份清單可以直接拿來找還沒更新修補的主機。
為什麼會這樣
同 4.1 的形狀:接線檔 core/plugins/asset.py 交出去的零件是齊的,但出貨版的資產套件(1.1.0)只在新增、修改、刪除上用了能力點,七條讀取網址只驗登入。CM-2029 在修正分支上已經補好(commit 0ecdd3a1),同樣沒發新版。
嚴重度為什麼是中不是高:只能讀、不能改;只看得到同一家客戶;需要登入帳號。
先講授權模組守門在做什麼:有些功能是「加價購」,例如問卷、檢測工具、AI 儀表板。客戶沒買,就算帳號有權限也不能用。這道門叫「授權模組守門」(require_license),它查的是「這家客戶的授權檔裡有沒有這個模組」。
查證結果:
AssetAdapters、IssueAdapters 只收登入、能力點、目前使用者、回應格式四樣;問卷的 SurveyAdapters 才有 license_guard。所以不是「套件要、宿主忘了給」,而是套件本來就沒設計這一道。license_center/src/license_center/core/module_catalog.py:8-14)把 device、information-system、feedback、feedback-view 都列在基礎包,三個預設方案(Basic/Professional/Enterprise)都包含整個基礎包。設計文件(FR-062-2608-license-management/design.md §6)的原話是:基礎包缺一系統不成立,加值包關掉系統仍完整。audit 40 處、project 19 處、cloud_integration 10 處、survey 2 處、remote-agent-manage 1 處;device、information-system、feedback、bulletin、user、role 這些基礎包模組都是 0 處。資產和議題跟其他基礎包模組一樣。common/middleware/license_readonly_mw.py),攔的是所有寫入請求,不分模組。所以授權過期的客戶一樣不能新增設備、送回饋。唯一的殘留風險:簽發站的方案表單允許逐項取消勾選模組(plan_form.html:58)。如果哪天有人真的發一張「沒有設備模組」的照,選單會隱藏設備頁,但網址不會擋。這屬於商務決策,不是程式漏接:設計上基礎包不拆開賣。建議首腦決定要不要在簽發站把基礎包的勾選框鎖住(見第 9 節第 3 條)。
卡片擔心的是:計數查詢受租戶隔離,只數得到自己家的引用,會不會少算?
compliance.job_execution_devices 這張關聯表(jedi_task_platform/.../device_reference_query.py:23-29)。這張表本身沒開租戶隔離(DEV 實查:relrowsecurity = f,2026-09-23 21:12),而且只有 id/job_execution_id/device_id 三欄。get_device_reference_count 會先用設備 uid 查設備(jedi_asset/app/service/device_service.py:126-138)。設備表(public.devices)有租戶隔離,查不到別家的設備就直接回 404,所以也不會回報別家的數字。結論:此項查證不成立,下次不用重查。「只有修改權限就能達成刪除效果」的形狀(總表第 112 項,資訊系統的「停用」欄位)跟計數無關,不在本棒範圍,CM-2029 已處理。
另外補一句給修的人:接線檔的說明(core/plugins/asset.py:41-47)與容器的註解(di_containers/asset/asset_containers.py:52「三張 job↔︎device 關聯表」)說法不一致,後者是 CM-1765 前的舊描述。這是註解過期,不是資安問題。
system_asset_snapshot.py):走同一套資料庫隔離,沒有繞過app/oscal/service/ssp_inventory_items_app_service.py 的新增、修改清單項目(第 147、159 行)。resolve_device_inventory_fields 在執行期沒有人呼叫(只有測試用到)。add_item、update_item 都是 self._perm.require_manager(ssp_uid)(第 75、102 行)。get_device_by_uid/get_by_uid,底下是 BaseRepositoryImpl,走請求當下的 session。public.devices、compliance.information_systems 兩張表都有租戶隔離(DEV 實查:兩張表的讀取政策都是「超級管理員,或租戶在允許範圍內」)。拿別家的設備 uid 來快照,會查不到、回 404。結論:與 FR-098 A2 對 ssp_resources_context_service.py 的結論相同,沒有繞過。此項查證不成立。
「用公司的權杖幫所有客戶開問題單」是設計,總表已登記為死碼待裁,本棒不重報。只確認權杖會不會外洩:
RootIntegrateConfigProvider.get_integrate_config(core/plugins/issue.py:70-76)透過 read_root_config_value 開一個獨立的 session 讀取,用完就 rollback、關掉(infra/system_config/system_config_root_reader.py:57-74)。讀取過程不寫日誌。self.config 裡(feedback_issue_service.py:55),用來判斷整合有沒有開、以及建立 GitLab/GitHub 用戶端。我 grep 了議題套件全部的日誌呼叫,沒有一處印出 config、token、private_token;回應格式(serializer/DTO)也沒有任何欄位帶設定內容。core/plugins/system_core.py:69 分到 issue-integrate-config 這組能力點(平台級,一般客戶沒有)。這條路不在本棒範圍,但門是有的。結論:權杖不會出現在回應或日誌裡。此項查證不成立。
先講 AI 儀表板這條路:使用者在儀表板上用一句話問問題,AI 挑一支「已登記的查詢」幫他呼叫,這條路不經過網頁那層的守門。所以每支查詢要自己說清楚「呼叫的人要有什麼權限」。
| 查詢 | 服務方法自己有沒有檢查權限或歸屬 | 現行分支(feature/review) |
修正分支(fix/security-b1,CM-2038) |
|---|---|---|---|
device.get_devices |
沒有。DeviceService.get_devices 直接查、直接回(jedi_asset/app/service/device_service.py:65-70),只靠資料庫的租戶隔離 |
申報沒寫 required_capabilities,儀表板套件也沒有「沒申報就拒絕」的規則 → 任何能開儀表板的人都查得到 |
申報補了 device.read;儀表板套件改成「沒申報就一律拒絕」 |
feedback.get_feedbacks |
沒有。FeedbackService.get_feedbacks 沒有「查看範圍」的過濾,一律回整家客戶的回饋(feedback_service.py:221-226);網頁那條 get_feedbacks_and_pager 才有 mine/all 的範圍判斷 |
同上,任何人都查得到 | 申報補了 feedback-view.read 或 feedback.read 任一 |
這兩條就是總表第 60 項(AI 儀表板 26 支查詢不問權限)裡的兩支,既有案、不另計。
但修正分支上意見回饋那支有一個「守了一半」的殘留,要跟首腦講清楚:修正後申報的是「feedback.read 或 feedback-view.read 任一」,但 get_feedbacks 這個方法不分範圍、一律回全部。網頁上,只有 feedback.read 的人預設只看得到自己的回饋(feedback-view.read 才是「看全部」那顆,見 jedi_issue/plugin/contract.py:36)。所以從 AI 儀表板查,只持有 feedback.read 的人會看到全部人的回饋,比網頁上看得到的多。
實務影響目前是零:DEV 上每個持有 feedback.read 的角色也都持有 feedback-view.read(實查 0 個例外,2026-09-23 21:13);出貨 seed 裡只有 Administrator 一個角色,兩顆都有。但只要有人照權限設計建一個「只能看自己回饋」的角色,落差就會出現。修法很小:修正分支上 di_containers/dashboard_apis/feedback.py 的申報改成只收 feedback-view.read。建議併進 CM-2038 的後續,不另開大卡。
common/code/feedback_code.py:死碼全專案只有 docs/api/feedback/generate_docx.py:657(文件產生腳本)的一個字串提到它,執行期沒有任何 import。議題套件有自己的一份 IssueProviderCode。列給 FR-092 系列清理,不是資安問題。
場景:使用者在 AI 儀表板問「列出所有設備」,AI 選到 device.get_devices 這支查詢,結果系統出錯,查不到任何東西。
原因:di_containers/dashboard_apis/device.py:19 寫的是 container_service("device_container", "device_service"),會去根容器上找一個叫 device_container 的屬性。但根容器上只有 asset_container:device_container=asset_container 只是把資產容器傳給別的子容器時用的參數名(di_containers/containers.py:182、:216、:227)。我實際建了一次根容器確認:hasattr(c, 'device_container') == False、hasattr(c, 'asset_container') == True。
推測是 FR-080 把設備模組搬成資產套件時(commit f858fc2d5)改了容器名,這支申報沒跟著改。修正分支上的同一行也沒改(CM-2038 只補了 required_capabilities)。
這是功能壞掉,不是資安問題:它是「查不到」,不是「查太多」。修正卡若要讓 CM-2038 補的 device.read 真的有意義,這一行要一起改成 container_service("asset_container", "device_service")。
| 資產 | 議題 | |
|---|---|---|
| 宿主給的零件 | host_defaults() 整包:登入、能力點、目前使用者、回應格式 |
同左,完全相同 |
| 套件契約要的 | 四樣都收,前三樣缺了就拒絕掛載(jedi_asset/plugin/assembly.py:80) |
登入、能力點缺了就拒絕掛載(jedi_issue/plugin/assembly.py:123) |
| 授權模組守門 | 契約沒有這欄;基礎包,刻意不守(5.1) | 同左 |
| 出貨版套件用了能力點的網址 | 寫入 4 條(新增、修改、刪除各一,資訊系統另三條),讀取 7 條沒用 | 匯出 1 條,其他 6 條沒用 |
| 修正分支 | 讀取 7 條補齊(CM-2029) | 6 條補齊+改刪補作者比對(CM-2030) |
| 另一個入口(AI 儀表板) | 壞掉(6.1)+未申報權限(5.5) | 未申報權限(5.5)+修正後範圍比網頁寬 |
並排的結論:宿主這邊兩支「給的」完全一致,沒有少給。問題全部在「套件拿到零件卻沒掛」(已由 FR-114 修好),加上「修好了但沒發版」(W6-3)。套件以為宿主守、宿主以為套件守的情況,本棒沒有找到:接線檔的註解都誠實寫出「BE 沒守」,套件的說明也沒有宣稱「守門由宿主做」。
這條是三人面板在投 W6-1/W6-2 時點出來的,我自己又逐一核對過:
scripts/build/build_release.sh 跑 poetry install,依 poetry.lock 裝 jedi-issue 1.2.0(wheel sha256 a896998c…)與 jedi-asset 1.1.0(d8089259…)。這兩個 wheel 裡的路由沒有補上能力點(面板實際核對過 wheel 內容)。jedi-python-package 的 fix/security-b1 上,jedi-issue/pyproject.toml 還是 version = "1.2.0",jedi-asset/pyproject.toml 還是 1.1.0。主專案修正分支的 pyproject.toml:65、:77 也還鎖這兩個版號。.venv 有一支 jedi_issue.pth,直接指向修正分支的 worktree(.claude/worktrees/jedi-wt-fix-security/jedi-issue)。所以在開發機上手測意見回饋,看到的是修好的版本。資產套件在本機倒是正式版 wheel(jedi_asset-1.1.0.dist-info,路由沒補),所以開發機上資產那條應該還重現得到。影響面:FR-114 板上 CM-2029、CM-2030 都已標 Done。如果首腦以「卡片 Done」當作「客戶端已修好」,這兩條(總表第 43、45 項)在客戶端其實都還開著。同一批其他動到套件的修正卡很可能是同樣狀況(例如 CM-2038 的儀表板套件,修正分支版號也還是 1.2.0)。我沒有逐張查,建議首腦派一棒統一盤點。
這不是我這棒的範圍能修的:發版與改 pin 依 CLAUDE.md 要決策者明示。這裡只負責報告。
「工具報的兩條存在嗎」——可信度高。三個獨立檢查員對兩條各投一票,6 票全數投出,兩條都是 3 票全數認定成立,嚴重度也沒被調降。面板實際比對過 lock 檔的 wheel 雜湊與 wheel 內容,不是只看開發機的原始碼。
「第 5、6、7 節」——runner 自行開檔核對,未經三人面板投票。關鍵事實都有實查:
device_container:實際建了一次 Containers() 確認。pg_class/pg_policies(2026-09-23 21:12)。feedback.read 卻沒有 feedback-view.read 的角色:DEV 實查 0 個(2026-09-23 21:13)。.pth 覆寫:開檔確認。「只有這些嗎」——不保證。
| 項目 | 數值 |
|---|---|
| 掃描範圍 | 8 檔/850 行 |
| 基準 commit | aa23a63554ce(工作區 dirty,有平行 session 改動) |
| 檔位 | effort low,focus 生產程式碼 |
| 研究員 | 派 2 支,回 2 支 |
| 原始候選 | 2 條,去重後 2 條 |
| 投票 | 三個檢查員 × 2 條 = 6 票,全數投出 |
| 票型 | W6-1 3:0(三票都評中);W6-2 3:0(三票都評中) |
| 驗證章 | verified(CLAUDE-SECURITY-REVISION-aa23a63554ce-dirty.json) |
| 工具 run ID | wf_cda2c1e8-d82 |
| 耗時 | 約 32 分鐘(8 個 agent,零失敗;其中 1 個回傳空結果,不影響候選數) |
| 工具產出原始報告 | CLAUDE-SECURITY-20260923-130819/(未入版控) |
asset_container(6.1,功能壞掉);② 意見回饋查詢的申報收窄成只收 feedback-view.read(5.5,修正後比網頁看得到的多)。common/code/feedback_code.py 列進 FR-092 死碼清單(5.6)。