致知录
第 XXXIII 卷 · 第 13 篇 · gbrain · 1970.01.01

访问控制:把 brain 借出去,又不失控

gbrain 工具拆解 · 1970.01.01 · 5 分钟阅读 · 1,158 字
目录 · 8
远程调用者默认只读、来源隔离、写操作有 slug 围栏——fail-closed 宁可拒绝不错放

你不会把日记本随便塞给别人翻。当你把 brain 通过 MCP 接口协议(让 agent 连上 brain 的标准协议)接给一个外部 agent、或一个同事的 OAuth 客户端时,问题就来了:它会不会看到我不该让它看的 source?会不会借着「写入」往我 brain 里塞东西?这一课讲 gbrain 怎么在你「借出」brain 时守住边界。

一、信任边界:远程调用者默认不被信任

gbrain 每个操作都带一个上下文标记 ctx.remote——本地 CLI 是 false(你自己的、可信),远程 MCP/OAuth 调用是 true(不可信)1。所有信任边界检查都基于这个标记:任何不是严格 false 的,一律按远程处理。这是 fail-closed(失败即拒绝)的设计——宁可把不确定的当成外人,也不放进来1

对使用者最直观的意义:你本地用 CLI 想干嘛干嘛;一旦通过远程接口(agent、同事的 token)访问,系统自动收紧权限,你不用手动设。

二、来源隔离:一个 token 只看得到被授予的 source

多 source 的 brain(比如你分了 investing/work/tech),每个 OAuth 客户端或远程调用都被限定在授予的来源范围2。代码用 sourceScopeOpts(ctx) 决定「这个调用者能读哪些 source」:联邦数组(federated)优先于单个 sourceId,再优于无限制;读取类操作(搜索、查询、取页、找专家)全都过这道闸。一个只被授予 investing 的 token,搜不到 work 里的内容——即使它试图绕,链接读取也会把「跨来源」的那一端剥掉2

三、写围栏:slug 前缀把「能写哪」钉死

读有来源隔离,写有更硬的slug 围栏(slug fence)。两类写者被围栏限制3

  • 子 agent(subagent):当它被授权时,它要写的页面路径必须匹配允许列表(allow-list);否则继承老的 wiki/agents/<id>/ 命名空间检查。不匹配就 permission_denied
  • OAuth 客户端:如果它的 token 绑定了一组 slug 前缀(boundSlugPrefixes),那它每一次写操作(建页、删页、加标签、加链接、加时间线等)的目标 slug 必须落在这组前缀内,否则直接拒绝。空数组绑定 = 全拒(fail-closed)3

而且围栏是边界感知的:emp-alice 不会因此被放行进 emp-alice-2/...——它匹配的是完整路径段,不是前缀子串。这种精确性防止了「前缀长得像就混进去」的越权。

四、一个建筑师记下的

gbrain 的访问控制不是事后补的开关,而是焊死在 OperationContext 里的 fail-closed 默认值——remote 不清零、scope 不继承就当无授权、空绑定就全拒、所有边界检查失败即 permission_denied。这种「默认拒绝、显式授予」的姿势,是任何要把知识库暴露给外部的系统最该抄的。你造工具时别用「默认放行、出事再限」,那样迟早漏。

五、这一课带走的

  • 远程调用者(agent/OAuth)默认不被信任,权限自动收紧。
  • 每个 token 被限定在授予的来源(source)范围内,看不到别的 source。
  • 写操作有 slug 围栏:子 agent 和 OAuth 客户端只能写被允许的路径前缀,越界即拒。
  • 全程 fail-closed(失败即拒绝),默认不放行——借出 brain 也不会失控。

练习题

1

gbrain 怎么区分「本地自己用「和「远程调用者「?

2

一个只被授予 investing 来源的 token,能搜到 work 来源的内容吗?

3

OAuth 客户端的 slug 围栏(boundSlugPrefixes)空数组意味着?

4

gbrain 访问控制的整体姿势是?


参考出处

Footnotes

  1. 代码 src/core/operations.tsOperationContext.remote 为 REQUIRED 字段标记不可信调用者;信任边界检查 ctx.remote === false 为可信、ctx.remote !== false 当作远程;fail-closed 语义:非严格 false 即按远程处理)。 2

  2. 代码 src/core/operations.tssourceScopeOpts(ctx) 编码来源读取优先级阶梯:联邦数组 allowedSources > 标量 sourceId/auth.sourceId > 无;所有读侧 op(search/query/list_pages/get_page/find_experts/by-slug 读取)过此闸;linkReadScopeOpts 对跨来源链接端做剥离;think 经 thinkSourceScopeOpts 继承调用者来源授权)。 2

  3. 代码 src/core/operations.tsenforceSubagentSlugFence 要求 viaSubagent 时 slug 匹配 allow-list,否则 legacy wiki/agents/<id>/ 命名空间检查;enforceClientSlugFence 对绑定 boundSlugPrefixes 的 OAuth 客户端,所有 slug 变更写操作(put_page/delete_page/add_tag/add_link/add_timeline_entry 等)越界即 permission_denied;空数组绑定 deny-all;边界感知匹配整段而非子串前缀)。 2