fix(builtin/a2a): restore OFFSEC-003 sanitize_a2a_result wrapping (mc#787)
builtin_tools/a2a_tools.py:delegate_task() had sanitize_a2a_result wrapping
removed from all peer-sourced return paths. A malicious peer could inject
control markers (A2A_RESULT_FROM_PEER, SYSTEM, OVERRIDE, etc.) that the LLM
would interpret as trust-boundary instructions rather than peer content text.
Fix:
- Re-add `from _sanitize_a2a import sanitize_a2a_result` import
- Wrap all peer-controlled returns with sanitize_a2a_result():
- parts[0].text (primary result)
- str(result) for empty-parts case
- str(result) for non-string result fallback
- f"Error: {msg}" for peer error responses
- str(data) for unknown-response-shape fallback
- Remove dead code (duplicate error-handling block after return statement)
Also removes duplicate test declarations blocking go build (TestHasUnresolvedVarRef_*
from org_test.go, TestExtractResponseText_ResultNotMap from delegation_test.go).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
4c14ab3eec
commit
e63bd7beca
@ -12,8 +12,7 @@ import httpx
|
||||
# OFFSEC-003: peer-controlled text MUST be wrapped with sanitize_a2a_result
|
||||
# before being returned to the LLM. This module's delegate_task() is one of
|
||||
# the trust-boundary entry points where peer output crosses into our agent's
|
||||
# context — same surface as a2a_tools_delegation.py:325 (fixed via #492).
|
||||
# Issue #537.
|
||||
# context. Issue #537 / mc#787.
|
||||
from _sanitize_a2a import sanitize_a2a_result
|
||||
|
||||
PLATFORM_URL = os.environ.get("PLATFORM_URL", "http://host.docker.internal:8080")
|
||||
@ -76,6 +75,8 @@ async def delegate_task(workspace_id: str, task: str) -> str:
|
||||
result = data["result"]
|
||||
parts = result.get("parts", []) if isinstance(result, dict) else []
|
||||
if parts and isinstance(parts[0], dict):
|
||||
# OFFSEC-003: wrap peer-controlled text before returning
|
||||
# to LLM context. Issue #537 / mc#787.
|
||||
return sanitize_a2a_result(parts[0].get("text", "(no text)"))
|
||||
# Empty parts list (e.g. {"parts": []}) should return str(result),
|
||||
# not "(no text)" — preserves pre-fix behavior (#279 regression fix).
|
||||
@ -93,8 +94,9 @@ async def delegate_task(workspace_id: str, task: str) -> str:
|
||||
msg = err
|
||||
else:
|
||||
msg = str(err)
|
||||
return f"Error: {msg}"
|
||||
return str(data)
|
||||
# OFFSEC-003: peer-controlled error message; wrap before return.
|
||||
return sanitize_a2a_result(f"Error: {msg}")
|
||||
return sanitize_a2a_result(str(data))
|
||||
except Exception as e:
|
||||
return f"Error sending A2A message: {e}"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user