agentscope-ai / agentscope-ai/agentscope-java
[Bug]:SandboxBackedFilesystem 并发多用户不安全 &&框架后台任务用 RuntimeContext.empty() 写文件系统,破坏多用户路由
- Linguagem predominante
- Java
- Estrelas
- 5.6k
- Forks
- 1.3k
- Merge médio
- 4d 12h
- PRs com merge (30d)
- 77
Descrição
---
Bug 1:SandboxBackedFilesystem 并发多用户不安全
Title: SandboxBackedFilesystem is not safe for concurrent multi-user agents (single volatile field)
## Problem
`SandboxBackedFilesystem` uses a single `volatile Sandbox sandbox` field injected per-call
via `setSandbox()`. This design implicitly assumes one agent instance serves one call at a time.
In a multi-tenant setup (one singleton `HarnessAgent` serving multiple concurrent users),
concurrent calls overwrite each other's sandbox field, causing cross-user sandbox contamination:
- User A's call sets `sandbox = sandboxA`
- User B's call sets `sandbox = sandboxB`
- User A's tool execution now runs inside sandboxB
## Expected Behavior
`getSandbox()` should route to the correct sandbox based on the current `RuntimeContext`
(userId / sessionId), not from a shared mutable field.
## Workaround (current)
We had to implement a custom `RoutingFilesystem` that overrides `getSandbox()` to look up
a per-user `SandboxPool` via `RuntimeContext`. This is hundreds of lines of boilerplate that
reimplements what `IsolationScope.USER` promises to provide.
## Environment
- agentscope-harness: v2.0.0-RC4
- IsolationScope.USER is documented as supported, but the implementation still uses
the single volatile field pattern
## Proposed Fix
Make `getSandbox()` accept a `RuntimeContext` parameter (or use a ThreadLocal bridge)
and route to the correct sandbox slot based on userId/sessionId, consistent with what
`IsolationScope` declares.
---
Bug 2:框架后台任务用 RuntimeContext.empty() 写文件系统,破坏多用户路由
Title: Framework background tasks (ConversationCompactor, WorkspaceMessageBus, etc.)
use RuntimeContext.empty(), making per-user filesystem routing impossible
## Problem
Framework-internal components run asynchronously with `RuntimeContext.empty()` (no userId,
no sessionId):
- `ConversationCompactor` — compacts session history
- `WorkspaceMessageBus`
- `AsyncToolRegistry`
- `SubagentsMiddleware`
In the framework's single-sandbox model this is fine — there's only one sandbox per call.
But in a multi-user setup where filesystem routing depends on `RuntimeContext.userId`,
empty context cannot be routed to the correct user's sandbox.
## Impact
These writes fall through to the host-local filesystem (pod-local), which:
1. Makes pods stateful — session files are lost on pod restart
2. Triggers spurious workspace re-syncs into sandbox on every Compactor write
3. Produces noisy "local workspace changed, re-syncing" logs on every turn
## Root Cause
Background tasks are scheduled on Reactor's `boundedElastic` scheduler without carrying
user context. `InheritableThreadLocal` doesn't propagate through thread pool reuse.
## Proposed Fix
Options (in order of preference):
1. Have the framework propagate session context to background tasks via
`Schedulers.onScheduleHook()` or Reactor `Context`
2. Expose a hook for background task context injection
3. Document that background tasks require a "system sandbox" slot and make the routing
explicit rather than silently falling back to host FS
Guia de contribuição
Avaliação
Esta issue ainda não foi avaliada.