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
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ jobs:
strategy:
fail-fast: false
matrix:
# The in-language jail is macOS-native (Seatbelt); the Linux/Docker
# backend is DRAFT and not yet live-verified (see deponent/jail.py).
# CI runs on macOS, where the jail escape-proofs execute for real.
os: [macos-latest]
# Both OSes run the platform-independent core (gate/ledger/receipts/…);
# the macOS-native Seatbelt jail tests and the DRAFT Docker tests skip on
# Linux, while macOS runs the full suite incl. the live Seatbelt proofs.
os: [ubuntu-latest, macos-latest]
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v4
Expand Down
24 changes: 17 additions & 7 deletions tests/test_jail.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
import unittest
from pathlib import Path

from deponent.jail import jail_available, jail_command, run_jailed as sb_run_jailed
from deponent.jail import SANDBOX_EXEC, jail_available, jail_command, run_jailed as sb_run_jailed

# Guard Seatbelt-specific tests on Seatbelt (macOS sandbox-exec) specifically — NOT
# jail_available(), which is True on any host with a Docker backend and so lets these
# Seatbelt-only tests run (and fail closed) on Linux CI.
_SEATBELT_OK = os.path.exists(SANDBOX_EXEC) and os.access(SANDBOX_EXEC, os.X_OK)


def run_jailed(cmd: str, work: Path) -> subprocess.CompletedProcess:
Expand All @@ -31,7 +36,7 @@ def _host_has_network() -> bool:
_HAS_NET = _host_has_network()


@unittest.skipUnless(jail_available(), "sandbox-exec not present (not macOS)")
@unittest.skipUnless(_SEATBELT_OK, "Seatbelt sandbox-exec not present (not macOS)")
class TestJail(unittest.TestCase):
def setUp(self):
self.work = Path(tempfile.mkdtemp(prefix="jailtest-"))
Expand Down Expand Up @@ -96,7 +101,7 @@ def test_child_process_also_jailed(self):
self.assertFalse(os.path.exists(target), "child process escaped the sandbox!")


@unittest.skipUnless(jail_available(), "sandbox-exec not present (not macOS)")
@unittest.skipUnless(_SEATBELT_OK, "Seatbelt sandbox-exec not present (not macOS)")
class TestMemoryWatchdog(unittest.TestCase):
def setUp(self):
self.work = Path(tempfile.mkdtemp(prefix="memtest-"))
Expand Down Expand Up @@ -132,7 +137,7 @@ def test_run_jailed_still_confines_writes(self):


class TestFailClosed(unittest.TestCase):
@unittest.skipUnless(jail_available(), "sandbox-exec not present (not macOS)")
@unittest.skipUnless(_SEATBELT_OK, "Seatbelt sandbox-exec not present (not macOS)")
def test_jail_command_contains_sandbox_exec(self):
from deponent.jail import SANDBOX_EXEC
work = Path(tempfile.mkdtemp())
Expand All @@ -141,16 +146,21 @@ def test_jail_command_contains_sandbox_exec(self):
self.assertIn("ulimit", wrapped)

def test_run_jailed_fails_closed_when_unavailable(self):
# Simulate "no jail" by pointing the check at a missing binary.
# Force "no jail backend" deterministically across platforms: the Seatbelt
# binary missing AND no backend selectable — otherwise a Docker host would
# pick the Docker backend and this would not fail closed.
import deponent.jail as J
original = J.SANDBOX_EXEC
original_sb = J.SANDBOX_EXEC
original_sel = J.select_backend
try:
J.SANDBOX_EXEC = "/nonexistent/sandbox-exec"
J.select_backend = lambda: None
r = J.run_jailed("ls", Path(tempfile.mkdtemp()))
self.assertEqual(r["killed"], "no-jail")
self.assertIsNone(r["returncode"])
finally:
J.SANDBOX_EXEC = original
J.SANDBOX_EXEC = original_sb
J.select_backend = original_sel


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion tests/test_jail_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def run(self, *a, **k):
_DOCKER = DockerBackend()


@unittest.skipUnless(_DOCKER.available(), "no Docker daemon — Docker backend stays DRAFT until verified")
@unittest.skipUnless(os.environ.get("DEPONENT_TEST_DOCKER") == "1", "Docker backend is DRAFT; set DEPONENT_TEST_DOCKER=1 to run")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require Docker availability before enabling tests

When DEPONENT_TEST_DOCKER=1 is exported on a host where Docker is absent or the daemon is down, this decorator now enables the whole class even though _DOCKER.available() is false; each test then calls _DOCKER.run() and fails with infrastructure errors instead of the documented auto-skip behavior in this file. Keep the env opt-in in addition to the availability check, e.g. require both DEPONENT_TEST_DOCKER=1 and _DOCKER.available().

Useful? React with 👍 / 👎.

class TestDockerBackend(unittest.TestCase):
"""Escape-proofs against the live Docker backend. Same contract as Seatbelt:
network denied, writes confined, resources bounded, positive work still runs."""
Expand Down
Loading