Skip to content

fix: security & robustness hardening (path traversal, cross-agent download, MCP lock, task refs, OAuth XSS, memory cache) - #597

Open
angri450 wants to merge 1 commit into
TencentCloud:developfrom
angri450:feature/hardening-round2
Open

fix: security & robustness hardening (path traversal, cross-agent download, MCP lock, task refs, OAuth XSS, memory cache)#597
angri450 wants to merge 1 commit into
TencentCloud:developfrom
angri450:feature/hardening-round2

Conversation

@angri450

@angri450 angri450 commented Sep 7, 2026

Copy link
Copy Markdown

Round-2 hardening from a code review of the API/infra layers. Six independent fixes, each verified on the running service.

Security

  1. Workspace path traversal (high)workspace_api_path only did lstrip("/"); a ../../../etc/passwd path flowed through to the backend. Data was contained by BackendWorkspace._backend_storage_key (ValueError), but surfaced as a noisy 500. Now rejects any .. segment with 403, matching the discipline already in host_dirs.py / knowledge relpath. Verified: 500 -> 403.

  2. Cross-agent file download (high)is_allowed_host_download_abs_path returned True for any path containing /.octop/agents/ without binding it to the requesting agent's workspace, so a user with access to one (possibly shared) agent could read another agent's memory.sqlite. Allow branch narrowed to the current agent's own workspace prefix. Verified: other-agent path now 403, same-agent download still 200.

  3. OAuth callback self-XSS (low) — user-controlled redirect_after was embedded unescaped in the callback <script>. Now html.escape(..., quote=True) before embedding.

Robustness

  1. MCP shared-server lock bypass (medium) — the sync _call wrapper called _tool.invoke(kwargs) directly when a running event loop existed: no shared lock (concurrent interleaving on a shared MCP session) and it blocked the loop. Now runs the locked coroutine on a dedicated thread (no deadlock with run_coroutine_threadsafe when the caller is on the loop thread). Tested: no-loop / running-loop / async paths + 12 concurrent calls.

  2. Background tasks without strong refs (medium)asyncio.create_task results were dropped (only bool markers kept); GC could collect reload/bootstrap tasks and config changes would silently never rebuild the harness. Added a task set with done-callbacks (agent reload worker, deferred bootstrap, connector reload).

  3. Memory dashboard cache never released (low) — evicted/replaced Memory/Bridge instances were dropped without any close attempt; also fixed the eviction log printing the tuple instead of agent_id. Defensive teardown added (harness-memory has no public close today; sqlite closes with GC, this guards future versions).

Files: api/common/workspace.py, infra/gateway/media/backend_files.py, api/routers/connectors.py, infra/connectors/mcp_tool_cache.py, infra/agents/manager.py, api/routers/providers.py, api/common/memory_client.py.

All changes applied on top of develop (base = develop @ 85532ac). Unit-level checks and live service regression (403/200/200, zero errors) pass on the deployment.

…nt download, MCP lock, task refs, oauth XSS, memory cache release)

Security:
- workspace_api_path now rejects any path containing a `..` segment with 403
  (previously only lstrip("/")'d, so `../../../etc/passwd` flowed through to
  the backend and surfaced as an internal 500; data was contained by
  BackendWorkspace.relative_to but the error path was noisy and defence-in-depth
  relied on a single check).
- is_allowed_host_download_abs_path no longer returns True for *any*
  `/.octop/agents/` path: the allow branch is narrowed to the current agent's
  own workspace prefix, so a user with access to one (possibly shared) agent
  can no longer read another agent's workspace (memory.sqlite etc).
- OAuth callback HTML escapes redirect_after / state_id before embedding them
  in the inline script (user-controlled redirect_after was an unescaped
  self-XSS vector).

Robustness:
- MCP tool sync wrapper (`_call`) no longer bypasses the shared per-server lock
  when a running event loop exists: it runs the locked coroutine on a
  dedicated thread (avoids deadlock vs run_coroutine_threadsafe when invoked
  on the loop thread, keeps the loop unblocked).
- Background tasks (agent reload worker, deferred bootstrap, connector reload)
  now keep strong references in a task set with done-callbacks, per the
  asyncio requirement to hold a reference to every created task.
- Memory dashboard cache closes/tears-down evicted and replaced Memory/Bridge
  instances defensively, and logs the real agent_id on eviction.
Copilot AI lite review requested due to automatic review settings September 7, 2026 10:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants