agentscope-ai / agentscope-ai/agentscope-java

[Bug]:SandboxBackedFilesystem 并发多用户不安全 &&框架后台任务用 RuntimeContext.empty() 写文件系统,破坏多用户路由

Offen
#1,896 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
area/core/tool bug
Vorherrschende Sprache
Java
Sterne
5.6k
Forks
1.3k
Ø Merge
4 T. 12 Std.
Gemergte PRs (30 T.)
77

Beschreibung

---
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

Beitragsleitfaden

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.