Scrypt / tests /test_sandbox.py
IMJONEZZ's picture
SCRYPT: initial commit — game, sandbox, Warden, Space web layer
9fca766
Raw
History Blame Contribute Delete
6.53 kB
"""Sandbox tests: VFS, shell, puzzles, fabrication, mirror, and the
no-real-filesystem tripwire."""
from pathlib import Path
import pytest
from scrypt.sandbox.fabricate import fabricate_home
from scrypt.sandbox.mirror import mirror_home
from scrypt.sandbox.puzzles import plant_all
from scrypt.sandbox.shell import Shell
from scrypt.sandbox.vfs import VFS, VfsError
def make_shell(seed: int = 1) -> Shell:
vfs = VFS()
fabricate_home(vfs, seed=seed)
return Shell(vfs)
# ---------------------------------------------------------------------- vfs
def test_vfs_write_read_roundtrip():
vfs = VFS()
vfs.write("/home/drifter/a/b.txt", "hello")
assert vfs.read("/home/drifter/a/b.txt") == "hello"
assert vfs.read("~/a/b.txt") == "hello"
def test_vfs_relative_paths_and_dotdot():
vfs = VFS()
vfs.write("~/x/deep/file.txt", "v")
vfs.chdir("~/x/deep")
assert vfs.read("file.txt") == "v"
assert vfs.read("../deep/file.txt") == "v"
vfs.chdir("..")
assert vfs.cwd_path == "/home/drifter/x"
def test_vfs_remove_counts_files():
vfs = VFS()
for i in range(5):
vfs.write(f"~/photos/{i}.jpg", "x")
assert vfs.remove("~/photos", recursive=True) == 5
assert vfs.resolve("~/photos") is None
with pytest.raises(VfsError):
vfs.remove("~/photos")
# -------------------------------------------------------------------- shell
def test_shell_ls_hides_dotfiles_without_dash_a():
sh = make_shell()
plain = sh.run("ls").out
assert ".bash_history" not in plain
assert ".bash_history" in sh.run("ls -a").out
def test_shell_pipe_cat_grep():
sh = make_shell()
out = sh.run("cat documents/todo.txt | grep insurance").out
assert "insurance" in out and "gym" not in out
def test_shell_grep_file_directly():
sh = make_shell()
assert "insurance" in sh.run("grep insurance documents/todo.txt").out
def test_shell_glob_expansion():
sh = make_shell()
sh.vfs.chdir("~/photos/vacation")
out = sh.run("ls *.jpg").out
assert "IMG_" in out
def test_shell_find_by_name():
sh = make_shell()
out = sh.run("find / -name todo.txt").out
assert "/home/drifter/documents/todo.txt" in out
def test_shell_rm_reports_deletions():
sh = make_shell()
sh.run("rm -r photos")
assert sh.last_deletions >= 5
def test_shell_revocation():
sh = make_shell()
sh.run("grep x documents/todo.txt")
sh.revoke("grep", "no more needles.")
result = sh.run("grep x documents/todo.txt")
assert "command not found" in result.err and "needles" in result.err
assert "grep" not in sh.available()
def test_most_used_tracks_and_excludes_revoked():
sh = make_shell()
for _ in range(3):
sh.run("ls")
sh.run("pwd")
assert sh.most_used() == "ls"
sh.revoke("ls")
assert sh.most_used() == "pwd"
def test_unknown_command():
sh = make_shell()
assert "command not found" in sh.run("vim todo.txt").err
# ------------------------------------------------------------------ puzzles
def test_zip_puzzle_solvable_via_history():
sh = make_shell(seed=3)
puzzles = {p.id: p for p in plant_all(sh.vfs, seed=3)}
# The password is in .bash_history
history = sh.run("cat ~/.bash_history").out
password = next(
word.rstrip(",") for line in history.splitlines() if "# pw" in line
for word in [line.split()[2]]
)
sh.run(f"cd ~/downloads")
assert "inflating" in sh.run(f"unzip severance.zip {password}").out
assert puzzles["zip_password"].poll(sh) is not None
assert puzzles["zip_password"].solved
def test_zip_rejects_wrong_password():
sh = make_shell(seed=3)
plant_all(sh.vfs, seed=3)
assert "incorrect password" in sh.run("unzip ~/downloads/severance.zip guess").err
def test_hidden_dir_puzzle_requires_reading_manifest():
sh = make_shell(seed=3)
puzzles = {p.id: p for p in plant_all(sh.vfs, seed=3)}
assert puzzles["hidden_dir"].poll(sh) is None
sh.run("cat ~/.warden/manifest.txt")
reward = puzzles["hidden_dir"].poll(sh)
assert reward is not None and reward.kind == "card"
def test_cron_defusal_by_rm():
sh = make_shell(seed=3)
puzzles = {p.id: p for p in plant_all(sh.vfs, seed=3)}
assert puzzles["cron_defusal"].poll(sh) is None
sh.run("rm /etc/cron.d/reinforcement")
reward = puzzles["cron_defusal"].poll(sh)
assert reward is not None and reward.kind == "mercy"
# -------------------------------------------------------------- ingestion
def test_fabricate_is_deterministic():
a, b = VFS(), VFS()
fabricate_home(a, seed=9)
fabricate_home(b, seed=9)
assert [p for p, _ in a.iter_files()] == [p for p, _ in b.iter_files()]
def test_mirror_filters_secrets_and_caps(tmp_path: Path):
(tmp_path / "notes").mkdir()
(tmp_path / "notes" / "diary.txt").write_text("dear diary")
(tmp_path / ".ssh").mkdir()
(tmp_path / ".ssh" / "id_rsa").write_text("PRIVATE KEY")
(tmp_path / "api_token.txt").write_text("sk-12345")
(tmp_path / "big.txt").write_text("x" * 100_000)
(tmp_path / "photo.png").write_bytes(b"\x89PNG....")
vfs = VFS()
mirror_home(vfs, source=tmp_path)
files = dict(vfs.iter_files())
assert files["/home/drifter/notes/diary.txt"].content == "dear diary"
assert not any(".ssh" in p for p in files)
assert not any("api_token" in p for p in files)
# Oversized text and binary files come through as name-only stubs.
assert files["/home/drifter/big.txt"].content == "<mirrored file>"
assert files["/home/drifter/photo.png"].content == "<mirrored file>"
def test_mirror_never_writes_to_host(tmp_path: Path):
(tmp_path / "a.txt").write_text("hi")
before = sorted(p.name for p in tmp_path.rglob("*"))
vfs = VFS()
mirror_home(vfs, source=tmp_path)
vfs.remove("/home/drifter/a.txt") # the Warden deletes the COPY
after = sorted(p.name for p in tmp_path.rglob("*"))
assert before == after
def test_vfs_and_shell_never_import_os():
"""The tripwire: only mirror.py may touch the host filesystem."""
import scrypt.sandbox.puzzles as puzzles
import scrypt.sandbox.shell as shell
import scrypt.sandbox.vfs as vfs
for module in (vfs, shell, puzzles):
source = Path(module.__file__).read_text()
assert "import os" not in source, f"{module.__name__} imports os"
assert "pathlib" not in source, f"{module.__name__} imports pathlib"
assert "open(" not in source, f"{module.__name__} calls open()"