Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions shield-claw/src/shieldclaw/sandbox/docker_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import hashlib
import logging
import os
import platform
import subprocess
import time
import uuid
Expand Down Expand Up @@ -87,13 +86,12 @@ def _detonate_image() -> str:
def _security_opts_for_attacker() -> list[str]:
"""Return security options for the attacker container.

Linux daemons accept ``seccomp=default`` explicitly. Docker Desktop on
Windows treats that token as a profile path, so we fall back to the engine
default there and only assert that we never run ``unconfined``.
Docker applies its default seccomp profile implicitly unless the container
overrides it. Rely on that engine default so detonation stays portable
across Linux CI runners and Docker Desktop environments, and only guard
against accidentally running ``unconfined`` in tests.
"""
if platform.system().lower() == "windows":
return []
return ["--security-opt", "seccomp=default"]
return []


def _compose_start_timeout() -> float:
Expand Down
13 changes: 5 additions & 8 deletions shield-claw/tests/test_docker_orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,9 @@ def test_detonate_timeout_returns_124(mocker: MockerFixture) -> None:
kill_mock.assert_called_once()


def test_detonate_applies_default_seccomp_profile(mocker: MockerFixture) -> None:
"""The attacker container must run under Docker's default seccomp profile."""
def test_detonate_uses_engine_default_seccomp_profile(mocker: MockerFixture) -> None:
"""Detonation should rely on Docker's default seccomp policy, not override it."""
mocker.patch.object(DockerOrchestrator, "_ensure_docker", autospec=True)
mocker.patch("shieldclaw.sandbox.docker_orchestrator.platform.system", return_value="Linux")
completed = subprocess.CompletedProcess(["docker", "run"], 0, "", "")
run_mock = mocker.patch(
"shieldclaw.sandbox.docker_orchestrator.subprocess.run",
Expand All @@ -223,15 +222,13 @@ def test_detonate_applies_default_seccomp_profile(mocker: MockerFixture) -> None

assert outcome.exit_code == 0
cmd = run_mock.call_args[0][0]
assert "--security-opt" in cmd
assert "seccomp=default" in cmd
assert "--security-opt" not in cmd
assert "seccomp=unconfined" not in cmd


def test_detonate_never_uses_unconfined_on_windows(mocker: MockerFixture) -> None:
"""Windows hosts should rely on the engine default instead of forcing unconfined."""
def test_detonate_never_uses_unconfined(mocker: MockerFixture) -> None:
"""Detonation must never opt out of seccomp via ``unconfined``."""
mocker.patch.object(DockerOrchestrator, "_ensure_docker", autospec=True)
mocker.patch("shieldclaw.sandbox.docker_orchestrator.platform.system", return_value="Windows")
completed = subprocess.CompletedProcess(["docker", "run"], 0, "", "")
run_mock = mocker.patch(
"shieldclaw.sandbox.docker_orchestrator.subprocess.run",
Expand Down
10 changes: 4 additions & 6 deletions shield-claw/tests/test_docker_orchestrator_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

import os
import platform
import re
import shutil
import subprocess
Expand Down Expand Up @@ -263,7 +262,7 @@ def test_attacker_network_is_internal_and_blocks_egress(integration_compose: Pat
@pytest.mark.skipif(not _COMPOSE_SRC.is_file(), reason="vulnerable-flask-app compose file missing")
@pytest.mark.skipif(not _docker_available(), reason="Docker engine not available")
def test_attacker_container_uses_default_seccomp_profile(integration_compose: Path) -> None:
"""The live attacker container should expose the default seccomp profile via inspect."""
"""The live attacker container must not disable Docker's default seccomp profile."""
result_id = str(uuid.uuid4())
project = compose_project_name(result_id)
compose_dir = integration_compose.parent
Expand Down Expand Up @@ -347,10 +346,9 @@ def run_detonation() -> None:
assert inspect.returncode == 0, inspect.stderr
security_opt = inspect.stdout.strip()
assert "seccomp=unconfined" not in security_opt
if platform.system().lower() == "windows":
assert security_opt in ("null", "[]", "")
else:
assert "seccomp=default" in security_opt
# Docker's default seccomp policy is typically implicit, so inspect
# often reports null/[] when no override is configured.
assert security_opt in ("null", "[]", "")
finally:
detonation_thread.join(timeout=90.0)
if original_tag is None:
Expand Down
Loading