Guidant AI 資安檢視總報告 · 連線清單
全部業務資料都存在這條線的另一端。這條線的特別之處是它自己會擋人——資料庫用「列級安全規則」(RLS)依每次連線設定的身分變數,決定哪一列看得到、改得動。程式層漏掉的守門,理論上由這道牆兜底;但牆有沒有建、建對了沒、身分有沒有帶上,就是這條線上 35 條掃描問題的全部。這一頁回答:這條線怎麼連、哪些功能走它、它帶來什麼威脅、駭客怎麼打、我們要怎麼防、目前做到哪。
這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這裡以連線為單位整理。C05 命中的 35 條依「駭客實際怎麼打」歸成七種手法,每種寫駭客怎麼打、得手什麼,底下列全部實例連回 STRIDE 頁。個別問題修了沒不在這頁講。
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB'}}}%%
flowchart LR
subgraph PROC["三個行程(同一組 DB 帳密 cm_app)"]
direction TB
API["guidant-api<br/>請求期:身分來自 JWT"]
SIO["guidant-socketio"]
WK["guidant-worker・排程<br/>無人登入:身分要自己宣告"]
end
subgraph SS["每次開交易(session_scope)"]
direction TB
S1["有身分 →<br/>SET app.user_id<br/>SET app.allowed_tenant_paths<br/>SET app.is_super_admin"]
S2["沒身分 →<br/>什麼都看不到+記一行警告"]
S3["具名系統身分 →<br/>is_super_admin = t<br/>(繞牆,顯式宣告)"]
end
subgraph DB["guidant-db :5432(不對外開埠)"]
direction TB
W1[("有牆的表<br/>規則讀變數決定每列")]
W2[("沒牆的表<br/>oscal.* 52 張只 5 張有牆<br/>靠程式層當唯一防線")]
V[("視圖<br/>以查詢者身分執行")]
end
API --> SS
SIO --> SS
WK --> SS
S1 == "C05 · TCP :5432 明文(docker 內網)" ==> W1
S1 --> W2
S3 -. "看全部" .-> W1
S2 -. "0 列" .-> W1
W1 --> V
classDef bad fill:#FDECEC,stroke:#C0392B
class S2,W2 bad
| 項目 | 現況 |
|---|---|
| 誰 → 誰 | guidant-api、guidant-socketio、guidant-worker(含 worker 內的排程)三個行程 → guidant-db(PostgreSQL 16 容器)。走 docker 內部網路 guidant_default,guidant-db 沒有 ports: 映射、不對外開埠,只有 stack 內的服務連得到 |
| 協定/埠 | TCP :5432,psycopg 驅動,連線字串 postgresql+psycopg://cm_app:…@guidant-db:5432/guidant_ai,沒帶 sslmode,是明文(內網;與 C02、C06 同一個接受前提)。連線池每個行程 10 常駐+20 溢出 |
| 傳什麼 | 全部業務資料的讀寫:專案、問卷、範本、稽核紀錄、授權、系統設定;也包含紀錄類——操作日誌(api_logs)與系統日誌(system_logs)兩張表直接寫在這裡,worker 的工作單(background_jobs)也經這裡入列(即 C09) |
| 怎麼驗身分 | 兩層。第一層是 DB 帳密:三個行程全程只持 cm_app(無特權、受 RLS),叢集管理帳號 cmmgr(BYPASSRLS)只在建庫與升級時用,服務容器不持有。第二層是每次交易的身分變數:session_scope() 開交易時把登入身分寫進連線變數——app.user_id、app.allowed_tenant_paths(你能看的客戶路徑,如 /1/102/)、app.is_super_admin;資料庫的 RLS 規則讀這些變數決定每一列給不給看、給不給改 |
| 沒身分時怎麼辦 | 什麼都看不到(fail-closed):session_scope() 查不到身分時設 app.is_super_admin = 'f'、不設客戶路徑,規則的兩個條件都不成立,查詢回 0 列;同時記一行 WARNING 指出是哪個呼叫點沒帶身分。要繞牆只認兩個顯式訊號:system_context("<排程名>") 具名系統身分,或身分本身就在原廠最上層租戶路徑 |
| 牆怎麼判 | 每張有牆的表掛四條規則(查/增/改/刪),形狀固定:「app.is_super_admin = 't',或 app_tenant_allowed_for_session(tenant_id)」。後者做前綴比對——你的路徑是 /1/102/,就看得到 /1/102/ 與 /1/102/…/ 底下的(自己與子公司),看不到 /1/ 母公司與 /1/103/ 兄弟;路徑變數為空一律拒絕 |
| 牆蓋到哪 | 不是每張表都有牆(DEV 實查 2026-10-02):有客戶欄位的 49 張表 48 張已開牆(剩 config.log_forwarding_settings 一張,裁定屬平台層設定);另有 15 張沒客戶欄位、靠「看得到專案才看得到它」的跟隨規則。按 schema 看:compliance 42 張有 31 張、survey 14/14、config 13 張有 8 張、public 72 張有 17 張、oscal 52 張只有 5 張。沒牆的表分三類:① 合規文件那一整塊(oscal.*)——連客戶欄位都沒有,程式層是唯一防線;② 紀錄與系統表(api_logs/system_logs 各月分區、capabilities/ui_routes/system_menus)——天生不分客戶;③ 關聯表與子表(job_evidences、bulletin_org_units、*_trans)——靠掛它的主表擋 |
| 何時存在 | 必有。三個行程實測都持有連線 |
依據:core/app_factory.py 連線字串、jedi-common jedi_common/session/database/db.py 的 session_scope()、jedi_common/session/auth/system_context.py、scripts/init/00-cluster.sql 角色設計、scripts/init/02-schema.sql 的 app_tenant_allowed_for_session() 與 222 條 CREATE POLICY、docker/production/docker-compose.yml 的 guidant-db 段、DEV 庫 pg_class.relrowsecurity 逐 schema 統計、DFD Level 0。
全部——沒有一個功能不讀寫資料庫。但對這條線的威脅分析,功能依「牆對它有沒有效」分四群,每群出的問題形狀不同:
| 群 | 功能 | 牆的狀況 | 威脅的來源 |
|---|---|---|---|
| ① 有牆、靠牆兜底 | 專案、任務、問卷、設備、公告、授權、檢測工具與掃描紀錄、使用者與角色、檔案紀錄 | 表上有客戶欄位+四條規則 | 牆建錯:開關沒開、規則方向反、視圖繞過、新增規則寫成一律允許 |
| ② 沒牆、程式層是唯一防線 | 合規文件整塊(系統安全計畫、評估計畫、稽核發現、改善計畫及其子表)、範本內容 | oscal.* 連客戶欄位都沒有,歸屬只記在 compliance.module_frames 這張對照表 |
程式層守門漏一支就跨客戶,資料庫不會兜底 |
| ③ 無人登入的背景作業 | 半夜清理排程(過期工作單、孤兒綁定)、日誌分區維護、授權到期檢查、worker 撿單、Drive 同步 | 要掃全部客戶,必須顯式宣告系統身分才看得到東西 | 身分沒帶上:以前是靜默拿到全庫,現在是靜默看到 0 筆——判斷方向反過來就會刪錯東西 |
| ④ 紀錄類寫入 | 每個請求寫一筆操作日誌、程式錯誤寫系統日誌、寄信與整合設定的錯誤訊息 | 兩張紀錄表沒牆(天生不分客戶) | 寫進去的內容帶祕密(密碼、通行證、整組設定);寫失敗拖垮請求;保存期限沒生效 |
35 條歸納成七種手法。與 C02 不同,這條線上的攻擊者不只是「有帳號的人」——T4、T5 的攻擊者是「拿得到資料庫備份或唯讀帳號的人」(維運、外包、離職者、拿到回廠診斷包的人),他們不經過 api,直接讀資料庫。每種寫駭客實際的步驟與得手什麼;實例欄是該手法的全部條目,點了連回 STRIDE 頁。
| # | 風險 | 威脅 | 駭客怎麼打 | 得手什麼 | STRIDE | 實例 |
|---|---|---|---|---|---|---|
| T1 | 🟠 高 | 牆沒蓋到:表上有客戶欄位但隔離沒生效、或連欄位都沒有(6 條) | ① 任一家客戶的員工登入,打開專案清單、我的任務、改善計畫、檔案下載這類頁面;② 程式層那一支剛好沒過濾客戶(C02 的 T1/T2 任一條都算);③ 本該兜底的資料庫規則不在——開關沒開、規則只寫一半、視圖以建立者(最高權限)身分查、或表上根本沒有「這是哪家的」欄位;④ 別家客戶的資料直接出現在畫面上。 | 別家客戶的專案、待辦、任務執行紀錄、稽核缺失、改善計畫、上傳的證據檔;SaaS 版上還有系統安全計畫與稽核發現。 | I 資料外洩 | M20-1 13 張標有客戶歸屬的資料表,資料庫那道隔離牆沒有真正生效 🟠 M20-2 4 個資料查詢畫面整個繞過隔離機制 🟠 M02-6 存放檔案紀錄的資料表沒有客戶歸屬欄位,資料庫擋不住跨客戶 🟠 M20-5 一整類業務資料表連客戶歸屬欄位都沒有,牆沒有東西可依據 🟡 M17-5 公告與部門的關聯表沒有設定客戶資料隔離 ⚪ M11-41 兩張解析工作單表的資料庫新增規則寫成一律允許 ⚪ |
| T2 | 🟠 高 | 牆蓋反了:子公司看得到、改得動母公司的東西(4 條) | ① 被停權客戶、或任一子公司的成員登入(只要手上有一個子單位帳號);② 打開檢測工具設定、授權狀態、解析工作單這類會讀「母公司那層」的頁面;③ 規則把子公司路徑 /1/102/152/ 拆成 [1, 102, 152] 逐一比對(或用純文字包含比對),於是母公司 102、原廠 1 的列全被算成「你碰得到的」,而且讀得到=改得到=刪得到;④ 刪掉停權紀錄當場復權;改母公司的掃描紀錄;疊上「測試連線」把母公司的維運帳密送到自己主機。 |
被停權客戶自行復權並改寫授權內容、刪掉異動時間線;母公司的檢測設定、維運帳密與掃描紀錄;母公司稽核計畫的解析內容。 | E 權限提升、I 資料外洩 | M18-8 子單位帳號改得動、刪得掉母公司的授權與停權紀錄 🟠 M03-7 資料庫的客戶隔離規則方向寫反 🟡 M12-7 兩張「解析工作單」資料表照抄了五月就判定壞掉的隔離寫法 ⚪ M11-40 人員對帳收了公司編號卻沒用,會拿所有客戶的人員來比對 ⚪ |
| T3 | 🟠 高 | 身分沒帶上:沒身分時牆的方向整個反過來(6 條) | 兩個方向——(a) 以前:讓請求在「沒有登入身分」的狀態下進到業務邏輯(登入入口本身、雲端硬碟通知入口、漏掛認證的路由、沒還原身分的背景執行緒),資料庫把「查不到身分」解讀成「最高權限」,那段程式碰得到的每一筆都從一家放大成全部客戶;(b) 現在:沒身分改成什麼都看不到,於是半夜清理排程若忘了宣告系統身分,會「看不到任何任務」→「每一筆綁定都指向不存在的任務」→ 把所有客戶的任務與證據對應一次清空;或安靜掃到 0 筆、孤兒資料永遠堆積。另外「每次連線都設、但沒有規則讀」的假開關,與「用字串拼接寫身分變數」的寫法,是下一次有人改這段時的陷阱。 | (a) 那段程式碰得到的全部客戶資料;(b) 沒有人得手——是一夜之間所有客戶的任務與證據對應被清掉,或清理機制在不知不覺中停擺。 | E 權限提升、T 竄改資料、D 讓服務停擺 | M04-2 沒有登入身分時,客戶資料隔離整個關掉 🟠 M20-4 兩支半夜清理程式靠「找不到身分就給最高權限」才跑得動 🟡 M06-25 孤兒清理排程若沒帶身分,會把正常資料整批刪掉 ⚪ M04-14 每次連線都設定、但全系統沒人讀的開關 ⚪ M04-13 資料庫隔離設定值用字串拼進查詢語句 ⚪ M21-3 沒有部門的一般使用者送意見回饋必定失敗、畫面不給理由 🟡 |
| T4 | 🟠 高 | 祕密以明文落在資料庫裡:密碼、通行證、主機帳密(4 條) | ① 不用打——使用者正常登入、管理員設定 LDAP 或 SSH 整合、按「開始掃描」,程式就把密碼原文、登入通行證、解密後的客戶主機帳密寫進操作日誌表或派工單表;② 拿得到資料庫備份、唯讀帳號、或回廠診斷包的人打開搜尋 password/Authorization;③ 直接拿到密碼與可冒用身分的通行證,不必破解任何東西。遮蔽機制遇到密碼裡的雙引號只遮一半——密碼越強越容易中招;寄信失敗時整組郵件設定連密碼一起進紀錄。 |
使用者登入密碼原文、可直接冒用的通行證、客戶正式機房的主機密碼與掃描器管理員權杖、公司郵件帳密、系統整合密碼的後半截。 | I 資料外洩 | M04-1 登入密碼與通行證原文寫進紀錄檔與資料庫 🟠 M03-9 開始掃描時把解密後的主機帳密多存一份明文 🟡 M04-3 密碼裡有雙引號就只遮一半 🟡 M16-2 寄信失敗把整組郵件設定含密碼寫進紀錄 🟡 |
| T5 | 🟡 中 | 紀錄表本身:寫不進去拖垮請求、寫太長就不留痕、該刪的不刪(5 條) | 三種路——(a) 已登入的人要刪資料或改設定,在網址後面加一段無意義參數撐過 500 字,操作照做、寫操作日誌時因為太長整筆失敗,事後查不到這次操作;(b) 資料庫那一刻寫不進去(連線斷、表被鎖),「記一筆日誌」的錯誤往上冒,把使用者已經做完的請求一起弄成失敗;(c) 無需攻擊者——按月分區從沒被建,90/180 天保存期限從沒生效,含密碼原文的舊日誌一直留著;兩張紀錄表天生不分客戶,完整錯誤堆疊帶出內部路徑與資料片段。 | (a) 刪資料、改設定而不留紀錄;(b) 沒有人得手,是正常請求被連累;(c) 早該刪掉的舊日誌、全部客戶的運作紀錄、系統內部結構。 | R 事後無法追查、D 讓服務停擺、I 資料外洩 | M09-6 把網址加長,這次操作就不會出現在操作日誌 🟡 總表#103 寫日誌進資料庫失敗會連帶弄壞使用者的請求 🟡 M09-5 日誌表按月分區失效,保存期限沒生效 🟡 M04-4 兩張運作紀錄表沒有客戶隔離 ⚪ M04-5 完整的程式出錯內容被寫進那張沒有隔離的紀錄表 ⚪ |
| T6 | 🟡 中 | 一個請求讓資料庫做太多:整張表撈出來、交易開著一列列讀(4 條) | ① 任何登入帳號打開任一清單頁,把「一頁幾筆」改成超大數字,資料庫把整張表一次撈出來組好回傳;② 或拿一份幾 KB 的稽核表在第 20 萬列填一格去匯入,系統從頭一列列讀到那裡,整段時間資料庫交易一直開著;③ 或有法規框架修改權的人存一筆空白流程範本,之後每個人打開清單頁都讀到它、解析出錯、整頁 500,不會自己恢復;④ 連送幾次,處理程序與資料庫連線池全被佔住。 | 讓整個產品對所有客戶停止回應,或讓所有人的某個清單頁持續打不開。 | D 讓服務停擺 | M04-7 一次要求回傳幾筆資料,沒有上限 🟡 M12-3 稽核紀錄 Excel 在很後面填一格,系統就從頭一列列讀到那裡 🟡 M06-8 一筆空白內容的流程範本讓所有人的清單頁一直出錯 🟡 M19-3 設備清單一頁要顯示幾筆,沒有上限 ⚪ |
| T7 | 🟠 高 | 沒牆的表上,程式層漏檢查就是整份改寫(6 條) | ① 任一登入帳號(連唯讀稽核員都算)拿到一份範本或輪次編號——別家公司的或原廠公版的;② 上傳一份自己做的 Excel/Word,去向選「覆蓋既有範本」、目標填那個編號,或換網址讀別人專案的稽核判定;③ 範本內容與稽核判定落在 oscal.* 沒牆的表,程式層只查「範本存不存在」、只驗「有沒有登入」,資料庫不會兜底;④ 整份清空重寫,之後每個用這份範本開專案的客戶都複製到被改過的內容。另外兩條是同一塊的時序與一致性問題:平台管理員刪框架不問有沒有客戶在用(各客戶資源庫斷鏈);兩家同時上傳同一張授權檔,查重與寫入不在同一個交易,兩邊都查到「沒人用」。 |
整份改寫別家公司或原廠公版的合規範本並汙染之後所有專案;別人專案的稽核判定與改善計畫;一次誤刪讓所有客戶資源庫斷鏈;一張授權兩家同時用。 | T 竄改資料、I 資料外洩 | M11-2 用 Excel 蓋掉既有範本,從頭到尾不問你是誰 🟠 M11-3 用 Word 蓋掉既有範本,只查範本存不存在 🟠 M10-4 不是這個專案的人,換網址就看到別人專案的稽核判定與改善計畫 🟠 M12-1 同公司不是這個專案的人,換個網址就看得到稽核判定與改善計畫 🟠 M11-27 刪框架或版本時不檢查還有沒有資源庫在用 🟡 M18-9 同一張授權檔可能被兩家客戶同時用上 ⚪ |
七種手法的共同點:T1~T3、T7 共 22 條都是同一件事——這道牆是「最後一道防線」,但它的覆蓋範圍、方向、和身分有沒有帶上,都是人手一張表、一支排程地維護,沒有機制保證。牆蓋到哪(T1、T7)、蓋對了沒(T2)、有沒有人在門口(T3)三個問題各自獨立,任何一個出錯,程式層的漏洞就從「一家公司」放大成「全部客戶」。T4~T6 是另一件事——資料庫被當成「什麼都能放、放多少都行」的地方:祕密原文放進去、紀錄無限留、一次撈整張表;這些不是隔離問題,是「寫進去之前沒人把關」。
對應七種威脅,防線分九條。D1~D7 直接對應掃描抓到的手法;標 ◇ 的是依這條線的特性補的標準防線,掃描範圍沒涵蓋。「目前」欄寫程式與資料庫裡實際有的機制;⚠️ 表示只靠慣例或只守到局部。
| # | 防線 | 擋哪種威脅 | 目前做到哪 |
|---|---|---|---|
| D1 | 每張業務表都要有牆,牆的形狀只有一種。表上有客戶欄位+四條標準規則(查/增/改/刪,各自「超管或 app_tenant_allowed_for_session(tenant_id)」);沒有客戶欄位的子表用「看得到父表才看得到子表」的跟隨規則;視圖一律以查詢者身分執行 |
T1、T7 | ✅ 標準規則函式 app_tenant_allowed_for_session() 已是全庫唯一判準(路徑為空一律拒絕);專案底下 13 張無客戶欄位的表用跟隨規則並有守衛測試 test_project_scoped_tables_rls.py;upload_files 補欄位+開牆並有 test_upload_files_rls.py;兩支視圖寫進定義正本 WITH (security_invoker = true)。⚠️ oscal.* 52 張只有 5 張有牆(DEV 實查),合規文件整塊靠程式層當唯一防線——跟隨規則的方向已定(53 張、4 個接點)但未施工,決策者排在 SaaS 前;沒有一支守衛測試掃「哪些有客戶欄位的表沒開牆」或「開了牆的表四條規則齊不齊」(DEV 實查有 11 張開了牆但只掛一條 ALL 規則:弱點檢測五張、遠端代理 agent_tasks、background_jobs、ai_call_log 等——ALL 一條管四件事不算錯,但形狀與標準四條不同,守衛寫起來要先定義哪種算合格),新加一張表漏開不會被抓到 |
| D2 | 牆的方向只准前綴比對,禁止拆陣列或純文字包含。子公司只看自己與底下,看不到母公司與兄弟;需要「子讀母」的例外(授權)另加只管讀的祖先規則,改刪仍只吃前綴 | T2 | ✅ 九張表統一改前綴比對;授權三張另加 app_tenant_is_ancestor_of_session() 讀取規則;守衛測試 test_rls_no_path_array_policy.py 讀全庫規則、斷言不再有拆陣列寫法;test_rls_license_tables_ancestor_read.py 守子讀母=1、子改母=0。這一條是全庫掃描、有機制保證 |
| D3 | 沒身分一律看不到,繞牆必須具名宣告。session_scope() 查不到身分時 fail-closed 並記警告;排程與背景作業只能用 system_context("<名字>") 繞牆;刪除類背景作業刪之前先確認「我看得到東西」 |
T3 | ✅ session_scope() 無身分時設 is_super_admin = 'f'、不設路徑、每個呼叫點警告一次;超管只認 is_system_context 與原廠路徑兩個顯式訊號(程式碼註明「不可再加缺值即超管的條件」);守衛測試 test_scheduler_system_context.py 掃 core/scheduler.py 每支 tick 都要有具名身分;孤兒清理刪前先數 count_visible_jobs(),0 筆就本輪不刪。⚠️ 守衛只掃主專案 scheduler,套件內自己起的背景執行緒(Drive 同步、webhook 續約)靠人記得包;身分變數仍用 f-string 寫入(M04-13 裁定不修,值目前全由系統產生) |
| D4 | 祕密在寫出去的那一刻就遮掉,解密後的值只留在記憶體。寫日誌的三個出口(請求標頭、請求內容、回應內容)全接同一支遮蔽;遮蔽要能處理跳脫字元;整合設定的錯誤訊息只記位址與錯誤類別;解密後的帳密不寫回任何表 | T4 | ✅ mark_password 遮蔽範圍擴到 secret/token/credential/authorization/api_key,三個出口都接上,請求標頭改白名單;正則改成允許反斜線跳脫;派工單寫明文那一行已刪;寄信失敗只記伺服器與錯誤類別。⚠️ 遮蔽靠「欄位名清單」,新欄位沒加進名單就漏;診斷包那端另有一套 diag_masking.py,兩套名單要同步 |
| D5 | 紀錄是旁路,失敗不拖垮請求、超長先截斷、過期要真的刪。寫日誌包錯誤處理走標準出口;欄位寫入前截到上限、截斷後仍失敗補最小替代紀錄;分區維護有排程、以擁有者身分執行 | T5 | ✅ DBLogHandler.emit() 整支包 try/except 走 handleError;攔截器 _API_LOG_COLUMN_LIMITS 截斷+ _add_fallback_api_log 替代紀錄;maintain_log_partitions() 改 SECURITY DEFINER、排程每日 03:30 UTC 呼叫。✅ 兩張紀錄表不分客戶屬裁定(落地版一客戶一庫) |
| D6 | 資料庫做的事有上限:每頁筆數在共用的分頁格式設上限;讀檔解析用串流、連續空白即視為檔尾、列數上限;寫入端拒絕空內容、讀取端一筆壞資料不拖垮整份查詢 | T6 | ✅ PagerSchema.page_size 上限 1000(MAX_PAGE_SIZE),全站十二處一起生效;稽核表解析改串流+200 列空白即停+5,000 列上限;流程範本寫入拒絕空白、讀取端防呆。⚠️ 沒有資料庫層的 statement_timeout(基線 SET statement_timeout = 0、連線字串沒設),一個卡住的查詢可以無限期佔連線;上限散在各模組,沒有「所有解析端點必過」的一處守門 |
| D7 | 沒牆的表上,程式層守門要能被看見。資源歸屬只記在對照表的那幾塊(oscal.*),所有寫入與讀取端點走統一守門,守門的使用者參數改必填讓漏傳直接報錯;跨客戶的唯一性用資料庫索引兜底 |
T7 | ✅ 範本覆蓋走 resource_library_overwrite_guard.py(能力點+歸屬)、上傳與確認兩端都跑;稽核輪次七支讀取守門參數改必填;刪框架前用獨立超管連線數引用數、查不了就拋錯不放行;授權檔部分唯一索引兜底。⚠️ 這一條與 C02 D1/D9 是同一件事——oscal.* 沒牆,程式層漏一支就是一個洞,而「哪些端點掛了守門」沒有程式產的清單 |
| D8 ◇ | DB 帳號最小權限、連線不對外、憑證不進 image。服務容器只持無特權帳號;管理帳號只在建庫與升級時用;資料庫埠不映射到主機;密碼由安裝程式產生、不寫死 | 這條線的前提——帳密若外流,牆對拿到帳密的人不存在 | ✅ 三個行程只持 cm_app(受 RLS、無 CREATEROLE/BYPASSRLS);guidant-db 無 ports: 映射;密碼 installer 產生、.env.example 留空。⚠️ bundle 內建 PG 時 cmmgr 實際是 superuser(compose 註明「可接受」,降規設計服務的是客戶自備 DB);連線不加密(psycopg 沒帶 sslmode,DB 容器 ssl=off),docker 內網被認為可信——與 C02、C06 同一個前提,SaaS 或客戶自備 DB 時要重新評估 |
| D9 ◇ | 備份與匯出受同等保護。T4 的攻擊者拿的是「備份或唯讀帳號」——備份檔要加密或至少限權,放在資料目錄下不隨意複製;回廠診斷包經同一套遮蔽 | T4 的另一條路 | ⚠️ 升級前 `pg_dump |
最該補的一步:D1 的「牆覆蓋率用程式掃、不靠人記」。35 條裡 T1~T3 共 16 條,病因全是「某張表/某支排程漏了」——D2、D3 已經各有一支守衛測試掃全庫,唯獨「有客戶欄位的表有沒有開牆」「有牆的表是不是四條規則都在」沒有守衛,而這正是 M20-1 那 13 張表的病。一支讀 pg_class+pg_policies 的測試就能把這條從「規範」變「機制」。
tenant_id 的表 48 張已開牆,但「開了牆的表是否四條規則齊全」(11 張只掛一條 ALL)「新加的表有沒有開」沒有測試守著;oscal.* 47 張沒牆的表,哪些是「裁定靠程式層」、哪些是「還沒輪到」,只有 M20-5 的盤點清單,沒有機器可讀的標記。test_scheduler_system_context.py 只掃主專案 core/scheduler.py;jedi-* 套件自己起的背景作業(Drive 同步、webhook 續約、授權時鐘)有沒有都包了具名身分,沒盤過。statement_timeout,一個卡住的查詢可以無限期佔住連線池的一格;T6 的 4 條修的是「別讓它卡」,沒有「卡了多久就斷」的兜底。sslmode=require。依據:STRIDE 六頁信任邊界連線標記(CM-2403/2404 驗收後版本)、jedi-common session/database/db.py/elevated_session.py/auth/system_context.py、scripts/init/00-cluster.sql/02-schema.sql、core/app_factory.py、core/scheduler.py、docker/production/docker-compose.yml、scripts/installer/install.sh 升級備份段、DEV 庫 pg_class/pg_policies 實查(2026-10-02)、test/test_rls_*.py/test_scheduler_system_context.py/test_project_scoped_tables_rls.py/test_upload_files_rls.py。分組是依標題與「駭客怎麼打」歸納的,同一條若兩種手法都沾,歸主要那種(M10-4/M12-1 在 C02 歸「換編號讀別人專案」,這裡歸 T7 是因為它們落在沒牆的表上、資料庫不會兜底;M04-7/M19-3/M12-3 在 C02 歸 T2,這裡看的是資料庫端的負載)。