状态:实施中(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;ProtectKernel* 等选项会创建挂载命名空间,LockPersonality、RestrictRealtime、SystemCallFilter 等在非 root 用户下会隐含 NoNewPrivileges,所以都不能用。实际只设了 MemoryMax、TasksMax(实施时更正)。mnt/ 下失效的挂载(fusermount3 -uz),再按登记重新挂载。只有挂载程序本身有改动时,deploy.sh 才重启它,挂载中断几秒后自动恢复。挂载参数:ro,nosuid,nodev,noexec,allow_other。
| 行为 | 说明 |
|---|---|
| 只读 | 挂载层面只读,任何写入都返回"只读文件系统" |
| 隐藏文件 | 以 . 开头的文件和目录不出现在目录列表中,按路径访问也返回"不存在"。ls -a、find、grep -r 都看不到(已实测) |
| 权限 | 文件显示为所有人只读、目录为所有人可进入。实际以部署用户的身份读取,不修改原文件的权限 |
| 符号链接 | 原样呈现,由读取方在自己的文件系统视图中解析。指向挂载范围外的链接在平台和沙箱中会失效 |
| 防止路径替换 | 每次访问先打开源目录并核对设备号、inode 号,再相对它打开(openat2 + RESOLVE_BENEATH \| RESOLVE_NO_SYMLINKS \| RESOLVE_NO_XDEV)。即使有人在访问过程中把目录替换成符号链接,也不会读到源目录以外的内容;源目录下另外挂载的文件系统不提供 |
| 不占用设备 | 不长期持有源目录的句柄(只有 Agent 正在打开的文件会占用),外接设备可以正常卸载、安全弹出,自动挂载的空闲卸载也不受影响。设备拔出后、挂载表变化被处理之前,原路径即使露出挂载点下面的其他文件,也会因设备号不符而返回"输入/输出错误" |
| 不可用时 | 资料库状态不是"正常"时,挂载点根目录照常存在,读取目录或文件返回"输入/输出错误" |
登记、列目录、每次解析源目录时都会检查:
/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_*),不放在命令行里,因为命令行在进程列表中所有人可见。
状态:
rclone lsf 列根目录,带超时)的结果和错误信息,得出 ok、unreachable、auth_failed、path_missing(存储桶不存在)、mount_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 沙箱中 | ✅ 同上 |
| 只读、传入平台服务 | ✅ |
2026-11-06:挂载服务(第二期)在生产环境实测,用 root 创建的 loop 设备(ext4 镜像)模拟外接盘。
| 测试 | 结果 |
|---|---|
平台连接挂载服务(平台的 /srv 是只读的,socket 仍可连接) |
✅ |
agentsvc 在平台服务的命名空间中读取:ls、cat、grep -r |
✅ 原目录权限 0700,agentsvc 不能直接读,经挂载可读;.env、.git 不可见 |
| 写入 | ✅ "只读文件系统" |
| 安全弹出(umount) | ✅ 第一版长期持有源目录句柄,umount 报"设备忙",已改为每次访问时打开 |
| 弹出后 | ✅ 挂载表变化立即处理,状态"设备未连接",读取返回"输入/输出错误",挂载点根目录照常存在 |
| 原路径下面有其他文件 | ✅ 不提供 |
插回并挂到另一个位置(/mnt/kbtest → /media/kbdisk) |
✅ 立即恢复,内容正确 |
| 挂载服务重启 | ✅ 按 state.json 恢复挂载,随后平台同步的列表为准 |
现场外接盘 /media/jerry-remote/Data |
✅ 识别为 /dev/sda,UUID 88bc33be-…,设备内路径 / |
2026-11-06:远程存储(第三期),本机 MinIO(专用只读账户 agentserver,存储桶 agentserver-kb)。
| 测试 | 结果 |
|---|---|
| 测试连接:正确凭据 / 错误凭据 / 地址错误 | ✅ 正常并列出根目录 / 认证失败(SignatureDoesNotMatch)/ 无法连接 |
agentsvc 在平台服务的命名空间中读取:ls、cat、grep -r、pandas 读 CSV |
✅;.env、.git 不可见;写入为"只读文件系统" |
| 凭据 | ✅ 不在 rclone 的命令行中;agentsvc 读不到 rclone 进程的环境变量,也进不了内部挂载点 |
| 在 MinIO 中禁用账户 | ✅ 30 秒内显示"认证失败",读取返回"输入/输出错误";重新启用后约 40 秒自动恢复 |
| 强制结束 rclone | ✅ 显示"rclone 意外退出",5 秒后自动重启并恢复 |
待测:两个引擎的沙箱中读取本机目录与远程存储(需要在后台登记后开会话)。
| 介质 | 方式 | 说明 |
|---|---|---|
| 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 的相应修改。apps/mounter(Go,go-fuse):
rules.go:允许范围(写死 /home、/mnt、/media,不能选根目录本身、用户主目录本身、隐藏目录)、逐级选择目录;device.go:解析 /proc/self/mountinfo、/dev/disk/by-uuid,识别系统盘与外接设备,按 UUID 找到设备当前的挂载位置;设备已连接未挂载时访问原路径以触发自动挂载;fs.go:只读文件系统,隐藏 . 开头的文件,统一权限(文件 0444、目录 0555、属主 root),每次访问核对源目录的设备号和 inode 号;manager.go:sync 增删改、状态、state.json、启动时清理残留挂载、监听挂载表;不可用时有人访问会触发重新检查(限速 2 秒);server.go:Unix socket 协议(ping、status、list、check、sync),密钥每次启动重新生成。deploy/agent-mounter.service(以执行 deploy.sh 的用户运行,附加 agentsvc 组);deploy.sh 编译挂载服务,创建 knowledge/mnt、mounter 目录,程序或 unit 有变化时才重启挂载服务;install.sh 开启 user_allow_other;平台服务 Wants=agent-mounter.service。knowledge/mounter.ts(socket 客户端;KnowledgeMounts 负责同步与状态:变更时 sync,挂载服务不可用时按 1 秒起、最长 60 秒的间隔重试;status 发现登记不一致时重新同步)。本机目录新建、修改路径时由挂载服务检查并记录 UUID 与设备内路径;路径不变时不重新检查(设备拔出时也能修改名称、说明)。会话开始时如果用到本机目录,先查询状态(超时 1 秒)。后台接口 /knowledge/mounter、/knowledge/browse、/knowledge/check-local。挂载服务目录在后台文件管理中也不显示。mount_failed(挂载失败)。SDK 升级到 0.7.0(共享类型有变化)。remote.go
RCLONE_CONFIG_KB_*),WebDAV 密码经 rclone obscure -(从标准输入)处理;使用空的 mounter/rclone.conf,不读部署用户自己的 rclone 配置;mounter/remote/<id>(只有部署用户能访问),缓存在 mounter/cache/<id>;本进程的文件系统确认那里确实是 rclone 的挂载后再提供;test 请求:用填写的配置列根目录,返回状态和根目录条目;..、换行注入)、错误分类、用 rclone 的 local 后端做真实的两层挂载(读取、隐藏文件、只读、凭据不在命令行、删除后清理)。deploy.sh 下载固定版本的 rclone(v1.75.2,校验 sha256,缓存在 ~/.cache/agent-server),放到 /srv/agent-server/app/mounter/rclone。KNOWLEDGE_REMOTE_TYPES,后台表单与服务端校验共用);凭据只写不读,留空沿用原值,换了存储类型不沿用;登记列表中的远程存储带凭据发给挂载服务;后台接口 /knowledge/test-remote(编辑时没有重新填写的凭据沿用原值);审计日志只记录类型、地址、存储桶、前缀。