ouroboros/tests/test_protected_artifacts_policy.py

292 lines
13 KiB
Python

"""Explicit black-box operand contracts and ordinary execution capabilities.
Actual file/script operands and shell redirections retain task authority.
Interpreter bodies, quoted patterns and data mentions are not physical targets.
Positive registry execution coverage lives in test_tool_capabilities_black_box_policy.
"""
from types import SimpleNamespace
import pytest
import ouroboros.tools # noqa: F401 — normal package init order (registry first)
from ouroboros.protected_artifacts import shell_block_reason
REF = "reference_executable"
def _ctx(tmp_path, *, allow=("execute",), deny=None):
workspace = tmp_path / "workspace"
workspace.mkdir(exist_ok=True)
(workspace / REF).write_bytes(b"\x7fELF-blackbox")
record = {"id": "programbench_reference", "role": "black_box_reference", "paths": [f"./{REF}"]}
if allow is not None:
record["allow"] = list(allow)
if deny is not None:
record["deny"] = list(deny)
record.pop("allow", None)
ctx = SimpleNamespace(
workspace_root=str(workspace),
workspace_mode="external",
repo_dir=str(workspace),
drive_root=str(tmp_path / "data"),
task_metadata={"task_contract": {"resource_policy": {"protected_artifacts": [record]}}},
task_contract={"resource_policy": {"protected_artifacts": [record]}},
)
return ctx, workspace
def _block(ctx, workspace, cmd):
return shell_block_reason(ctx, cmd, cwd="", default_cwd=workspace)
# ---------------------------------------------------------------------------
# Core denials (the fa59893 harness cases): direct content access stays blocked.
@pytest.mark.parametrize("cmd", [
["cat", "./" + REF],
["cp", "./" + REF, "/tmp/stolen"],
["xxd", "./" + REF],
["dd", "if=./" + REF, "of=/tmp/out"],
["strings", "./" + REF],
["sha256sum", "./" + REF],
])
def test_direct_content_access_blocked(tmp_path, cmd):
ctx, ws = _ctx(tmp_path)
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, cmd)
def test_interpreter_reading_artifact_as_script_blocked(tmp_path):
ctx, ws = _ctx(tmp_path)
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["python3", "./" + REF])
def test_write_and_delete_of_artifact_blocked(tmp_path):
ctx, ws = _ctx(tmp_path)
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["rm", "-f", "./" + REF])
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["sh", "-c", "echo x > ./" + REF])
# ---------------------------------------------------------------------------
# Sanctioned differential-testing workflows pass.
@pytest.mark.parametrize("cmd", [
["./" + REF, "--help"],
"./reference_executable --version > ref.out 2>&1",
"./reference_executable input.wav out.wav && ./executable input.wav out2.wav && diff out.wav out2.wav",
"python3 -c \"import subprocess; print(subprocess.run(['./reference_executable','-h'], capture_output=True).stdout[:64])\"",
])
def test_execute_and_output_capture_allowed(tmp_path, cmd):
ctx, ws = _ctx(tmp_path)
assert _block(ctx, ws, cmd) == ""
def test_write_targets_only_mention_with_scratch_redirect_allowed(tmp_path):
"""fa59893 pin: redirecting to a scratch file while MENTIONING the artifact
is the differential-loop shape, not a write to the artifact."""
ctx, ws = _ctx(tmp_path)
assert _block(ctx, ws, "./reference_executable probe > probes/ref.txt") == ""
# ---------------------------------------------------------------------------
# Round-2 structural exceptions.
def test_compound_segmentation_touch_then_execute_allowed(tmp_path):
"""Live smoke2 FP: `touch /tmp/x && ./reference_executable /tmp/x` read the
execute mention as a touch WRITE target."""
ctx, ws = _ctx(tmp_path)
assert _block(ctx, ws, ["sh", "-c", "touch /tmp/hxprobe/empty && ./reference_executable /tmp/hxprobe/empty"]) == ""
def test_ln_source_is_not_a_write_target(tmp_path):
ctx, ws = _ctx(tmp_path)
assert _block(ctx, ws, ["ln", "-sf", str(ws / REF), "/tmp/hxprobe/executable"]) == ""
def test_git_diff_output_limited_shapes_allowed_content_diff_blocked(tmp_path):
ctx, ws = _ctx(tmp_path)
# Output-LIMITED diffs (names/stat only — the vcs_diff(stat=true) FP shape) pass.
assert _block(ctx, ws, ["git", "diff", "--stat"]) == ""
assert _block(ctx, ws, ["git", "diff", "--staged", "--name-only"]) == ""
assert _block(ctx, ws, ["git", "diff", "--stat", "HEAD"]) == "" # limited output wins over rev
# A BARE worktree diff can dump a modified text file's content → still blocked
# via the whole-work_dir fallback (conservative: the guard can't know the
# protected artifact is binary); a rev/content-flag diff likewise.
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["git", "diff"])
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["git", "diff", "4b825dc642cb6eb9a060e54bf8d69288fbee4904"])
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["git", "diff", "--binary"])
# A pathspec naming the protected file blocks even under --stat.
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["git", "diff", "--stat", "--", REF])
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["git", "show", "HEAD:" + REF])
def test_heredoc_quoted_mentions_in_verify_check_allowed(tmp_path):
"""Live smoke2 FP: a verify check whose python heredoc merely QUOTES the
protected filename in asserts got its tokens treated as read candidates."""
ctx, ws = _ctx(tmp_path)
check = (
"python3 - <<'PY'\n"
"import subprocess\n"
"ignored = subprocess.check_output(['git','status','--short','--ignored'], text=True)\n"
"assert '!! executable' in ignored\n"
"assert '!! reference_executable' in ignored\n"
"print('OK')\n"
"PY"
)
assert _block(ctx, ws, ["sh", "-c", check]) == ""
def test_pty_spawn_with_output_reads_allowed(tmp_path):
"""Live smoke2 FP: pty differential probes spawn the artifact and read the
pty STREAM (`os.read(fd)`) — execute + output capture, not a byte read."""
ctx, ws = _ctx(tmp_path)
script = (
"import os, pty, select\n"
"pid, fd = pty.fork()\n"
"if pid == 0:\n"
" os.execv('./reference_executable', ['./reference_executable', '--help'])\n"
"out = b''\n"
"while True:\n"
" r, _, _ = select.select([fd], [], [], 1)\n"
" if not r: break\n"
" out += os.read(fd, 4096)\n"
"print(len(out))\n"
)
assert _block(ctx, ws, ["python3", "-c", script]) == ""
# pexpect.spawn shape too.
script2 = "import pexpect\nchild = pexpect.spawn('./reference_executable --i x.wav')\nprint(child.read())\n"
assert _block(ctx, ws, ["python3", "-c", script2]) == ""
def test_open_near_mention_is_not_a_direct_target(tmp_path):
"""An inline body is assessed as a whole by the configured Supervisor."""
ctx, ws = _ctx(tmp_path)
script = (
"import subprocess\n"
"subprocess.run(['./reference_executable', '-h'])\n"
"data = open('./reference_executable', 'rb').read()\n"
)
assert _block(ctx, ws, ["python3", "-c", script]) == ""
def test_execute_denied_artifact_requires_actual_execution_operand(tmp_path):
"""A concrete execute operand is distinct from a quoted program body."""
ctx, ws = _ctx(tmp_path, allow=None, deny=["execute", "read_bytes", "copy", "hash",
"static_introspection", "dynamic_trace", "debug",
"write", "delete"])
script = "import subprocess\nsubprocess.run(['./reference_executable', '-h'])\n"
assert _block(ctx, ws, ["python3", "-c", script]) == ""
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["./" + REF, "--help"])
def test_proximity_does_not_change_program_body_admission(tmp_path):
"""Source proximity never establishes a physical target."""
ctx, ws = _ctx(tmp_path)
far = (
"import subprocess, hashlib\n"
"out = subprocess.run(['./reference_executable', 'x'], capture_output=True).stdout\n"
+ "pad = 1\n" * 40 +
"print(hashlib.sha256(out).hexdigest())\n"
)
assert _block(ctx, ws, ["python3", "-c", far]) == ""
near = "import hashlib\nprint(hashlib.sha256(open('./reference_executable','rb').read()).hexdigest())\n"
assert _block(ctx, ws, ["python3", "-c", near]) == ""
# --- v6.56.0 review regressions: round-2 exception BYPASSES (adversarial) ------
def test_program_body_spawn_arguments_are_not_direct_operands(tmp_path):
"""Program-internal argv remains opaque to the direct operand policy."""
ctx, ws = _ctx(tmp_path)
for prog in ("cat", "cp", "sha256sum", "xxd", "od", "install"):
arg = "'/tmp/x'" if prog in ("cp", "install") else ""
sep = ", " if arg else ""
script = f"import subprocess\nsubprocess.run(['{prog}', './reference_executable'{sep}{arg}])\n"
assert _block(ctx, ws, ["python3", "-c", script]) == "", prog
# os.execvp with a read tool as the program, artifact as an argv element.
ex = "import os\nos.execvp('cat', ['cat', './reference_executable'])\n"
assert _block(ctx, ws, ["python3", "-c", ex]) == ""
# But the genuine execute (artifact IS argv[0], incl. the exec(prog,[prog,...])
# echo) stays exempt.
assert _block(ctx, ws, ["python3", "-c",
"import subprocess\nsubprocess.run(['./reference_executable', '-h'])\n"]) == ""
assert _block(ctx, ws, ["python3", "-c",
"import os\nos.execv('./reference_executable', ['./reference_executable', '-h'])\n"]) == ""
def test_git_diff_stat_with_patch_flag_dumps_content_blocks(tmp_path):
"""`--stat`/`--name-only` are content-free ONLY without a patch flag: `-p`/
`-u`/`--patch` re-enable hunk output alongside the stat and can dump the
protected file's content, so the whole-work_dir fallback must NOT be skipped."""
ctx, ws = _ctx(tmp_path)
for cmd in (["git", "diff", "--stat", "-p"],
["git", "diff", "--name-only", "-p"],
["git", "diff", "-p", "--stat"],
["git", "diff", "--stat", "--patch"],
["git", "diff", "--numstat", "-U5"]):
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, cmd), cmd
# content-free stat diffs (the real vcs_diff FP) still pass.
assert _block(ctx, ws, ["git", "diff", "--stat"]) == ""
assert _block(ctx, ws, ["git", "diff", "--name-only"]) == ""
def test_python_dash_m_file_operand_read_blocks(tmp_path):
"""`python -m <module> <file>` OPENS the file operand (pdb/py_compile/zipfile/
trace); the artifact passed as that operand is a read/copy and must block."""
ctx, ws = _ctx(tmp_path)
for cmd in (["python3", "-m", "pdb", "./reference_executable"],
["python3", "-m", "py_compile", "./reference_executable"],
["python3", "-m", "zipfile", "-c", "/tmp/o.zip", "./reference_executable"]):
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, cmd), cmd
def test_alias_source_is_not_a_direct_read_target(tmp_path):
"""Variable binding inside source does not create a direct operand."""
ctx, ws = _ctx(tmp_path)
pad = "\n".join(f"pad{i} = {i}" for i in range(60))
for read in ("print(open(p, 'rb').read())",
"import pathlib; q = pathlib.Path(p); data = q.read_bytes()",
"import shutil; shutil.copy(p, '/tmp/leak')"):
script = f"p = './reference_executable'\n{pad}\n{read}"
assert _block(ctx, ws, ["python3", "-c", script]) == "", read
def test_alias_bound_execute_not_a_read_allowed(tmp_path):
"""A program body may perform useful differential execution."""
ctx, ws = _ctx(tmp_path)
pad = "\n".join(f"pad{i} = {i}" for i in range(60))
script = f"import subprocess\np = './reference_executable'\n{pad}\nsubprocess.run([p, '--version'], capture_output=True)"
assert _block(ctx, ws, ["python3", "-c", script]) == ""
# --- v6.57.0 (1.6): glob carve-out — a scratch-cleaning glob beside the ref -----
def test_scratch_cleaning_glob_beside_protected_allowed(tmp_path):
"""`rm -f *.out` must NOT block just because the black-box binary sits in the
same directory — the glob pattern `*.out` cannot match `reference_executable`.
Now the pattern is matched against the protected basename, not the whole dir.
(A compound `rm ... && ./ref` remains a separate flat-parse limitation.)"""
ctx, ws = _ctx(tmp_path)
assert _block(ctx, ws, ["rm", "-f", "*.out"]) == ""
assert _block(ctx, ws, ["rm", "-f", "*.txt"]) == ""
# A glob subdir'd away from the artifact is likewise fine.
assert _block(ctx, ws, ["rm", "-f", "probes/*.out"]) == ""
def test_glob_matching_protected_name_still_blocks(tmp_path):
"""A glob whose pattern CAN match the protected artifact's basename still blocks
(the carve-out is precise, not a blanket un-blocking of globs)."""
ctx, ws = _ctx(tmp_path)
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["sh", "-c", "rm -f reference_*"])
assert "RESOURCE_POLICY_BLOCKED" in _block(ctx, ws, ["sh", "-c", "rm -f reference_executabl?"])
def test_refusal_names_nearest_allowed_action(tmp_path):
"""The refusal message names WHAT IS allowed (execute + capture its output) so
the agent redirects in one move (1.6)."""
ctx, ws = _ctx(tmp_path)
reason = _block(ctx, ws, ["cat", "./" + REF])
assert "RESOURCE_POLICY_BLOCKED" in reason
assert "EXECUTE" in reason and "stdout" in reason