状态:实施中(2026-11-06):第一期(资料库类型重构、网络链接、资料集合)已完成,见 §15。取代 07 §4.1 中"登记服务器路径"的资料库。
现有资料库只能登记平台服务进程看得到的任意路径。平台以 agentsvc 运行,systemd 配置了 ProtectHome=yes、ProtectSystem=strict(06 §5、ADR-0012)。这带来四个问题:
/etc 等系统文件(已验证 agentsvc 能读 /etc/hostname)。隐藏文件(.git、.env)也不会被过滤。目标:
非目标:
kb_search);类型 kind |
登记内容 | Agent 看到的路径 | Agent 读写 | 由谁提供 |
|---|---|---|---|---|
managed 托管目录 |
名称、说明 | /srv/agent-server/knowledge/files/<id> |
只读或可写 | 平台自己的目录,后台"文件管理"上传、编辑 |
local 本机目录 |
源目录(/home、/mnt、/media 下) |
/srv/agent-server/knowledge/mnt/<id> |
只读 | 挂载进程 |
remote 远程存储 |
存储类型、连接参数、凭据、路径 | /srv/agent-server/knowledge/mnt/<id> |
只读 | 挂载进程(rclone) |
link 网络链接 |
链接(http/https) | 无,提示词中给出链接 | 只读 | Agent 用 web_fetch 读取 |
所有资料库都有:名称、说明(告诉 Agent 这里有什么)、启用状态。
迁移:生产环境目前没有登记资料库。开发库中已有的记录迁移为 local,源目录取原路径;挂载进程检查不通过的,显示为"位置不允许"。Profile 中的引用保持不变。
apps/mounter/,依赖 go-fuse 和 golang.org/x/sys。deploy.sh 编译为静态程序,放到 /srv/agent-server/app/mounter/(root 所有、只读)。agent-mounter.service,以部署用户运行(install.sh 执行时的 $SUDO_USER,生产上是 jerry-remote,已确认可以接受它等同于 root)。fusermount3 挂载,不需要 root。需要在 /etc/fuse.conf 中开启 user_allow_other,agentsvc 才能访问。ProtectSystem、ProtectHome、PrivateTmp、PrivateMounts;fusermount3 是 setuid 程序,所以不能开 NoNewPrivileges;ProtectKernelModules、LockPersonality。mnt/ 下失效的挂载(fusermount3 -uz),再按登记重新挂载。只有挂载程序本身有改动时,deploy.sh 才重启它,挂载中断几秒后自动恢复。挂载参数:ro,nosuid,nodev,noexec,allow_other。
| 行为 | 说明 |
|---|---|
| 只读 | 挂载层面只读,任何写入都返回"只读文件系统" |
| 隐藏文件 | 以 . 开头的文件和目录不出现在目录列表中,按路径访问也返回"不存在"。ls -a、find、grep -r 都看不到(已实测) |
| 权限 | 文件显示为所有人只读、目录为所有人可进入。实际以部署用户的身份读取,不修改原文件的权限 |
| 符号链接 | 原样呈现,由读取方在自己的文件系统视图中解析。指向挂载范围外的链接在平台和沙箱中会失效 |
| 防止路径替换 | 打开文件时,从源目录开始逐级打开(openat2 + RESOLVE_BENEATH \| RESOLVE_NO_SYMLINKS)。即使有人在访问过程中把目录替换成符号链接,也不会读到源目录以外的内容 |
| 不可用时 | 资料库状态不是"正常"时,挂载点根目录照常存在,读取目录或文件返回"输入/输出错误" |
登记、列目录、每次解析源目录时都会检查:
/home、/mnt、/media 之下。允许的根目录写死在代码中,不接受平台传来的配置。/home/<user>),只能是更深一层的子目录。. 开头。mnt/<id>),<id> 必须匹配资料库 ID 的格式。后台逐级选择目录时,list 只返回子目录名,隐藏以 . 开头的目录,并标出不能选择的目录及原因。
现场情况:
/media/jerry-remote/Data 写在 fstab 中,开机自动挂载;因此,本机目录不能只按路径判断,而是按"设备加设备内路径"识别:
/proc/self/mountinfo 和 /dev/disk/by-uuid)和设备内的相对路径,同时保留原路径用于显示。/proc/self/mountinfo 的 POLLPRI 事件),立即重新计算状态,不需要定时检查。状态:
| 状态 | 判断 |
|---|---|
ok 正常 |
设备在、路径存在、检查通过 |
device_missing 设备未连接 |
这个 UUID 当前没有挂载在任何地方 |
path_missing 路径不存在 |
设备在,但其中的目录被删除或改名 |
not_allowed 位置不允许 |
实际路径不在 §4.3 允许的范围内 |
unreachable 无法连接 / auth_failed 认证失败 |
远程存储(§6) |
设备由谁挂到系统上:挂载进程只负责识别设备回来了,不负责把设备挂到系统上。
| 方式 | 结论 |
|---|---|
fstab 中加 x-systemd.automount(以及 x-systemd.device-timeout=1s) |
推荐用于用作资料的硬盘。访问路径时系统自动挂载,设备不在时很快报错。由 root 为每块盘配置一次 |
| 桌面自动挂载(udisks2) | 不可靠:需要有人登录桌面,而且只有那个用户能读。目前登录桌面的是 jerry,jerry-remote 读不到 /media/jerry |
| 挂载进程自己挂载设备 | 不做:需要 root 或桌面会话授权 |
后台资料库页显示这些说明。设备状态为"未连接"时,提示检查设备是否已挂载。
/srv/agent-server/mounter/mounter.sock。
/srv/agent-server/mounter/token(0640);/srv/agent-server/mounter/ 整个目录加入内置全局禁止项,两个引擎的沙箱都看不到。目录里还有远程存储的凭据和缓存(§6)。| 请求 | 说明 |
|---|---|
list { path } |
列出可选择的子目录 |
check { source } |
按 §4.3 检查本机目录,返回识别结果(是否在系统盘、UUID、设备内路径) |
test { remote } |
测试远程存储的连接,列出根目录 |
sync { mounts } |
发送完整的登记列表,挂载进程挂上缺少的、卸载多余的、更新有变化的,并返回各挂载的状态 |
status |
各挂载的状态及最近一次变化的时间 |
sync 的列表保存到 /srv/agent-server/mounter/state.json(0600),只用于开机后不等平台就能立即恢复挂载。平台每次 sync 都会覆盖它,而且挂载进程对每一项都会重新检查。sync 下发,也保存在这个文件中。调用时机:
| 时机 | 动作 |
|---|---|
| 平台启动 | sync(挂载进程没启动时,按退避间隔重试) |
| 资料库新增、修改、删除、启用、停用 | sync |
| 挂载进程启动 | 读取 state.json 恢复挂载 |
| 后台打开资料库页 | status |
| 每次会话开始运行 | status(超时 1 秒),不可用的资料库在提示词中标注"暂不可用" |
不需要定时同步。
/srv/agent-server/knowledge/files/<id> 下创建目录,所有者 agentsvc。用后台"文件管理"上传、新建、编辑、删除文件。. 开头的项。远程存储 ──rclone mount(只读)──▶ /srv/agent-server/mounter/remote/<id>(内部,Agent 看不到)
│ 挂载进程的文件系统(隐藏文件过滤、只读、状态)
▼
/srv/agent-server/knowledge/mnt/<id>(Agent 看到的位置)
/srv/agent-server/app/mounter/rclone。Ubuntu 自带的 1.53 太旧。挂载进程为每个远程存储资料库启动一个 rclone 子进程:
rclone mount kb: /srv/agent-server/mounter/remote/<id> --read-only
--vfs-cache-mode full --cache-dir /srv/agent-server/mounter/cache/<id>
--vfs-cache-max-size 1G --vfs-cache-max-age 1h --dir-cache-time 1m
--vfs-cache-mode full 让 pandas 等需要随机读取的程序可以正常工作(xlsx、parquet)。连接参数和凭据:通过子进程的环境变量传给 rclone(RCLONE_CONFIG_KB_*),不放在命令行里,因为命令行在进程列表中所有人可见。
状态:
ok、unreachable、auth_failed。挂载进程需要联网,只访问管理员登记的地址。
| 存储 | 填写 | 覆盖范围 |
|---|---|---|
| S3 兼容 | 地址(endpoint)、区域(可选)、存储桶、路径前缀、Access Key、Secret Key | MinIO、AWS S3、阿里云 OSS、腾讯云 COS、华为云 OBS、七牛、Ceph、R2 等 |
| WebDAV | 地址、用户名、密码、服务类型(通用 / Nextcloud / ownCloud / SharePoint) | 坚果云、Nextcloud、群晖 WebDAV 等 |
SMB、SFTP 视需要在后续增加,表单字段已经确定:SMB 为服务器、共享名、用户名、密码;SFTP 为主机、端口、用户名、密码或私钥。NAS 也可以由系统用 fstab 挂到 /mnt 下,再按"本机目录"登记。
grep -r(这相当于下载全部文件,又慢,公有云还会产生流量费)。kb_list_roots 的清单中列出名称、链接和说明,由 Agent 自己决定:
web_fetch 转为 Markdown;network.blockPrivateFetch 开启时(默认开启),网关 web_fetch 和 Claude 的 WebFetch 都会拦截私网地址。对当前会话可用的链接资料库:
web_fetch。knowledgeCollectionIds。生效的资料库 = 勾选的单个资料库 ∪ 所勾选集合的成员,去掉停用和已删除的。knowledge_roots
+ kind 'managed' | 'local' | 'remote' | 'link'
+ source json local:{ path, uuid?, relPath? };remote:{ type, 连接参数(不含凭据) };其他为空
+ secrets json remote:凭据(不出现在 DTO 中)
+ url text link
path 派生:managed → knowledge/files/<id>;local、remote → knowledge/mnt/<id>;link 为空
access 只有 managed 可以是 'write'
knowledge_collections (id, name, description, root_ids json, created_at, updated_at)
profiles
+ knowledge_collection_ids json 默认 '[]'
managed 按 access 开放读或写;local、remote 只读开放挂载点;link 不对应路径。/srv/agent-server/mounter/ 加入内置全局禁止项。{ knowledgeRootId }):写规则只能指向可写的托管目录;读规则不能指向网络链接。kb_list_roots:
web_fetch 和 Claude 的 WebFetch 拦截逻辑(providers/claude/policy.ts)接收当前会话的链接资料库列表,按 §7 放行。install.sh(首次安装,需 root):
/etc/fuse.conf 中开启 user_allow_other;/srv/agent-server/knowledge/files(agentsvc,0700);/srv/agent-server/knowledge/mnt(部署用户:agentsvc,0750,平台只能进入,不能创建或删除挂载点);/srv/agent-server/mounter(部署用户:agentsvc,0750);agent-mounter.service(User=$SUDO_USER),平台服务配置 Wants=agent-mounter.service。deploy.sh:
| 风险 | 对策 |
|---|---|
| 平台被攻破,指挥挂载进程挂载敏感目录 | 允许的根目录写死在挂载进程中;不能挂 home 目录本身和隐藏目录;隐藏文件在文件系统层过滤;只读。最坏情况是部署用户能读的普通资料被只读暴露 |
| Agent(同为 agentsvc)调用挂载进程,或读取凭据 | 需要密钥;socket、密钥、state.json、rclone 缓存都在 /srv/agent-server/mounter/ 下,该目录在全局禁止项中 |
| 挂载进程被攻破 | 等同于部署用户被攻破(可 sudo)。对策:代码小、只接受本机 socket 上带密钥的请求、只访问登记的远程地址 |
| 访问过程中目录被替换为符号链接 | 逐级用 openat2 打开,不跟随符号链接 |
| 移动设备拔出后,挂载点下原有的其他文件被当成资料 | 按文件系统 UUID 识别设备 |
| 远程存储凭据泄露 | 不出现在命令行、DTO、审计日志中;建议使用只读密钥 |
| 链接资料库被用来访问任意内网地址 | 只放行同源地址,只对勾选了它的会话生效,重定向逐跳检查 |
| 集合扩大授权范围 | 集合页显示使用者,添加成员时提示 |
| 可写托管目录被会话改坏 | 页面提示共享影响;本机原始资料不受影响 |
2026-11-05:bindfs 方案(已放弃,改为自己的文件系统)。
| 测试 | 结果 |
|---|---|
| jerry-remote 不用 root 挂载 | ✅ |
挂载传入平台服务的挂载命名空间(/proc/<pid>/mountinfo) |
✅ 平台服务中 /srv/agent-server/knowledge 是主机挂载的从属挂载 |
| 只读、权限映射 | ✅ |
| bubblewrap 沙箱中读取 | ✅ 可读、不可写、指向 /home 的符号链接读不到 |
2026-11-06:go-fuse 原型(约 80 行)。
| 测试 | 结果 |
|---|---|
ls -la、find、grep -r |
✅ 看不到 .ssh、.git、.env |
| 按路径直接读取隐藏文件 | ✅ "没有那个文件或目录" |
| bubblewrap 沙箱中 | ✅ 同上 |
| 只读、传入平台服务 | ✅ |
待测(实施时):
user_allow_other 后,agentsvc 在平台服务和两个引擎的沙箱中读取挂载目录;| 介质 | 方式 | 说明 |
|---|---|---|
| SMB、SFTP、FTP、HTTP 目录、HDFS、Azure Blob、GCS、Swift | rclone 原生支持 | 增加表单即可 |
| NFS | 系统挂载到 /mnt,按本机目录登记 |
rclone 不支持 |
| Google Drive、OneDrive、Dropbox、Box 等 | rclone 原生支持 | 需要在后台完成 OAuth 授权;国内网络访问不稳定 |
| 百度网盘、阿里云盘、夸克、115 等 | AList / OpenList 转为 WebDAV | 依赖非公开接口,可能失效,还有违反服务条款的风险,不建议作为正式来源 |
| 飞书 / 钉钉文档、Confluence、Notion、语雀 | MCP 服务 | 在线文档不适合挂载成文件 |
| Git 仓库 | 托管目录定期 git pull |
以后再考虑 |
..、符号链接逃逸);openat2 防路径替换;sync 的增删改;| 期 | 内容 | 依赖 |
|---|---|---|
| 一 | 资料库类型重构(托管目录、网络链接,去掉任意路径)、内网同源放行、资料集合 | 只改平台 |
| 二 | 挂载进程:文件系统、隐藏文件过滤、本机目录、设备识别与自动恢复、install.sh / deploy.sh / systemd | 开启 user_allow_other |
| 三 | 远程存储:rclone、S3 兼容、WebDAV、凭据 | 第二期;MinIO 测试环境 |
每期完成后部署、实测,经你确认后再提交。
knowledge_roots(kind、source、secrets、url,去掉 path,Agent 看到的目录由类型和 id 派生);新增 knowledge_collections;profiles.knowledge_collection_ids。已有记录迁移为本机目录(只读),在生产数据库副本上验证过。apps/server/src/knowledge/service.ts(四种类型的增删改、集合、effective() 计算生效资料库、测试访问)、knowledge/describe.ts(提示词与 kb_list_roots 共用的清单、链接放行站点)。本机目录与远程存储暂时不能新建(需要挂载服务)。web_fetch 每一跳按目标是否与会话的链接资料库同源决定是否拦截内网;Claude 的 WebFetch 同样处理(RunContext.fetchAllowOrigins)。/srv/agent-server/mounter/ 加入内置全局禁止项。去掉"任意路径"的检查(checkKnowledgePath),/paths/check 只保留通用检查。test/knowledge.test.ts,以及 access-policy、gateway(同源放行、跳转到其他站点仍拦截)、file-manager 的相应修改。