本卡屬 FR-079(母卡 CM-1609),第 1 棒:套件本體(jedi monorepo)。只掃不修。建議在 B2(CM-XXXX,宿主接線)之後跑。
用 claude-security plugin 掃 jedi-bulletin 套件本身的源碼(29 檔、847 行),找出公告資料的存取層有沒有資安問題,產出報告。只找問題、不修問題。
切成獨立一棒的理由不是檔數(29 檔本來就在 30 檔尺內),而是工具的 scanRoot 只能指一個目錄,而公告這條鏈橫跨 jedi monorepo 與 BE repo 兩個 repo。與 FR-078(N1 套件/N2 宿主)完全同構。
預期產出低於 B2。 這一半只有沒有守門的 CRUD,安全決策都外包給宿主了。但**「預期低」不是「不用掃」**——套件是可被其他產品接走的共用資產,它自己該有的自我保護(查詢條件怎麼組、tenant scope 有沒有掛、有沒有寫死值)要獨立看一次。
這些是首腦讀過程式碼後認為最容易出事的地方,是思考起點不是檢查清單;全部未經任何驗證,以你實際讀到的為準。
plugin.py 檔頭明寫本套件 mount_api=True 掛出來的 blueprint 沒有任何 resource,且有測試焊死。要確認的是:這個「刻意留白」有沒有留下可被繞過的入口——plugin.py(226 行)的四個插槽(adapters/config/schema_extensions/mount_api)本身有沒有能被外部影響的地方。jedi_bulletin/app/service/bulletin_service.py 最後一行:bulletin_service = BulletinService(BulletinDomainService(BulletinRepoImpl()))——模組被 import 時就實例化。CLAUDE.md 有明確鐵則:repo 的 session 必須 lazy,__init__ 不可 get_session(),否則實例化早於 @transaction 開 scope 會直接 500。要確認 BulletinRepoImpl 走的是 BaseRepositoryImpl 的 lazy @property session(看起來是,但要實查),以及這個單例有沒有人真的在用、會不會跨請求共用狀態。jedi_bulletin/infra/repository/bulletin_repo_impl.py:19-33 的 _gen_filters() 在 query_entity.__dict__ 上跑迴圈、用 hasattr(self.model, field) 決定要不要變成 SQL 條件。套件端的 BulletinQueryEntity(domain/entities/bulletin_query_entity.py)只收五個具名參數、不收 **kwargs(與主專案那支不同),所以套件端本身較安全——但要確認 app/service/bulletin_service.py 的 BulletinQueryEntity(**kwargs)(第 20、36 行)在收到未預期的鍵時是拋錯還是靜默吞掉。get_bulletins/get_bulletin/add_bulletin/update_bulletin/delete_bulletin 各自 BulletinDomainService(BulletinRepoImpl()) 就地新建(第 35、44、51、61、71 行),不用建構子注入的那個。這是可疑的重複,要看有沒有因此跳過某層應有的處理。add/update 是手寫欄位搬運,容易漏。 bulletin_repo_impl.py:35-69:add() 逐欄位建 model、update() 逐欄位覆寫。要看的是:update() 沒有覆寫 is_delete、也沒有任何歸屬檢查(filter_by(uid=...) 拿到就改);還有第 53-56 行有個 bug 形狀——bulletin_model 查出來後檢查的卻是 if not bulletin(檢查了參數而不是查詢結果),查無此筆時 bulletin_model 是 None,下一行 .title = 會 AttributeError。這是 DoS/錯誤處理問題不是權限問題,照實記,嚴重度自行判斷。add_bulletin 用 get_user_context().uid 當建立者,與主專案語意不同。 套件版(app/service/bulletin_service.py:50)存的是 uid,主專案版存的是 login_name。要看的是:get_user_context() 在無 user context 時(背景 thread/排程)會回什麼、會不會 None 而寫進 NULL 或炸開。jedi_bulletin/infra/models/bulletin.py 的 Bulletin(BaseModel, TenantScopedMixinModel),__tablename__ = "bulletins"。主專案的 ExtendedBulletin 用 extend_existing=True 蓋在同一張表上——兩個 model 對同一張表,要確認 tenant 欄位與 RLS 期待的欄位一致、不會因為 model 疊加而失效。generate_uuid 的實作。 jedi_bulletin/common/utils/common_util.py——公告 uid 是它產的,而 B2 的重點 ② 指出單筆讀取只靠 uid、不做任何範圍檢查。若這支不是密碼學等級的隨機(例如用 uuid1、時間戳、或序號),uid 就是可預測的,那條無守門的讀取路徑影響就放大。common/enum/code.py 的 error code。 看有沒有把內部資訊(表名、SQL、路徑)帶進對外訊息。harness/dev_app.py。 開發用的啟動殼,看有沒有寫死憑證、debug 開關、或會被誤帶進正式環境的設定。這類檔案是密鑰專項最常撈到東西的地方。