agentscope-ai / agentscope-ai/agentscope-java

[Bug]:CompositeFilesystem 的路由前缀会劫持同名的宿主挂载点(/knowledge、/skills…),静默读不到文件

Đang mở
#2,690 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
area/core/memory bug
Ngôn ngữ chính
Java
Star
5.6k
Fork
1.3k
Merge trung bình
4 ngày 12 giờ
Pull request đã merge (30 ngày)
77

Mô tả

> Labels 建议:`bug` / `harness` / `filesystem`

## 环境

| | |
|---|---|
| agentscope-harness | 2.0.2 |
| store | `agentscope-extensions-mysql` 2.0.2(`JdbcStore` → MySQL 8.0.21) |
| 场景 | **非 sandbox**,`CompositeFilesystem` + 远程 store,K8s 2 副本 |
| 宿主挂载 | `/repos`(源码,只读)、`/knowledge`(知识库,只读)、`/skills`(技能,只读)、`/workspace`(emptyDir,可写) |

## 现象

系统提示词里明确告诉模型「知识库根目录 = `/knowledge`,需用全路径访问」。模型照做发出 `read_file("/knowledge/services.md")`,**读不到**。

没有异常、没有 WARN,只是返回一个失败结果 —— 从日志上看不出任何异常。

## 根因

`CompositeFilesystem.routeForPath()` 在匹配前会**剥掉前导斜杠**,因此**宿主绝对路径也会参与路由前缀匹配**:

```java
// CompositeFilesystem.java:100-128
private RouteResult routeForPath(String path) {
String matchPath = stripLeadingSlash(path); // :106 "/knowledge/x.md" → "knowledge/x.md"
for (RouteEntry entry : sortedRoutes) {
...
if (matchPath.startsWith(normalizedPrefix)) { // :122 命中 "knowledge/" 路由
String suffix = matchPath.substring(normalizedPrefix.length());
String backendPath = suffix.isEmpty() ? "/" : "/" + suffix; // :124
return new RouteResult(entry.backend(), backendPath, entry.prefix());
}
}
return new RouteResult(defaultBackend, path, null);
}
```

于是 `/knowledge/services.md` 被转发到 `knowledge/` 这条路由:

- 上层 = `RemoteFilesystem`(`knowledge` 段),我们从不往这里写 → 空
- 下层 = `LocalFilesystem(workspace/knowledge, SANDBOXED)`,workspace 是 emptyDir → 空

**读取失败。而宿主机上 `/knowledge/services.md` 明明存在**,只是永远不会被访问到 —— 那需要落到 defaultBackend、由它的 `PathPolicy` 白名单放行。

`RemoteFilesystemSpec.toFilesystem()` 默认注册的 10 条路由里,`knowledge/`、`skills/`、`subagents/`、`plans/`、`memory/` 都可能与宿主挂载点撞名。我们的 `/repos` 之所以没事,纯粹因为没有叫 `repos/` 的路由。

## 复现

1. 用 `RemoteFilesystemSpec` + 任意 `BaseStore` 构建 agent
2. 在宿主机上准备 `/knowledge/services.md`,并把 `/knowledge` 加进可读根(我们是自建 `PathPolicy`;官方 spec 下 defaultBackend 是 `UNRESTRICTED`,理论上任何绝对路径都该放行)
3. 调用 `filesystem.read(rc, "/knowledge/services.md", 0, 10)`
4. 预期读到内容;实际失败 —— 请求被转发到了远程 `knowledge` 段

## 影响

- **静默失败**:无异常、无日志,只有"文件读不到"这一个现象,排查成本高
- 任何把只读卷挂在与路由前缀同名路径上的部署都会中招,而这几个名字(knowledge、skills、plans)恰恰是最自然的命名
- 我们最终只能**弃用 `knowledge/` 这条路由**(我们从不往那儿写,弃用无损),让 `/knowledge/**` 正常落 defaultBackend

## 建议

匹配前区分「宿主绝对路径」与「workspace 相对路径」,例如任一:

- 只对**不以 `/` 开头**的路径做路由匹配;以 `/` 开头的先尝试 defaultBackend,失败再回退路由(会改变现有语义,需评估)
- 提供 `RemoteFilesystemSpec#excludeHostRoots(Path...)`,匹配时显式排除这些前缀
- 至少在路由未命中任何文件、而该路径在宿主机上存在时打一条 WARN,把「疑似被路由劫持」暴露出来

## 相关

可能与 [#2683](https://github.com/agentscope-ai/agentscope-java/issues/2683) 相关,但根因不同:那条是 workspace **内部** `/knowledge` 的路径解析;本条是**外部宿主挂载点**与路由前缀撞名。

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.