本卡屬 FR-114 資安修正(母卡 CM-2019),CM-2053(5A-2)驗收時攢下的待裁項,決策者 2026-09-24 裁 A 案:只收窄「權限不足」。中。改套件 jedi-integrity(已在本批 18 支發版清單內),發版卡 CM-2110 第三段等本卡。
客戶的產品主機因竄改被鎖住後,除了檔案系統上有一個鎖定標記檔,資料庫 public.integrity_tamper_events 也記一筆。開機時若標記檔不見了,會去資料庫查這台有沒有未解鎖的事件,查到就把標記補回去、照樣鎖住。問題是:查詢時任何錯誤都被當成「資料庫連不上」而放行。有主機權限的人可以先刪標記檔、再用資料庫管理員帳號把這張表的讀取權限拿掉——重開機時查詢失敗、放行,鎖定就被繞過了。
決策者裁定(A 案):
連不上資料庫(逾時、拒絕連線、DNS) → 放行並警示(不變)
表不存在 → 放行並警示(不變;整庫還原不會缺表,缺表視同還原不完整,不擋客戶)
表存在、查詢成功 → 照結果判定(不變)
表存在、但沒有讀取權限(permission denied) → 🔴 改成拒絕開機
本機 DB 帳密設定缺欄位(DB_SECRET 缺 DB_USER/DB_PASSWORD)→ 🔴 改成拒絕開機(設定錯誤,BE 本體也連不上,訊息寫清楚比放行好)
其他未預期錯誤 → 放行並警示(不變,保守)
「刪表」這條繞法本卡不擋,記成已知限制(見「做完要回寫報告」)。
套件 jedi-integrity/jedi_integrity/app/boot_db_lock.py:21-38 find_db_lock():try 包住 store.find_active_lock,except Exception 一律印警示回 None(放行)
套件 jedi-integrity/jedi_integrity/domain/ports.py:107-122 IBootLockStore 契約,docstring 寫「連不上一律 raise,由閘門決定放行」
套件 jedi-integrity/jedi_integrity/app/startup_gate.py:75-99 _refuse():統一拒啟出口(印訊息 → 起 lockdown 殼 → SystemExit)
套件 jedi-integrity/jedi_integrity/app/startup_gate.py:233-235 呼叫 find_db_lock 的位置
BE common/integrity/boot_lock_store.py PostgresBootLockStore:psycopg 短命連線(connect_timeout=3),load_secrets() 取 DB_USER/DB_PASSWORD
BE pyproject.toml:24-25 psycopg==3.2.4
套件 /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/.claude/worktrees/jedi-wt-fix-security branch fix/security-b1
BE /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/.claude/worktrees/wt-fix-security branch fix/security-b1
🔴 主 checkout 給掃描線讀,絕不動。⚠️ 發版卡 CM-2110 與郵件卡 CM-2111 可能同時在套件工作區改檔:你不動任何 pyproject.toml,commit 前 git status 只 add 自己的檔。
jedi_integrity 新增一個例外類別(例 BootLockStoreAccessDenied,放在 ports.py 或 domain 對應處並 export),IBootLockStore docstring 補一句「連得上但無權讀取(或本機設定缺帳密)→ raise BootLockStoreAccessDenied;連不上 → raise 其他例外」。boot_db_lock.find_db_lock:先 except BootLockStoreAccessDenied → 拒絕開機;其餘 except Exception 維持現行放行並警示。拒絕開機走 startup_gate._refuse 同一個出口(起 lockdown 殼),不要自己 print+sys.exit。實作上可讓 find_db_lock 把該例外往上拋、由 startup_gate.py:233 附近接住後呼叫 _refuse,或在 boot_db_lock 裡回一個明確的拒絕結果——選一種,保持 _refuse 為唯一拒啟出口。PostgresBootLockStore.find_active_lock:psycopg.errors.InsufficientPrivilege → 轉拋 BootLockStoreAccessDenied;psycopg.errors.UndefinedTable → 維持原樣往外拋(被套件當一般錯誤放行);連線類(psycopg.OperationalError 等)維持往外拋。_connect() 取 secrets["DB_USER"]/["DB_PASSWORD"] 時的 KeyError → 轉拋 BootLockStoreAccessDenied(訊息註明是設定缺欄位)。record_event 不改(它本來就 best-effort)。python -c "import psycopg.errors as e; print(e.InsufficientPrivilege, e.UndefinedTable)")。BootLockStoreAccessDenied → 閘門拒啟(走 _refuse、lockdown 被呼叫);store 拋一般例外(模擬連不上)→ 放行並印警示;store 回 None → 放行;store 回事件 → 補標記並鎖(既有行為)。突變:把新的 except 分支刪掉,第一條要紅。PostgresBootLockStore 在 execute 拋 InsufficientPrivilege 時轉成 BootLockStoreAccessDenied;拋 UndefinedTable 時不轉;secrets 缺 DB_USER 時轉。可用 mock 連線,不必真的連 DB。