本卡屬 FR-114 資安修正(母卡 CM-2019),b4 驗證 V2(CM-2265)第二輪用子租戶帳號挖到、首腦在 190 pg_policies 親自核過的新洞,收集卡 CM-2231 #38。三個套件的 migration+一支主線 migration+一支守衛測試。方向是子看父,落地版通常單一頂層租戶所以打不到,SaaS 多租戶才打得到;依「落地版優先、SaaS 預留正確形狀」修法要對,不擋 b5 出包但進 b5。

問題是什麼(白話)

租戶有樹狀關係,每個租戶有一條路徑,例如母公司 Billows 是 /1/3/、子公司是 /1/3/4/。資料庫的資料列隔離(RLS)大多數表用的是「前綴比對」:你的路徑是 /1/3/4/,只看得到路徑以它開頭的租戶,母公司 /1/3/ 不是,所以看不到。但有九張表用的是另一種寫法:把路徑 /1/3/4/ 拆成數字陣列 [1,3,4],只要資料列的 tenant_id 在陣列裡就可見——於是子公司的人查這九張表時,母公司(3)與平台(1)的資料全部算可見。

190 實證(runner 用 blsfrank、子租戶 4):打 /license/status 回的是母公司 Billows 的授權(created_user: blsadmin, customer_code: Billows);對母租戶三個檢測設定 uid 打測試連線都通過「設定存在」檢查,假 uid 才 404。反向(母看子)不會,因 /1/3/ 拆成 [1,3] 不含 4。

首腦已核(190 pg_policies,qual ILIKE '%string_to_array%',正好 9 張):

compliance.agent_tasks                       agent_tasks_tenant_isolation
compliance.detection_execution_groups        detection_execution_groups_tenant_isolation
compliance.detection_executions              detection_executions_tenant_isolation
config.job_execution_detection_tool_agents   jedta_tenant_isolation
config.job_execution_detection_tools         jedt_tenant_isolation
config.tenant_detection_tool_configs         tdtc_tenant_isolation
config.tenant_license_events                 tenant_license_events_tenant_isolation
config.tenant_license_suspensions            tenant_license_suspensions_tenant_isolation
config.tenant_licenses                       tenant_licenses_tenant_isolation

工作區

在哪裡

壞寫法(九處,全部同一句):
  ... OR (tenant_id = ANY ((string_to_array(TRIM(BOTH '/' FROM current_setting('app.allowed_tenant_paths', true)), '/'))::bigint[]))

來源(套件 migration,各自 DO 區塊包 CREATE POLICY、duplicate_object 即略過——所以只改這些檔對既有庫無效,見怎麼修):
  /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/.claude/worktrees/jedi-wt-fix-security/jedi-detection/jedi_detection/migrations/002-detection-rls-grants.sql:34-40, 86-97   (5 張:detection_execution_groups/detection_executions/jedta/jedt/tdtc)
  /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/.claude/worktrees/jedi-wt-fix-security/jedi-license-runtime/jedi_license_runtime/migrations/002-license-rls.sql、003-tenant-license-suspensions.sql   (3 張 license)
  /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/.claude/worktrees/jedi-wt-fix-security/jedi-remote-agent/jedi_remote_agent/migrations/002-remote-agent-rls.sql   (agent_tasks)
  主線舊檔也有一份(歷史,不動):scripts/sql/2026-07-26-fr056-3-agent-tasks.sql、2026-08-08-fr062-2-tenant-licenses.sql、2026-08-09-fr062-tenant-license-events.sql
  BE 攤平副本:scripts/sql/packages/jedi_detection/002-*.sql、jedi_license_runtime/002-*.sql、jedi_remote_agent/002-*.sql(build 期由 flatten_pkg_migrations.sh 從套件產,改套件後重跑攤平)

對的寫法(projects/upload_files 等在用):
  ... OR app_tenant_allowed_for_session(tenant_id)
  函式定義 scripts/init/02-schema.sql:623-640:SECURITY DEFINER,對 tenants.path 做 LIKE 前綴,空/未設 allowed_tenant_paths 一律 false

守衛測試風格:test/test_project_scoped_tables_rls.py(讀 pg_policies 的 polqual 斷言)

怎麼修

手測(DEV,本機 localhost:5432 guidant_ai_dev)