[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
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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user