feat(security): apply audited PR #6738 remediations and test hardening

- Enforce vision probe SSRF safe local host validation, SafeProbeBackend TOCTOU elimination, and session owner authorization
- Authorize session titling endpoints against registered owner configuration
- Sanitize sensitive metadata logging from agent loop debug logs
- Add async non-blocking execution for spec router tools and thinking decisions
- Harden email cache invalidation fencing and thread-safe mutation
- Add regression test suites for legacy browser authority, spec contracts, spec router request scoping, and task MCP delivery
This commit is contained in:
Alexandre Teixeira
2026-10-27 17:25:25 +00:00
parent 3246ae5d9c
commit 0133de6f0e
43 changed files with 2187 additions and 673 deletions
+119
View File
@@ -0,0 +1,119 @@
"""Spec must never widen a request or discard its evidence obligations."""
import sys
import types
from dataclasses import replace
import pytest
from src import agent_loop
from src.tool_policy import ToolPolicy
from src.tool_schemas import FUNCTION_TOOL_SCHEMAS
from src.turn_contract import active_turn_contract, bind_turn_contract, resolve_turn_contract
def _contract():
return resolve_turn_contract(
capabilities={'notes'}, required_capabilities={'notes'},
schemas=FUNCTION_TOOL_SCHEMAS, policy=ToolPolicy(),
selected_tools={'manage_notes', 'ask_user'}, message='List my notes',
)
def _router(monkeypatch, picks, choice='t1', mode='strict'):
fake = types.ModuleType('src.spec_router')
fake.LAST_DECISIONS = [{'choice': choice, 'top': [(next(iter(picks), ''), .99)]}]
fake.latest_decision = lambda: fake.LAST_DECISIONS[-1]
fake.spec_route_tools = lambda *a, **kw: (picks, True)
monkeypatch.setitem(sys.modules, 'src.spec_router', fake)
monkeypatch.setattr(agent_loop, '_configured_model_tool_surface_raw', lambda *a, **kw: 'spec')
monkeypatch.setattr(agent_loop, 'get_mcp_manager', lambda: None)
monkeypatch.setenv('ODYSSEUS_SPEC_MODE', mode)
def _route(contract):
return agent_loop._spec_router_contract(contract, [{'role': 'user', 'content': 'List my notes'}],
set(), 'http://fixture', 'fixture', 'alice', {}, ToolPolicy())
@pytest.mark.parametrize('picks,choice', [({'bash'}, 't1'), (set(), 'answer_directly'), ({'ask_user'}, 't1')])
def test_router_cannot_replace_required_read_or_offer_other_tools(monkeypatch, picks, choice):
_router(monkeypatch, picks, choice)
contract = _contract()
assert contract.required_read_operation is not None
with bind_turn_contract(contract):
routed = _route(contract)
assert routed.required == contract.required
assert routed.required_read_operation == contract.required_read_operation
assert routed.offered <= contract.offered
assert routed.executable == contract.executable
assert active_turn_contract() is routed
assert active_turn_contract() is None
def test_router_keeps_unavailable_read_outside_required_tool_set(monkeypatch):
# The only representable read outside `required` is an explicitly
# unavailable one; the router must not turn that refusal into a menu.
_router(monkeypatch, {'ask_user'})
base = _contract()
contract = replace(base, required=frozenset(),
unavailable=frozenset({base.required_read_operation.tool}))
with bind_turn_contract(contract):
assert _route(contract) is contract
assert active_turn_contract() is contract
def test_router_narrowing_keeps_read_and_executable_set(monkeypatch):
_router(monkeypatch, {'manage_notes'})
contract = _contract()
with bind_turn_contract(contract):
routed = _route(contract)
assert routed is not contract and routed.routing_experiment == 'spec-router'
assert routed.required == contract.required
assert routed.required_read_operation == contract.required_read_operation
assert routed.offered <= contract.offered
assert routed.executable == contract.executable
@pytest.mark.parametrize('mode', ['off', 'unsupported'])
def test_off_and_unknown_modes_keep_contract_without_router(monkeypatch, mode):
_router(monkeypatch, {'bash'}, mode=mode)
contract = _contract()
assert _route(contract) is contract
def test_router_exception_keeps_bound_contract(monkeypatch):
_router(monkeypatch, {'bash'})
sys.modules['src.spec_router'].spec_route_tools = lambda *a, **kw: 1 / 0
contract = _contract()
with bind_turn_contract(contract):
assert _route(contract) is contract
assert active_turn_contract() is contract
@pytest.mark.asyncio
async def test_driver_refuses_before_provider_or_dispatch(monkeypatch):
from src import spec_driver
monkeypatch.setattr(spec_driver, 'preview_model_response', lambda *a, **k: pytest.fail('provider called'))
monkeypatch.setattr(spec_driver, 'execute_tool_block', lambda *a, **k: pytest.fail('tool called'))
contract = _contract()
with bind_turn_contract(contract):
chunks = [chunk async for chunk in spec_driver.stream_spec_turn(
endpoint_url='http://fixture', model='fixture', messages=[], headers={},
session_id='s', owner='alice', disabled_tools=set(), tool_policy=ToolPolicy())]
assert active_turn_contract() is contract
assert 'unavailable' in ''.join(chunks)
assert chunks[-1] == 'data: [DONE]\n\n'
@pytest.mark.asyncio
async def test_driver_close_restores_context_after_early_disconnect(monkeypatch):
from src import spec_driver
contract = _contract()
with bind_turn_contract(contract):
stream = spec_driver.stream_spec_turn(
endpoint_url='http://fixture', model='fixture', messages=[], headers={},
session_id='s', owner='alice', disabled_tools=set(), tool_policy=ToolPolicy())
await anext(stream)
await stream.aclose()
assert active_turn_contract() is contract
assert active_turn_contract() is None