Postgres、PostGraphile、RLS 與 SQL
總覽
此堆疊的資料層機制。實作縱深防禦(RLS 作為最終防線)與唯一合法資料路徑。特定於此堆疊——不適用於沒有 PostGraphile/Postgres 的專案。
資料路徑
瀏覽器 → 應用伺服器 → PostGraphile → PostgreSQL。 唯一合法的路徑。
- 瀏覽器從不直接與 GraphQL/資料庫通訊;應用程式從不直接查詢資料庫以取得應用程式資料。
- 應用程式僅以標頭形式傳遞工作階段上下文(使用者 ID、組織提示)——應用程式中無授權邏輯;上下文在下游驗證,而非信任。
- 單一路徑 = 單一強制執行驗證上下文、驗證與 RLS 的位置。側通道是繞過強制執行的漏洞。
安全規則
- RLS 是最終的強制執行層。 應用層的檢查是 UX;資料庫才是防線。即使上層發生錯誤,也無法洩漏另一個租戶的資料列。
- 角色建模真實的工作階段狀態——未登入、已登入但無組織、已登入且有組織、僅連線角色、僅遷移角色。角色具有實際意義。
- 特權連線角色永不執行應用程式查詢。 應用查詢在受限且受 RLS 約束的角色下執行。
SECURITY DEFINER函數在其定義中內嵌設定search_path——每次皆然,無一例外。- 缺席值為
NULL,絕非空字串。 禁止使用空字串作為哨兵;當值不存在時省略該值。 - GraphQL 是 DoS 攻擊面——加以限制。 強制執行查詢深度限制與成本/複雜度分析;拒絕超額查詢。限制分頁大小。為應用角色設定資料庫的
statement_timeout。除非刻意需要,否則在生產環境中停用自省查詢。
SQL 風格
- 每個物件一個檔案——每個資料表、函數、策略、權限授予集各自獨立一個檔案。
- 按
schema/object_type/name組織——目錄結構反映資料庫的組織方式。 - 包含順序即為依賴順序; 包含清單即為目錄。
- 內嵌文件。
COMMENT ON與物件放在同一個檔案中——說明是什麼與為什麼。 - 使用描述性別名(資料表名稱的單數形式,而非單一字元);遵循 Postgres 慣用法(
current_*取得器)。 - 型別與面向使用者的角色是資料,而非硬編碼的約束——使用具有鍵/標籤/描述的查詢表,以外鍵驗證,無需 schema 變更即可擴充。
快速參考
| 規則 | 檢查事項 |
|---|---|
新的 SECURITY DEFINER 函數 | search_path 是否已內嵌設定? |
| 包含租戶資料的新資料表 | RLS 策略與拒絕測試(參見 graphql-contract-testing)? |
| 缺席的值 | 是 NULL,而非 ''? |
| 新的 GraphQL 表面 | 是否設有深度/成本限制與分頁上限? |
| 新的物件 | 是否在獨立檔案、依賴順序包含、內嵌 COMMENT ON? |
完整原理:參閱守則第 XIII、XIV、XVIII 條,收錄於 engineering-constitution/references/engineering-constitution.md。遷移:zero-downtime-migrations。合約測試:graphql-contract-testing。