Ctrl K
ComunidadGuías…

test/acp/test_agent_guard.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
"""ACP ``_meta`` must never displace a validated request field.

The SDK router merges a request's ``_meta`` *over* its canonical parameters, so a
colliding key silently replaces the real value and leaves no trace. ``_meta`` is
exactly where an application is encouraged to put data from elsewhere — chat-room
provenance, say — so that data must not be able to name the session a prompt runs
against.
"""

from typing import Any

import acp
import pytest
from acp.exceptions import RequestError

from ag2 import Agent
from ag2.acp import ACPAgent
from ag2.acp.guard import colliding_meta_keys
from ag2.acp.testing import connect
from ag2.testing import TestConfig


def _agent(*turns: str) -> Agent:
    return Agent("workie", config=TestConfig(*(turns or ("ok",))))


def _raw(conn: Any) -> Any:
    return conn._conn if hasattr(conn, "_conn") else conn._connection


class TestCollisionDetection:
    def test_a_reserved_field_is_spotted(self) -> None:
        params = {"sessionId": "a", "prompt": [], "_meta": {"session_id": "b"}}

        assert colliding_meta_keys("session/prompt", params) == frozenset({"session_id"})

    def test_a_json_alias_is_spotted_too(self) -> None:
        params = {"sessionId": "a", "prompt": [], "_meta": {"sessionId": "b"}}

        assert colliding_meta_keys("session/prompt", params) == frozenset({"sessionId"})

    def test_namespaced_application_metadata_is_fine(self) -> None:
        params = {"cwd": "/tmp", "mcpServers": [], "_meta": {"ag2.space": {"room": "!r"}}}

        assert colliding_meta_keys("session/new", params) == frozenset()

    def test_a_request_without_meta_is_fine(self) -> None:
        assert colliding_meta_keys("session/new", {"cwd": "/tmp", "mcpServers": []}) == frozenset()


@pytest.mark.asyncio
class TestOverrideIsRefused:
    async def test_meta_cannot_redirect_a_prompt_to_another_session(self) -> None:
        server = ACPAgent(_agent("ok", "ok"))

        async with connect(server) as (conn, _):
            victim = (await conn.new_session(cwd="/tmp")).session_id
            mine = (await conn.new_session(cwd="/tmp")).session_id

            with pytest.raises(RequestError):
                await _raw(conn).send_request(
                    "session/prompt",
                    {
                        "sessionId": mine,
                        "prompt": [{"type": "text", "text": "hi"}],
                        "_meta": {"session_id": victim},
                    },
                )

            target = await server.sessions.get(victim)
            assert list(await server.sessions.stream(target).history.get_events()) == []

    async def test_meta_cannot_replace_the_prompt_payload(self) -> None:
        server = ACPAgent(_agent())

        async with connect(server) as (conn, _):
            session = await conn.new_session(cwd="/tmp")

            with pytest.raises(RequestError):
                await _raw(conn).send_request(
                    "session/prompt",
                    {
                        "sessionId": session.session_id,
                        "prompt": [{"type": "text", "text": "harmless"}],
                        "_meta": {"prompt": [{"type": "text", "text": "substituted"}]},
                    },
                )

    async def test_meta_cannot_retarget_a_new_session_s_cwd(self) -> None:
        server = ACPAgent(_agent())

        async with connect(server) as (conn, _):
            with pytest.raises(RequestError):
                await _raw(conn).send_request(
                    "session/new",
                    {"cwd": "/tmp", "mcpServers": [], "_meta": {"cwd": "/etc"}},
                )

    async def test_meta_cannot_redirect_a_load_to_another_session(self) -> None:
        server = ACPAgent(_agent("ok", "ok"))

        async with connect(server) as (conn, _):
            victim = (await conn.new_session(cwd="/tmp")).session_id
            mine = (await conn.new_session(cwd="/tmp")).session_id

            with pytest.raises(RequestError) as caught:
                await _raw(conn).send_request(
                    "session/load",
                    {"sessionId": mine, "cwd": "/tmp", "mcpServers": [], "_meta": {"sessionId": victim}},
                )

        assert "sessionId" in caught.value.data["reason"]

    async def test_the_error_names_the_offending_key(self) -> None:
        server = ACPAgent(_agent())

        async with connect(server) as (conn, _):
            with pytest.raises(RequestError) as caught:
                await _raw(conn).send_request(
                    "session/new",
                    {"cwd": "/tmp", "mcpServers": [], "_meta": {"cwd": "/etc"}},
                )

        assert "cwd" in caught.value.data["reason"]


@pytest.mark.asyncio
class TestLegitimateMetadataStillWorks:
    async def test_namespaced_metadata_reaches_the_session(self) -> None:
        server = ACPAgent(_agent())

        async with connect(server) as (conn, _):
            response = await _raw(conn).send_request(
                "session/new",
                {"cwd": "/tmp", "mcpServers": [], "_meta": {"ag2.space": {"room": "!r"}}},
            )
            session = await server.sessions.get(response["sessionId"])

        assert session.meta == {"ag2.space": {"room": "!r"}}

    async def test_an_ordinary_prompt_is_unaffected(self) -> None:
        async with connect(ACPAgent(_agent("200"))) as (conn, recorder):
            session = await conn.new_session(cwd="/tmp")
            response = await conn.prompt(
                session_id=session.session_id,
                prompt=[acp.text_block("what's 100 + 100")],
            )

        assert response.stop_reason == "end_turn"
        assert recorder.updates_for(session.session_id)