aboutsummaryrefslogtreecommitdiff
path: root/hooks/tests/test_rulesets_write_boundary.py
blob: 826a94180cffc14a073b268b94b1fb5a87617742 (plain)
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
import json
import os
import subprocess
import sys
from pathlib import Path


SCRIPT = Path(__file__).parents[1] / "rulesets-write-boundary.py"


def run_hook(payload: dict, rulesets: Path) -> dict | None:
    proc = subprocess.run(
        [sys.executable, str(SCRIPT)],
        input=json.dumps(payload),
        text=True,
        capture_output=True,
        env={**os.environ, "RULESETS_ROOT": str(rulesets)},
        check=True,
    )
    return json.loads(proc.stdout) if proc.stdout else None


def test_allows_write_from_rulesets_session(tmp_path):
    rulesets = tmp_path / "rulesets"
    rulesets.mkdir()
    target = rulesets / "file"
    result = run_hook(
        {
            "cwd": str(rulesets),
            "tool_name": "Write",
            "tool_input": {"file_path": str(target)},
        },
        rulesets,
    )
    assert result is None


def test_blocks_absolute_cross_project_write(tmp_path):
    rulesets = tmp_path / "rulesets"
    other = tmp_path / "other"
    rulesets.mkdir()
    other.mkdir()
    result = run_hook(
        {
            "cwd": str(other),
            "tool_name": "Edit",
            "tool_input": {"file_path": str(rulesets / "rule.md")},
        },
        rulesets,
    )
    assert result["hookSpecificOutput"]["permissionDecision"] == "deny"
    assert "inbox-send rulesets" in result["hookSpecificOutput"][
        "permissionDecisionReason"
    ]


def test_blocks_write_reached_through_symlink(tmp_path):
    rulesets = tmp_path / "rulesets"
    other = tmp_path / "other"
    installed = tmp_path / "installed"
    rulesets.mkdir()
    other.mkdir()
    (rulesets / "rules").mkdir()
    installed.symlink_to(rulesets / "rules", target_is_directory=True)
    result = run_hook(
        {
            "cwd": str(other),
            "tool_name": "Write",
            "tool_input": {"file_path": str(installed / "todo-format.md")},
        },
        rulesets,
    )
    assert result["hookSpecificOutput"]["permissionDecision"] == "deny"
    assert str(rulesets) in result["systemMessage"]


def test_blocks_apply_patch_target(tmp_path):
    rulesets = tmp_path / "rulesets"
    other = tmp_path / "other"
    rulesets.mkdir()
    other.mkdir()
    patch = (
        f"*** Begin Patch\n*** Update File: {rulesets / 'file'}\n"
        "@@\n-old\n+new\n*** End Patch\n"
    )
    result = run_hook(
        {
            "cwd": str(other),
            "tool_name": "apply_patch",
            "tool_input": {"input": patch},
        },
        rulesets,
    )
    assert result["hookSpecificOutput"]["permissionDecision"] == "deny"


def test_allows_unrelated_write(tmp_path):
    rulesets = tmp_path / "rulesets"
    other = tmp_path / "other"
    rulesets.mkdir()
    other.mkdir()
    result = run_hook(
        {
            "cwd": str(other),
            "tool_name": "Edit",
            "tool_input": {"file_path": str(other / "file")},
        },
        rulesets,
    )
    assert result is None