mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
75 lines
2.4 KiB
Python
75 lines
2.4 KiB
Python
"""_remove_container validates the slug before building the log-dir path.
|
|||
|
|
|
||
|
|
The slug (``container_name`` with the ``roboco-agent-`` prefix stripped)
|
||
|
|
flows into ``Path("/data/logs/agents") / slug`` and ``mkdir(parents=True)``.
|
||
|
|
A traversal-shaped slug (``../../etc``) would mkdir outside the logs root.
|
||
|
|
``spawn_agent`` already validates the agent_id the container name is derived
|
||
|
|
from, so this is defense-in-depth — but the guard must fire before the join.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from pathlib import Path
|
||
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from roboco.runtime.orchestrator import AgentOrchestrator
|
||
|
|
|
||
|
|
|
||
|
|
def _make_orchestrator() -> AgentOrchestrator:
|
||
|
|
with patch.object(AgentOrchestrator, "__init__", return_value=None):
|
||
|
|
orch = AgentOrchestrator.__new__(AgentOrchestrator)
|
||
|
|
orch._instances = {}
|
||
|
|
orch._lock = MagicMock()
|
||
|
|
return orch
|
||
|
|
|
||
|
|
|
||
|
|
class _FakeProc:
|
||
|
|
async def wait(self) -> int:
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_remove_container_skips_log_dump_on_traversal_slug(
|
||
|
|
monkeypatch: pytest.MonkeyPatch,
|
||
|
|
) -> None:
|
||
|
|
"""A traversal-shaped slug must NOT mkdir outside /data/logs/agents."""
|
||
|
|
orch = _make_orchestrator()
|
||
|
|
|
||
|
|
mkdir_paths: list[str] = []
|
||
|
|
|
||
|
|
def _fake_mkdir(self: Path, *_a: object, **_kw: object) -> None:
|
||
|
|
mkdir_paths.append(str(self))
|
||
|
|
|
||
|
|
monkeypatch.setattr(Path, "mkdir", _fake_mkdir)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
"asyncio.create_subprocess_exec", AsyncMock(return_value=_FakeProc())
|
||
|
|
)
|
||
|
|
|
||
|
|
# No exception propagates: log-dump is best-effort; removal still runs.
|
||
|
|
await orch._remove_container("roboco-agent-../../etc")
|
||
|
|
|
||
|
|
assert mkdir_paths == [], f"traversal slug must not reach mkdir; got {mkdir_paths}"
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.asyncio
|
||
|
|
async def test_remove_container_normal_slug_dumps_logs(
|
||
|
|
monkeypatch: pytest.MonkeyPatch,
|
||
|
|
) -> None:
|
||
|
|
"""A normal slug passes the guard and reaches the log-dir mkdir."""
|
||
|
|
orch = _make_orchestrator()
|
||
|
|
|
||
|
|
mkdir_paths: list[str] = []
|
||
|
|
|
||
|
|
def _fake_mkdir(self: Path, *_a: object, **_kw: object) -> None:
|
||
|
|
mkdir_paths.append(str(self))
|
||
|
|
|
||
|
|
monkeypatch.setattr(Path, "mkdir", _fake_mkdir)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
"asyncio.create_subprocess_exec", AsyncMock(return_value=_FakeProc())
|
||
|
|
)
|
||
|
|
|
||
|
|
await orch._remove_container("roboco-agent-be-dev-1")
|
||
|
|
|
||
|
|
assert any(p.endswith("/data/logs/agents/be-dev-1") for p in mkdir_paths)
|