Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release History

## 1.0.0b6 (Unreleased)

### Bugs Fixed

- `GET /invocations/{id}` and `POST /invocations/{id}/cancel` no longer reflect a malformed `invocation_id` path parameter (illegal characters or longer than `_MAX_ID_LENGTH`) into the `x-agent-invocation-id` response header and the request-scoped log and span fields. The id is now validated with a generated fallback, matching the create path.

## 1.0.0b5 (2026-06-12)

### Bugs Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ async def _traced_invocation_endpoint(
dispatch: Callable[[Request], Awaitable[Response]],
) -> Response:
raw_invocation_id = request.path_params["invocation_id"]
invocation_id = _sanitize_id(raw_invocation_id, raw_invocation_id)
invocation_id = _sanitize_id(raw_invocation_id, str(uuid.uuid4()))
request.state.invocation_id = invocation_id

raw_session_id = request.query_params.get("agent_session_id", "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------

VERSION = "1.0.0b5"
VERSION = "1.0.0b6"
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# ---------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------
"""Tests that the ``{invocation_id}`` path param is sanitised before it is
reflected into the ``x-agent-invocation-id`` response header."""
import pytest
from httpx import ASGITransport, AsyncClient
from starlette.requests import Request
from starlette.responses import Response

from azure.ai.agentserver.invocations import InvocationAgentServerHost
from azure.ai.agentserver.invocations._constants import InvocationConstants
from azure.ai.agentserver.invocations._invocation import _MAX_ID_LENGTH, _VALID_ID_RE

_HEADER = InvocationConstants.INVOCATION_ID_HEADER


def _build_app() -> InvocationAgentServerHost:
app = InvocationAgentServerHost()

@app.invoke_handler
async def handle(request: Request) -> Response:
return Response(content=b"ok")

@app.get_invocation_handler
async def get_handler(request: Request) -> Response:
return Response(content=b"got")

return app


async def _echoed_id(path_id: str) -> str:
transport = ASGITransport(app=_build_app())
async with AsyncClient(transport=transport, base_url="http://testserver") as client:
resp = await client.get(f"/invocations/{path_id}")
return resp.headers[_HEADER]
Comment on lines +34 to +36


@pytest.mark.asyncio
async def test_invalid_char_id_not_reflected_in_header():
Comment on lines +39 to +40
"""A path id with characters outside the id allow-list is replaced by a
safe fallback rather than echoed back verbatim."""
echoed = await _echoed_id("bad~id")
assert echoed != "bad~id"
assert _VALID_ID_RE.match(echoed)


@pytest.mark.asyncio
async def test_overlong_id_not_reflected_in_header():
"""An over-length path id does not bypass the ``_MAX_ID_LENGTH`` cap."""
echoed = await _echoed_id("a" * (_MAX_ID_LENGTH + 50))
assert len(echoed) <= _MAX_ID_LENGTH
assert _VALID_ID_RE.match(echoed)


@pytest.mark.asyncio
async def test_valid_id_passes_through_unchanged():
"""A well-formed path id is preserved so lookups keep working."""
echoed = await _echoed_id("valid-id_123.abc")
assert echoed == "valid-id_123.abc"