diff --git a/app/api/endpoints/agent.py b/app/api/endpoints/agent.py index ff276d758..f15e27d88 100644 --- a/app/api/endpoints/agent.py +++ b/app/api/endpoints/agent.py @@ -415,11 +415,14 @@ async def get_agent_chat_session( chat = await web_agent_application.get_accessible_agent_chat(service, session_id, current_user) server_session_id = session_id if not chat: - server_session_id = await web_agent_application.build_web_agent_session_id_async( - current_user, - session_id, - service, - ) + try: + server_session_id = await web_agent_application.build_web_agent_session_id_async( + current_user, + session_id, + service, + ) + except PermissionError: + return _SchemaResponse(success=False, message="会话不存在或无权访问") if server_session_id != session_id: chat = await web_agent_application.get_accessible_agent_chat( service, @@ -535,11 +538,14 @@ async def stop_web_agent_session_task( :param service: Agent 会话应用服务 :return: 停止结果 """ - server_session_id = await web_agent_application.build_web_agent_session_id_async( - current_user, - session_id, - service, - ) + try: + server_session_id = await web_agent_application.build_web_agent_session_id_async( + current_user, + session_id, + service, + ) + except PermissionError: + return _SchemaResponse(success=False, message="会话不存在或无权访问") chat = await web_agent_application.get_accessible_agent_chat( service, server_session_id, diff --git a/tests/test_security_user_boundaries.py b/tests/test_security_user_boundaries.py index 2172e2733..f231f13d4 100644 --- a/tests/test_security_user_boundaries.py +++ b/tests/test_security_user_boundaries.py @@ -220,6 +220,33 @@ def test_upload_denies_foreign_session_without_saving_file(monkeypatch): save.assert_not_awaited() +@pytest.mark.parametrize("method,path", [ + ("GET", "/agent/sessions/web-agent:victim"), + ("POST", "/agent/sessions/web-agent:victim/stop"), +]) +def test_session_endpoints_deny_foreign_session_without_server_error(monkeypatch, method, path): + """读取或停止他人会话返回统一拒绝结果,不能抛出 500 或触达运行中的 Agent。""" + principal = SimpleNamespace(id=2, name="attacker", is_superuser=False) + service = SimpleNamespace( + get=AsyncMock(return_value=SimpleNamespace(user_id="1", username="victim")), + get_accessible=AsyncMock(return_value=None), + ) + manager = Mock() + manager.stop_current_task = AsyncMock(return_value=True) + monkeypatch.setattr(agent_endpoint.agent_application, "get_running_agent_manager", lambda: manager) + app = FastAPI() + app.include_router(agent_endpoint.router, prefix="/agent") + app.dependency_overrides[get_current_active_user] = lambda: principal + app.dependency_overrides[get_agent_chat_service] = lambda: service + with TestClient(app) as client: + response = client.request(method, path) + assert response.status_code == 200 + assert response.json()["success"] is False + assert response.json()["message"] == "会话不存在或无权访问" + manager.stop_current_task.assert_not_awaited() + manager.is_session_busy.assert_not_called() + + @pytest.mark.parametrize("asynchronous", [False, True]) def test_admin_memory_restore_requires_explicit_authorization_on_cache_reuse(asynchronous): """管理员恢复其他归属后,未授权调用也不能通过该管理员的缓存读取。"""