[verified] fix: close 0.3.0 release security gaps
Docker / build (push) Has been cancelled
Build / build (push) Has been cancelled
Lint / ruff (push) Has been cancelled
Test / pytest (ubuntu-latest, 3.11) (push) Has been cancelled
Test / pytest (ubuntu-latest, 3.12) (push) Has been cancelled
Test / pytest (windows-latest, 3.11) (push) Has been cancelled
Test / pytest (windows-latest, 3.12) (push) Has been cancelled

This commit is contained in:
m4
2026-09-03 10:42:46 +08:00
parent c683f6e739
commit 5d893c1dc6
15 changed files with 853 additions and 60 deletions
+77
View File
@@ -1,6 +1,8 @@
from __future__ import annotations
import asyncio
import math
import time
from unittest.mock import MagicMock
import pytest
@@ -15,6 +17,14 @@ def _request(name: str = "task"):
return request
@pytest.mark.parametrize("value", [math.nan, math.inf, -math.inf])
@pytest.mark.parametrize("field", ["timeout_seconds", "cancellation_grace_seconds"])
def test_subagent_timeout_rejects_non_finite_bounds(field, value):
kwargs = {field: value}
with pytest.raises(ValueError, match="finite and positive"):
SubagentTimeoutMiddleware(**kwargs)
@pytest.mark.anyio
async def test_subagent_timeout_returns_stable_tool_error():
middleware = SubagentTimeoutMiddleware(timeout_seconds=0.01)
@@ -172,3 +182,70 @@ async def test_parent_cancellation_after_cleanup_before_timeout_return_wins(monk
)
with pytest.raises(asyncio.CancelledError):
await invocation
@pytest.mark.anyio
async def test_deadline_detaches_handler_that_ignores_cancellation():
middleware = SubagentTimeoutMiddleware(
timeout_seconds=0.01, cancellation_grace_seconds=0.01
)
cleanup_started = asyncio.Event()
release_cleanup = asyncio.Event()
handler_done = asyncio.Event()
async def handler(_request) -> ToolMessage:
try:
await asyncio.Event().wait()
except asyncio.CancelledError:
cleanup_started.set()
await release_cleanup.wait()
finally:
handler_done.set()
return ToolMessage(content="late", tool_call_id="call-1", name="task")
started = time.monotonic()
result = await middleware.awrap_tool_call(_request(), handler)
elapsed = time.monotonic() - started
assert cleanup_started.is_set()
assert elapsed < 0.1
assert result.additional_kwargs["error_code"] == "SUBAGENT_TIMEOUT"
release_cleanup.set()
await asyncio.wait_for(handler_done.wait(), timeout=0.2)
@pytest.mark.anyio
async def test_parent_cancel_detaches_handler_that_ignores_cancellation():
middleware = SubagentTimeoutMiddleware(
timeout_seconds=10, cancellation_grace_seconds=0.01
)
handler_started = asyncio.Event()
cleanup_started = asyncio.Event()
release_cleanup = asyncio.Event()
handler_done = asyncio.Event()
async def handler(_request) -> ToolMessage:
handler_started.set()
try:
await asyncio.Event().wait()
except asyncio.CancelledError:
cleanup_started.set()
await release_cleanup.wait()
finally:
handler_done.set()
return ToolMessage(content="late", tool_call_id="call-1", name="task")
invocation = asyncio.create_task(
middleware.awrap_tool_call(_request(), handler)
)
await handler_started.wait()
started = time.monotonic()
invocation.cancel()
with pytest.raises(asyncio.CancelledError):
await invocation
elapsed = time.monotonic() - started
assert cleanup_started.is_set()
assert elapsed < 0.1
release_cleanup.set()
await asyncio.wait_for(handler_done.wait(), timeout=0.2)