File size: 6,534 Bytes
9fca766
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
"""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()"