--- title: 权限模型 description: 网络和知识库等权限的申请方式与安装时校验;gem 自己的存储、文件和文件夹、文档抽取不用申请。 section: dev order: 30 updated: 2026-11-05 --- # 权限模型 ## 原则 Gem 在清单里列出要的权限。要申请的只有这几样:网络(`network`)、用户的模型(`providers` 里的 `llm`,花的是用户的钱)、知识库(`knowledge`)、邮件(`email`),以及用户的账号(`connections`)。gem 自己的存储、文件、文档抽取(`ctx.extract`)不是权限,每个 gem 的每个工具都有。 安装没有确认步骤(和手机的应用商店一样,用户在商店的介绍页上看到 gem 能做什么,点一下就装);安装时把清单里的权限记录下来,运行时只看这份记录,不再读清单。 **需要用户配置的,没配置就等于没有。** 能读哪些邮箱、能用知识库里的哪些内容、用哪个外部账号,装好时一个都没有,gem 的请求会被拒绝,直到用户在 gem 的页面上打开。升级时保留用户已经打开的,新增的同样默认关闭。运行时不会弹窗确认。 ## 网络 ```json "permissions": { "network": { "hosts": ["api.open-meteo.com"], "reason": "查询天气预报" } } ``` | 字段 | 必填 | 说明 | | --- | --- | --- | | `hosts` | 是 | gem 能访问的外网主机。只写主机名(`api.example.com`),不带协议和路径,不支持通配符;服务不在默认端口上时带上端口(`api.example.com:8443`,端口不同就是不同的主机);国际化域名写成 `xn--` 开头的形式。IP 地址和 `localhost` 只能在 `allowLocalNetwork` 为 `true` 时写。没有列出的主机,请求在发出之前就被拒绝。 | | `allowLocalNetwork` | 否 | 要不要访问用户的本机和内网,默认 `false`,见下文。为 `true` 时 `hosts` 可以为空。 | | `reason` | 是 | 为什么要联网,显示在 gem 的页面上。 | 工具里的 `ctx.http.fetch` 和 `ctx.extract.fromUrl`(下载一个文档再抽取文字)、能用浏览器的 agent,都按这同一份权限检查,每一跳重定向也一样。在这之上还有用户自己的网络规则(「设置 › 系统 › 网络访问」),gem 的权限只会更窄,不会更宽。 ### 本机和内网 Notochord 把地址分成两类,规则不同: - **外网**:网站和在线服务。用户可以选白名单或黑名单模式,默认放开。 - **本机和内网**:用户的电脑(`localhost`、`127.0.0.1`、`[::1]`),家里或公司内部网络的地址(`10.x.x.x`、`172.16–31.x.x`、`192.168.x.x`、`169.254.x.x`、`100.64–127.x.x`,以及 IPv6 的对应地址),还有解析到这些地址的域名(比如公司内网的 `wiki.corp.example.com`)。 为什么分开:本机和内网里是用户自己的设备和内部服务——路由器和 NAS 的管理页、打印机、公司的 OA 和 wiki——它们常常不设密码,因为平时只有内部的人连得到,里面又常有内部资料。AI 读到的网页可能藏着指令,第三方 gem 也可能想打探用户的内网;如果这些地址能随意访问,就等于在互联网和用户的内网之间开了一道门。所以这一类默认一个都不能访问,只有用户亲自列出的地址才放行。 gem 要访问本机和内网: - 在清单里写 `"allowLocalNetwork": true`(Gem Studio 草稿里写 `"allow_local_network": true`,或在概览的权限里勾「本机和内网」)。gem 的页面上会写明它能访问用户列出的本机和内网地址。 - 能访问的只有用户在「网络访问 › 本机和内网」里列出的地址,gem 自己不能指定;本机和内网的地址不用写进 `hosts`。 - 没写 `allowLocalNetwork` 的 gem,用户列出了某个地址,它也访问不了。 - 例如一个帮开发者测试网站的 gem:用户把 `localhost:5173` 列进名单以后,它的 agent 才能用浏览器打开 `http://localhost:5173`。 - [`resources`](/zh/dev/manifest.md#resources) 的下载永远不访问本机和内网。 ## 文件不是权限 gem 的代码能碰到的文件只有两种:**这一次运行自己的文件夹**,和**它被调用的那个对话的文件夹**。对话外的任何东西碰不到,和对话里的 AI 自己一样(它的文件工具也只在对话文件夹里)。没有固定的路径可以申请,清单里不用写什么:每个工具都有 `ctx.files`。读写的接口见[工具 API](/zh/dev/tools.md#ctxfiles)。 ### 运行文件夹 每次运行都有一个自己的文件夹: | 谁在运行 | 运行文件夹在哪 | | --- | --- | | 聊天里调用的 SOP 或工具 | 这个对话的工作目录里,`runs/<名字>-<时间>/` | | 聊天里调用的对外提供的 agent | 同上,`runs/<agent 名>-<时间>/`,也是这个 agent 会话的工作目录 | | 定时、事件触发的 SOP | 这个 gem 自己的数据文件夹里,`runs/<名字>-<时间>/` | | SOP 里调用的工具和 agent | 和这次 SOP 运行共用一个 | - 放在调用它的对话里,所以运行结果对话里的 AI 用自己的文件工具就能读写,用户也能在对话的工作目录里打开。 - 文件夹里的约定(不强制):`tmp/` 放中间文件,`output/` 放结果。生成的报告写成 Markdown 或 HTML。 - 运行成功结束后删掉 `tmp/`;失败、取消时保留——重试是从头重跑,已经成功的步骤不会再写一遍。什么都没留下的运行文件夹会被删掉。 - 留下了文件时,调用方拿到的是 `{ "output": <返回值>, "folder": "runs/…" }`,`folder` 相对于对话的工作目录;没有留下文件时是原样的返回值。对外提供的 agent 返回 `{ "reply": …, "folder": … }`。 - 卸载 gem 时,它数据文件夹里的运行文件夹一起删除;已经放进对话里的保留,那是用户的文件。 ### 对话文件夹 用户拖进对话的文件、「聊一个文件夹」选的文件夹、别的 gem 留下的结果,都在对话文件夹里。gem 用 `workspace:<相对路径>` 读写它(`workspace:inbox/发票.pdf`;`workspace:` 是顶层)。 - **路径就是普通参数。** AI 把对话文件夹里的相对路径当字符串传进来(`{ "folder": "inbox/", "note": "notes.txt" }`),gem 自己加 `workspace:` 前缀。多个文件、整个子文件夹,gem 自己 `list` 自己处理,不用 AI 一个个点名。`inputSchema` 里不需要也没有专门的文件类型。 - **以 `.` 开头的文件和文件夹对 gem 不存在**(`.env`、`.git`):列不出、读不到、写不进。 - **用户的文件不会丢,改动可以回退。** gem 对对话文件夹的每次修改都记进文件历史:覆盖时旧版本先存起来(100 MB 以内,更大的进系统回收站),`delete` 把文件移到用户的回收站并留一份副本,`move` 只记路径;`move` 到已有路径被拒绝。用户在聊天里重试或编辑上一条消息时,那条分支上 gem 改过的文件自动恢复到改前(之后被别处改过的不动)。没有回收站的环境(无界面运行的服务器版)删除是永久的,但历史里的副本照样能回退。 - 没有对话的调用——页面发起的调用,或者没在 gem 页面上选工作文件夹的定时 SOP——用 `workspace:` 报 `E_PERM_DENIED`。 - gem 的 agent 自带的 `fs.*` 平台工具以 `workspace/` **只读**看到对话文件夹;要改文件,走 gem 自己的工具。 ### 定时和事件触发的 SOP 没有对话。它的 `workspace:` 是用户在 gem 页面「设置 › 工作文件夹」里给**这个 gem** 选的一个文件夹(只在 gem 有定时或事件 SOP 时显示);没选就没有 `workspace:`。不能选的文件夹——文件系统的根、个人文件夹本身以及包含它的、Notochord 的数据目录、系统和其他应用的文件夹、隐藏文件夹——和「聊一个文件夹」一样会被拒绝。 ### 用户看到什么 文件不在 gem 页面的「它能做什么」里,升级也不因为文件重新批准:gem 在文件上能做的事由对话决定,不由清单决定。用户看到文件夹的地方只有两处:对话的工作目录,和 gem 页面上为定时 SOP 选的工作文件夹。装了一个声明了网络的 gem,就等于信任它可以把用到它的那些对话里的文件发到它声明的网站。 ## 存储不是权限 每个 gem 都有自己的存储,放运行状态、缓存和它自己整理出来的记录。它不是权限:清单里不用写,用户不用打开,gem 的页面上也不列。读写的接口见[数据与存储](/zh/dev/storage.md)。 - **每个工具都有全部存储**:`ctx.data`(集合、Log、文档、Blob、引用)、`ctx.storage.kv` 和私有的 SQL 数据库 `ctx.storage.sql`。SOP 也可以直接调用 `storage.kv.*`、`storage.sql.*`。清单里没有存储的字段。 - **`sideEffect: "read"` 是你的承诺,平台不检查。** 计划模式会在用户批准方案之前运行声明为 `read` 的工具,平台不看它实际做了什么。所以只有什么都不写的工具才能声明 `read`:不写 `ctx.data` 和 `ctx.storage`,不写知识库,不发 GET 以外的请求。声明不实,就是 gem 的质量问题。 - **每个 gem 一个自己的文件夹,别的 gem 读不到。** 数据库和 Blob 用这个 gem 自己的密钥加密;文档(`.md` 文件)和定时、事件触发的 SOP 的[运行文件夹](#运行文件夹)是明文。卸载 gem 时整个文件夹连同密钥一起删除。细节见[加密与隔离](/zh/dev/storage.md#加密与隔离)。 ## 资源下载不是权限 清单里的 [`resources`](/zh/dev/manifest.md#resources) 不需要申请权限:下载地址固定写在清单里,由 Notochord 下载并核对校验值,gem 的代码既改不了地址,也不经手下载。它和 gem 的 `network` 权限无关——`network` 管的是工具自己发出的请求。 不过用户看得到,也管得住: - gem 的页面上「它能做什么」里有一栏「下载额外的文件」,逐个列出文件的说明、大小、来自哪个主机、什么时候下载。 - 下载和 gem 自己的请求一样,受用户的网络策略(「设置 › 系统」)约束。 ## 知识库权限 权限字符串统一为 `数据类型.动作:范围`: | 权限 | 含义 | | --- | --- | | `facts.read:<主语>:<前缀>` | 读取某类事实。主语为 `self` 或实体 kind(如 `person`);前缀写 `a.b.*` 或完整谓词,至少两级。 | | `facts.write:<主语>:<前缀>` | 写入某类事实,必须被某条 `facts.read` 覆盖。 | | `entities.resolve:<kind>` | 用已知的邮箱或名字查实体,不能列出全部。 | | `entities.read:<kind>` | 列出该 kind 的全部实体。 | | `entities.write:<kind>` | 新建、编辑该 kind 的实体(包含 resolve)。 | | `collections.read:<目标>` / `collections.write:<目标>` | 目标为 `diary`、`keep` 或 `user-selected`(用户装好后在 gem 的页面上勾选)。授权包含该知识库里面的所有子文件夹。 | | `collections.create` | 创建自己的知识库,创建后自动可读可写。建在别的知识库里面时,还需要对那个知识库有写权限。 | | `changes.manage` | 查看、撤销、修改其他写入者的记录。 | | `origin.override` | 写入时自己指定来源(`origin` / `source`)。 | 校验规则: - 安装时:格式错误、前缀不足两级(例如 `self:*`)、`facts.write` 没有被 `facts.read` 覆盖、在清单里直接写知识库 id,都会让安装失败。 - 运行时:每次调用都重新读取用户当前的授权。用户在 gem 页面的「设置 › 知识库」里收回某条权限后,下一次调用立即生效。 - 查询类方法遇到未授权的内容只是不返回;针对单个对象的操作没有权限会抛错。 详见[知识库 API](/zh/dev/knowledge.md)。
Notochord
简体中文English