共用 AI Agent 記憶如何防止跨成員外洩:GBrain 的 default source 與逐頁授權
一句話摘要
同一個資料來源不等於同一種讀者;來源授權只劃出粗邊界,成員專屬頁還要在寫入時帶入讀者限制,並在每條讀取路徑都驗證。
從一份共用會議筆記開始
想像 Alice 和 Bob 都連到團隊的 AI 記憶庫。Alice 的 Agent 把私人的會議筆記寫進共用的 default 來源;如果系統只看「這個憑證能不能讀 default」,Bob 或沒有成員綁定的整合程式就可能看見 Alice 的頁面。問題不是模型答錯,而是資料在模型收到之前就已經越過讀者邊界。[10]
這裡要分清三件事:brain 是資料庫;source 是資料庫裡的內容庫;page reader 是單一頁面的允許讀者。[5] GBrain 文件把 source grant 定位為粗粒度邊界,頁面上的 readable_by 只能再縮小讀者範圍,不能擴大 source grant。[5] 身分驗證回答「呼叫者是誰」,授權回答「這個身分能否對這個物件做這個動作」;已登入不代表可讀所有資料。[9]
為什麼 default 不是私人空間
GBrain 文件指出,沒有 source grant 的遠端憑證會讀取 default 和已 federate 的來源;初始 default 來源本身會參與未指定來源的搜尋。[5] 因此,把 Alice 的憑證綁到 member,卻仍讓它寫進每個未綁定憑證都可讀的 default,只驗了來源,沒有完成頁面層級的隔離。[5]
GBrain v0.60.621.0 的 release notes 說明,修正前,綁定成員但沒有來源限制的憑證在 default 建立頁面時,其他成員和未綁定憑證可經 get_page、search、query、recall 與 list_pages 讀到它。[3] 這是專案對自身舊行為與修正內容的說明,不是本文在正式服務上重現的漏洞。[3]
新頁面的讀者如何被限制
在 v0.60.621.0,成員綁定的遠端 token 或 OAuth client 寫入 default 且建立新頁面時,系統會替頁面記上該成員為讀者。[3] 未提供新讀者的改寫會沿用原讀者;頁面要分享給另一位成員或群組,必須明確列出讀者。[3] 若改寫的是既有共用頁,省略讀者欄位仍不會自動把它轉成私人頁。[3]
| 狀況 | Alice | Bob | 未綁定憑證 |
|---|---|---|---|
| Alice 新建成員專屬頁 | 可讀 | 不可讀 | 不可讀 |
| Alice 改寫頁面但未指定新讀者 | 保留原權限 | 仍不可讀 | 仍不可讀 |
| Alice 明確把 Bob 加入讀者 | 可讀 | 可讀 | 不可讀 |
| Alice 改寫原有共用頁且未改讀者 | 依原設定 | 依原設定 | 依原設定 |
表格中的預設可見性與改寫行為來自 GBrain 的 release notes。[3] 這種規則把「建立時預設只給本人」和「明確分享」分開,避免一次一般更新意外清掉 ACL(存取控制清單,記錄哪些主體可讀取資源)。[3] OWASP 建議未明確允許時預設拒絕,且每個請求都要檢查權限。[9]
寫入路徑也會改變安全結果
上述修正針對能保留頁面讀者欄位的 put_page。[3] 同一版本的 release notes 指出,POST /ingest 無法把新頁限制給成員,所以這類成員憑證呼叫時會收到 permission_denied,而且不會排入 ingestion 工作;改用 put_page,或讓管理者把憑證配置到專屬來源。[3]
衍生頁面是另一個容易漏測的地方。[3] Release notes 表示,從成員專屬頁抽出的 atoms、chronicle events、derived_from 頁面及綜合概念頁,也會套用讀者規則;這些衍生內容對所有遠端呼叫者都不可讀,包含原寫入者。[3] 這能避免改了原頁 ACL,卻讓下游副本繼續外洩;代價是工作流要另外設計由誰整理及重新分享衍生頁。[3]
可驗收的授權測試矩陣
專案的 regression test 以 Alice、Bob 和未綁定讀者檢查新頁,並跨 get_page、search、query、recall、list_pages 觀察是否洩漏;它也涵蓋保留讀者的改寫、明確分享、既有共用頁、POST /ingest 拒絕,以及 repo-less 和 repo-backed 的持久化路徑。[4] Release notes 將測試範圍列為 PGLite、Postgres 與 repo brain,但這是上游提供的測試覆蓋說明。[3]
本文本次只靜態檢視 release notes、文件與測試原始碼,沒有在本機安裝 GBrain 或執行其測試套件。實際驗收時,應以合成頁面和三種憑證建立測試資料,逐項確認上表結果、直接查頁的拒絕、搜尋結果不含 canary 字串、改寫後 ACL 不消失,以及拒絕的 ingest 沒有新增佇列項目。[4] OWASP 也把物件層級授權檢查列為每個接收物件 ID 並執行動作的 API 端點責任。[10]
升級前後的操作檢查
- 先分類資料:整個內容庫只屬於一個人或群組時,以 source grant 建立主要邊界;同一來源內混有個人與團隊頁時,再用
readable_by表達例外讀者。[5] - 分享頁要在建立時明確設定成員或群組讀者;不要假設來源 federate、能搜尋到、或 agent 知道頁面 slug,就代表所有人都應該看到。[5]
- 升級後檢視既有頁面的實際讀者。版本升級不會自動重寫既有頁的讀者;GBrain 文件建議用
gbrain access who-can-read <slug>查單頁,再由 brain host 修正本來應為私有的舊頁。[3] - 不要用
POST /ingest的成功與否推定put_page的 ACL 行為相同;兩條路徑必須分開測。[3][4]
限制與風險
這項修正描述的是特定成員綁定遠端憑證寫入 default 的頁面可見性,不是「整個 GBrain 部署已安全」的證明。[3] GBrain README 明確提醒,本機檔案與共用資料庫憑證屬於不同信任邊界,source grant 不會隔離這些呼叫者;專案測試也只覆蓋特定存取路徑,不是所有方式都不會洩漏的保證。[2]
若團隊允許直接讀取主機檔案或使用共用資料庫帳號,應另外在作業系統、資料庫與憑證層定義邊界。[2] 即使遠端 API 隱藏頁面,仍需驗證修改、刪除、搜尋索引、衍生內容與匯出路徑;任何接收頁面 ID 或 slug 的端點,都必須使用呼叫者身分重新判斷該物件的權限。[9][10]
我的判斷
最重要的工程選擇不是替每一頁塞一個權限欄位,而是先問「誰是這份資料的讀者?」如果讀者群組彼此長期固定,拆成不同 source 通常比較容易稽核;只有在內容本來就共用、但少數頁面需要縮小範圍時,才讓 page-level readers 管理例外。[5] GBrain 這次把成員身分帶到新頁寫入時,比事後靠 prompt 要求 Agent「別讀別人的筆記」可靠,因為限制發生在資料讀取層,而不是模型自律層。[3][5]
它仍不是全面隔離方案:管理者、本機檔案與共用 DB 憑證可以跨越遠端讀取政策。[2] 正式採用前,應把遠端成員、未綁定憑證、管理者與主機層存取各自列成主體,並為每條讀取與寫入路徑維持可重跑的負向測試。[4][9]