fix(api): redact subagent stream fields + forward child_session_id
Hardening on top of the salvaged #51642: free-text fields (preview/goal/summary/output_tail) pass redact_sensitive_text(force=True) before leaving on the public /v1/runs SSE stream — same treatment the API already applies to error text — and child_session_id survives the allowlist so clients can correlate the child's session. Both were flagged in the sweeper review of #51642 and unaddressed.
This commit is contained in:
parent
4fbb86d2b8
commit
666076d137
|
|
@ -5982,12 +5982,15 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
"timestamp": ts,
|
||||
}
|
||||
if preview is not None:
|
||||
event["preview"] = preview
|
||||
event["preview"] = redact_sensitive_text(
|
||||
str(preview), force=True
|
||||
)
|
||||
for key in (
|
||||
"goal",
|
||||
"task_count",
|
||||
"task_index",
|
||||
"subagent_id",
|
||||
"child_session_id",
|
||||
"parent_id",
|
||||
"depth",
|
||||
"model",
|
||||
|
|
@ -6005,8 +6008,16 @@ class APIServerAdapter(BasePlatformAdapter):
|
|||
"output_tail",
|
||||
):
|
||||
value = kwargs.get(key)
|
||||
if value is not None:
|
||||
event[key] = value
|
||||
if value is None:
|
||||
continue
|
||||
# Free-text fields can carry child terminal/tool output —
|
||||
# force the same secret redaction the API applies to error
|
||||
# text before it leaves the process on a public stream.
|
||||
if key in ("goal", "summary", "output_tail") and isinstance(
|
||||
value, str
|
||||
):
|
||||
value = redact_sensitive_text(value, force=True)
|
||||
event[key] = value
|
||||
_push(event)
|
||||
# _thinking, subagent.tool, and subagent_progress are intentionally
|
||||
# not forwarded on the /v1/runs stream: they are high-volume UI
|
||||
|
|
|
|||
|
|
@ -941,6 +941,36 @@ class TestRunEventCallback:
|
|||
|
||||
assert adapter._run_statuses[run_id]["last_event"] == "subagent.complete"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_subagent_events_redact_secrets_and_carry_child_session(self, adapter):
|
||||
"""Free-text fields (goal/summary/output_tail/preview) must pass the
|
||||
forced secret redaction before hitting the public /v1/runs stream,
|
||||
and child_session_id must survive the allowlist so clients can
|
||||
correlate the child's session."""
|
||||
run_id = "run_subagent_redact"
|
||||
loop = asyncio.get_running_loop()
|
||||
queue = asyncio.Queue()
|
||||
adapter._run_streams[run_id] = queue
|
||||
adapter._run_statuses.pop(run_id, None)
|
||||
|
||||
callback = adapter._make_run_event_callback(run_id, loop)
|
||||
secret = "sk-proj-abcdef1234567890abcdef1234567890abcdef12"
|
||||
callback(
|
||||
"subagent.complete",
|
||||
preview=f"leaked {secret}",
|
||||
goal=f"use key {secret} to fetch data",
|
||||
subagent_id="deleg_999",
|
||||
child_session_id="child-sess-42",
|
||||
status="completed",
|
||||
summary=f"exported OPENAI_API_KEY={secret} then ran",
|
||||
output_tail=f"env shows {secret}",
|
||||
)
|
||||
|
||||
event = await asyncio.wait_for(queue.get(), timeout=1.0)
|
||||
assert event["child_session_id"] == "child-sess-42"
|
||||
for field in ("preview", "goal", "summary", "output_tail"):
|
||||
assert secret not in event[field], field
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /health endpoint
|
||||
|
|
|
|||
Loading…
Reference in New Issue