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
+73
View File
@@ -0,0 +1,73 @@
"""Scheduled MCP delivery must use the task owner's mailbox and report failure."""
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from src.task_scheduler import TaskScheduler
@pytest.mark.asyncio
async def test_mcp_delivery_uses_task_owner_mailbox(monkeypatch):
calls = []
monkeypatch.setattr('routes.email_helpers._get_email_config',
lambda **kwargs: calls.append(kwargs) or {'from_address': 'alice@example.test'})
manager = SimpleNamespace(call_tool=AsyncMock(return_value={'exit_code': 0, 'stdout': 'Sent email to alice@example.test'}))
monkeypatch.setattr('src.tool_utils.get_mcp_manager', lambda: manager)
task = SimpleNamespace(id='task', name='Digest', owner='alice')
await TaskScheduler(None)._deliver_via_mcp('mcp__email__send_email', task, 'content')
assert calls == [{'owner': 'alice'}]
assert manager.call_tool.await_args.args[1]['to'] == 'alice@example.test'
assert manager.call_tool.await_args.args[1]['_odysseus_owner'] == 'alice'
@pytest.mark.asyncio
@pytest.mark.parametrize('output', [
{'exit_code': 1, 'stderr': 'failed'},
{'error': 'not connected', 'exit_code': 1},
{'exit_code': 0, 'stdout': 'Error: SMTP failed'},
{'exit_code': 0, 'stdout': 'Draft staged for approval. Nothing has been sent yet.'},
{},
None,
])
async def test_mcp_delivery_failure_is_reported(monkeypatch, output):
monkeypatch.setattr('routes.email_helpers._get_email_config', lambda **kwargs: {})
manager = SimpleNamespace(call_tool=AsyncMock(return_value=output))
monkeypatch.setattr('src.tool_utils.get_mcp_manager', lambda: manager)
task = SimpleNamespace(id='task', name='Digest', owner='alice@example.test')
with pytest.raises(RuntimeError):
await TaskScheduler(None)._deliver_via_mcp('mcp__email__send_email', task, 'content')
@pytest.mark.asyncio
async def test_generic_mcp_delivery_reports_text_errors(monkeypatch):
monkeypatch.setattr('routes.email_helpers._get_email_config', lambda **kwargs: {})
manager = SimpleNamespace(call_tool=AsyncMock(return_value={'exit_code': 0, 'stdout': 'Error: delivery failed'}))
monkeypatch.setattr('src.tool_utils.get_mcp_manager', lambda: manager)
task = SimpleNamespace(id='task', name='Digest', owner='alice@example.test')
with pytest.raises(RuntimeError):
await TaskScheduler(None)._deliver_via_mcp('mcp__custom__send', task, 'content')
@pytest.mark.asyncio
async def test_authenticated_ownerless_action_is_not_executed(monkeypatch):
from src import builtin_actions
monkeypatch.setenv('AUTH_ENABLED', 'true')
action = AsyncMock(return_value=('done', True))
monkeypatch.setitem(builtin_actions.BUILTIN_ACTIONS, 'test_action', action)
task = SimpleNamespace(id='task', name='job', owner=None, action='test_action',
prompt='', task_type='action')
with pytest.raises(RuntimeError):
await TaskScheduler(None)._execute_action(task)
action.assert_not_awaited()
@pytest.mark.asyncio
async def test_authenticated_ownerless_mcp_delivery_is_not_executed(monkeypatch):
monkeypatch.setenv('AUTH_ENABLED', 'true')
manager = SimpleNamespace(call_tool=AsyncMock())
monkeypatch.setattr('src.tool_utils.get_mcp_manager', lambda: manager)
task = SimpleNamespace(id='task', name='job', owner=None)
with pytest.raises(RuntimeError):
await TaskScheduler(None)._deliver_via_mcp('mcp__email__send_email', task, 'content')
manager.call_tool.assert_not_awaited()