claude-dev-env 1.59.0 → 1.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +4 -0
- package/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md +1 -1
- package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -0
- package/audit-rubrics/category_rubrics/category-f-silent-failures.md +1 -1
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +2 -2
- package/audit-rubrics/prompts/category-e-dead-code.md +17 -4
- package/audit-rubrics/prompts/category-f-silent-failures.md +1 -0
- package/docs/CODE_RULES.md +2 -2
- package/hooks/blocking/code_rules_annotations_length.py +189 -10
- package/hooks/blocking/code_rules_dead_module_constant.py +321 -0
- package/hooks/blocking/code_rules_duplicate_body.py +152 -0
- package/hooks/blocking/code_rules_enforcer.py +38 -15
- package/hooks/blocking/code_rules_orphan_css_class.py +196 -0
- package/hooks/blocking/code_rules_typeddict_stub.py +172 -0
- package/hooks/blocking/config/__init__.py +5 -0
- package/hooks/blocking/config/verified_commit_constants.py +118 -0
- package/hooks/blocking/destructive_command_blocker.py +483 -61
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +240 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
- package/hooks/blocking/test_code_rules_enforcer_cross_skill_duplicate.py +146 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +188 -0
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +82 -0
- package/hooks/blocking/test_code_rules_enforcer_orphan_css_class.py +196 -0
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +415 -0
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +156 -0
- package/hooks/blocking/test_destructive_command_blocker.py +213 -0
- package/hooks/blocking/test_verdict_directory_write_blocker.py +720 -0
- package/hooks/blocking/test_verification_verdict_store.py +490 -0
- package/hooks/blocking/test_verified_commit_gate.py +495 -0
- package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -0
- package/hooks/blocking/test_verifier_verdict_minter.py +193 -0
- package/hooks/blocking/verdict_directory_write_blocker.py +667 -0
- package/hooks/blocking/verification_verdict_store.py +686 -0
- package/hooks/blocking/verified_commit_gate.py +535 -0
- package/hooks/blocking/verified_commit_message_accuracy_blocker.py +152 -0
- package/hooks/blocking/verifier_verdict_minter.py +221 -0
- package/hooks/diagnostic/test_hook_log_extractor.py +3 -3
- package/hooks/hooks.json +43 -1
- package/hooks/hooks_constants/blocking_check_limits.py +1 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +6 -0
- package/hooks/hooks_constants/dead_module_constant_constants.py +20 -0
- package/hooks/hooks_constants/destructive_command_segment_constants.py +15 -0
- package/hooks/hooks_constants/duplicate_function_body_constants.py +22 -5
- package/hooks/hooks_constants/orphan_css_class_constants.py +40 -0
- package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +1 -1
- package/hooks/validation/mypy_validator.py +59 -7
- package/hooks/validation/test_mypy_validator.py +94 -0
- package/package.json +1 -1
- package/rules/file-global-constants.md +7 -1
- package/rules/no-cross-skill-duplicate-helpers.md +29 -0
- package/rules/orphan-css-class.md +23 -0
- package/skills/_shared/pr-loop/scripts/preflight_worktree.py +392 -0
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +70 -0
- package/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +263 -0
- package/skills/autoconverge/SKILL.md +54 -17
- package/skills/autoconverge/reference/closing-report.md +59 -17
- package/skills/autoconverge/workflow/aggregate_runs.py +371 -0
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +192 -76
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +76 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +395 -206
- package/skills/autoconverge/workflow/converge.mjs +520 -57
- package/skills/autoconverge/workflow/convergence_summary.py +110 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +7 -0
- package/skills/autoconverge/workflow/render_report.py +488 -397
- package/skills/autoconverge/workflow/test_aggregate_runs.py +134 -0
- package/skills/autoconverge/workflow/test_convergence_summary.py +132 -0
- package/skills/autoconverge/workflow/test_render_report.py +518 -259
- package/skills/pr-converge/reference/per-tick.md +28 -8
- package/skills/rebase/SKILL.md +2 -4
- package/system-prompts/software-engineer.xml +2 -6
- package/hooks/blocking/content_search_to_zoekt_redirector.py +0 -59
- package/hooks/blocking/content_search_zoekt_bash_block_reason.py +0 -25
- package/hooks/blocking/content_search_zoekt_block_payload.py +0 -21
- package/hooks/blocking/content_search_zoekt_indexed_paths.py +0 -24
- package/hooks/blocking/content_search_zoekt_indexed_roots_config.py +0 -131
- package/hooks/blocking/content_search_zoekt_redirect_guidance.py +0 -52
- package/hooks/blocking/test_content_search_to_zoekt_redirector_integration.py +0 -61
- package/hooks/blocking/test_content_search_to_zoekt_redirector_unit.py +0 -92
- package/hooks/blocking/test_content_search_zoekt_indexed_roots_config.py +0 -102
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
"""Tests for the verdict-directory shell guard.
|
|
2
|
+
|
|
3
|
+
The verified-commit gate trusts that only the minter hook writes verdict
|
|
4
|
+
files. The Write/Edit/MultiEdit deny rules in settings.json stop the file
|
|
5
|
+
tools, but a shell command (``python -c``, a redirect, an Out-File) reaches
|
|
6
|
+
the same directory unless a Bash/PowerShell guard blocks it. These tests
|
|
7
|
+
exercise that guard: every shell spelling that targets the verdict directory
|
|
8
|
+
is denied, and commands that touch unrelated paths pass.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import importlib.util
|
|
12
|
+
import json
|
|
13
|
+
import pathlib
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
_HOOK_DIR = pathlib.Path(__file__).parent
|
|
17
|
+
if str(_HOOK_DIR) not in sys.path:
|
|
18
|
+
sys.path.insert(0, str(_HOOK_DIR))
|
|
19
|
+
|
|
20
|
+
guard_spec = importlib.util.spec_from_file_location(
|
|
21
|
+
"verdict_directory_write_blocker",
|
|
22
|
+
_HOOK_DIR / "verdict_directory_write_blocker.py",
|
|
23
|
+
)
|
|
24
|
+
assert guard_spec is not None
|
|
25
|
+
assert guard_spec.loader is not None
|
|
26
|
+
guard_module = importlib.util.module_from_spec(guard_spec)
|
|
27
|
+
guard_spec.loader.exec_module(guard_module)
|
|
28
|
+
references_verdict_directory = guard_module.references_verdict_directory
|
|
29
|
+
decision_for_payload = guard_module.decision_for_payload
|
|
30
|
+
|
|
31
|
+
constants_spec = importlib.util.spec_from_file_location(
|
|
32
|
+
"verified_commit_constants",
|
|
33
|
+
_HOOK_DIR / "config" / "verified_commit_constants.py",
|
|
34
|
+
)
|
|
35
|
+
assert constants_spec is not None
|
|
36
|
+
assert constants_spec.loader is not None
|
|
37
|
+
constants_module = importlib.util.module_from_spec(constants_spec)
|
|
38
|
+
constants_spec.loader.exec_module(constants_module)
|
|
39
|
+
ROOT_KEY_HEX_LENGTH = constants_module.ROOT_KEY_HEX_LENGTH
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_python_write_text_into_verdict_directory_is_flagged() -> None:
|
|
43
|
+
command_text = (
|
|
44
|
+
'python -c "import pathlib; '
|
|
45
|
+
"pathlib.Path.home().joinpath('.claude','verification','x.json')"
|
|
46
|
+
".write_text('{}')\""
|
|
47
|
+
)
|
|
48
|
+
assert references_verdict_directory(command_text) is True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_home_var_redirect_into_verdict_directory_is_flagged() -> None:
|
|
52
|
+
assert (
|
|
53
|
+
references_verdict_directory(
|
|
54
|
+
"echo forged > $HOME/.claude/verification/abc.json"
|
|
55
|
+
)
|
|
56
|
+
is True
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_tilde_redirect_into_verdict_directory_is_flagged() -> None:
|
|
61
|
+
assert (
|
|
62
|
+
references_verdict_directory("echo forged > ~/.claude/verification/abc.json")
|
|
63
|
+
is True
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_powershell_out_file_into_verdict_directory_is_flagged() -> None:
|
|
68
|
+
assert (
|
|
69
|
+
references_verdict_directory(
|
|
70
|
+
"'{}' | Out-File $HOME/.claude/verification/abc.json"
|
|
71
|
+
)
|
|
72
|
+
is True
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_backslash_verdict_path_is_flagged() -> None:
|
|
77
|
+
assert (
|
|
78
|
+
references_verdict_directory(
|
|
79
|
+
"echo forged > C:\\Users\\jon\\.claude\\verification\\abc.json"
|
|
80
|
+
)
|
|
81
|
+
is True
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_unrelated_claude_path_is_not_flagged() -> None:
|
|
86
|
+
assert (
|
|
87
|
+
references_verdict_directory(
|
|
88
|
+
"python $HOME/.claude/hooks/blocking/verified_commit_gate.py"
|
|
89
|
+
)
|
|
90
|
+
is False
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_plain_git_commit_is_not_flagged() -> None:
|
|
95
|
+
assert references_verdict_directory("git commit -m x") is False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_cd_then_tilde_relative_write_is_flagged() -> None:
|
|
99
|
+
assert (
|
|
100
|
+
references_verdict_directory(
|
|
101
|
+
"cd ~/.claude && echo x > verification/a.json"
|
|
102
|
+
)
|
|
103
|
+
is True
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_cd_then_absolute_relative_write_is_flagged() -> None:
|
|
108
|
+
assert (
|
|
109
|
+
references_verdict_directory(
|
|
110
|
+
"cd /home/u/.claude && echo x > verification/a.json"
|
|
111
|
+
)
|
|
112
|
+
is True
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_cd_with_trailing_slash_then_relative_write_is_flagged() -> None:
|
|
117
|
+
assert (
|
|
118
|
+
references_verdict_directory(
|
|
119
|
+
"cd ~/.claude/ ; echo x > verification/x.json"
|
|
120
|
+
)
|
|
121
|
+
is True
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_pushd_then_relative_write_is_flagged() -> None:
|
|
126
|
+
assert (
|
|
127
|
+
references_verdict_directory(
|
|
128
|
+
"pushd $HOME/.claude; echo x > verification/abc.json"
|
|
129
|
+
)
|
|
130
|
+
is True
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_set_location_then_relative_write_is_flagged() -> None:
|
|
135
|
+
assert (
|
|
136
|
+
references_verdict_directory(
|
|
137
|
+
"Set-Location ~/.claude; 'x' | Out-File verification/abc.json"
|
|
138
|
+
)
|
|
139
|
+
is True
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_relative_verdict_filename_shape_is_flagged() -> None:
|
|
144
|
+
assert (
|
|
145
|
+
references_verdict_directory(
|
|
146
|
+
"echo forged > verification/0123456789abcdef.json"
|
|
147
|
+
)
|
|
148
|
+
is True
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_verdict_filename_of_root_key_length_is_flagged() -> None:
|
|
153
|
+
root_key_length_name = "a" * ROOT_KEY_HEX_LENGTH
|
|
154
|
+
assert (
|
|
155
|
+
references_verdict_directory(
|
|
156
|
+
f"echo forged > verification/{root_key_length_name}.json"
|
|
157
|
+
)
|
|
158
|
+
is True
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_verdict_filename_one_hex_short_of_root_key_length_is_not_flagged() -> None:
|
|
163
|
+
too_short_name = "a" * (ROOT_KEY_HEX_LENGTH - 1)
|
|
164
|
+
assert (
|
|
165
|
+
references_verdict_directory(
|
|
166
|
+
f"echo forged > verification/{too_short_name}.json"
|
|
167
|
+
)
|
|
168
|
+
is False
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_verdict_file_pattern_quantifier_tracks_root_key_length() -> None:
|
|
173
|
+
expected_quantifier = "{" + str(ROOT_KEY_HEX_LENGTH) + "}"
|
|
174
|
+
assert expected_quantifier in constants_module.VERDICT_FILE_RELATIVE_REFERENCE_PATTERN
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_cd_into_verdict_directory_then_bare_write_is_flagged() -> None:
|
|
178
|
+
assert (
|
|
179
|
+
references_verdict_directory(
|
|
180
|
+
"cd ~/.claude/verification && echo x > 0123456789abcdef.json"
|
|
181
|
+
)
|
|
182
|
+
is True
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_pushd_into_verdict_directory_then_bare_write_is_flagged() -> None:
|
|
187
|
+
assert (
|
|
188
|
+
references_verdict_directory(
|
|
189
|
+
"pushd $HOME/.claude/verification; echo x > 0123456789abcdef.json"
|
|
190
|
+
)
|
|
191
|
+
is True
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_set_location_into_verdict_directory_then_bare_write_is_flagged() -> None:
|
|
196
|
+
assert (
|
|
197
|
+
references_verdict_directory(
|
|
198
|
+
"Set-Location ~/.claude/verification; 'x' | Out-File 0123456789abcdef.json"
|
|
199
|
+
)
|
|
200
|
+
is True
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_cd_into_verdict_directory_with_trailing_slash_is_flagged() -> None:
|
|
205
|
+
assert (
|
|
206
|
+
references_verdict_directory(
|
|
207
|
+
"cd ~/.claude/verification/ && echo x > 0123456789abcdef.json"
|
|
208
|
+
)
|
|
209
|
+
is True
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_set_location_path_option_into_verdict_directory_then_bare_write_is_flagged() -> None:
|
|
214
|
+
assert (
|
|
215
|
+
references_verdict_directory(
|
|
216
|
+
"Set-Location -Path ~/.claude/verification; "
|
|
217
|
+
"echo forged > 0123456789abcdef.json"
|
|
218
|
+
)
|
|
219
|
+
is True
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_set_location_literal_path_option_into_verdict_directory_then_bare_write_is_flagged() -> None:
|
|
224
|
+
assert (
|
|
225
|
+
references_verdict_directory(
|
|
226
|
+
"Set-Location -LiteralPath ~/.claude/verification; "
|
|
227
|
+
"echo forged > 0123456789abcdef.json"
|
|
228
|
+
)
|
|
229
|
+
is True
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_set_location_path_option_into_claude_home_then_relative_write_is_flagged() -> None:
|
|
234
|
+
assert (
|
|
235
|
+
references_verdict_directory(
|
|
236
|
+
"Set-Location -Path ~/.claude; 'x' | Out-File verification/abc.json"
|
|
237
|
+
)
|
|
238
|
+
is True
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_cd_double_dash_terminator_into_verdict_directory_then_bare_write_is_flagged() -> None:
|
|
243
|
+
assert (
|
|
244
|
+
references_verdict_directory(
|
|
245
|
+
"cd -- ~/.claude/verification && echo forged > 0123456789abcdef.json"
|
|
246
|
+
)
|
|
247
|
+
is True
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_set_location_path_option_into_unrelated_directory_then_write_is_not_flagged() -> None:
|
|
252
|
+
assert (
|
|
253
|
+
references_verdict_directory(
|
|
254
|
+
"Set-Location -Path ~/.claude/hooks; echo x > note.txt"
|
|
255
|
+
)
|
|
256
|
+
is False
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def test_cd_into_verdict_directory_then_cp_is_flagged() -> None:
|
|
261
|
+
assert (
|
|
262
|
+
references_verdict_directory(
|
|
263
|
+
"cd ~/.claude/verification && cp /tmp/forged.json 0123456789abcdef.json"
|
|
264
|
+
)
|
|
265
|
+
is True
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def test_cd_into_verdict_directory_then_mv_is_flagged() -> None:
|
|
270
|
+
assert (
|
|
271
|
+
references_verdict_directory(
|
|
272
|
+
"cd ~/.claude/verification && mv /tmp/forged.json 0123456789abcdef.json"
|
|
273
|
+
)
|
|
274
|
+
is True
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def test_cd_into_verdict_directory_then_tee_is_flagged() -> None:
|
|
279
|
+
assert (
|
|
280
|
+
references_verdict_directory(
|
|
281
|
+
"cd ~/.claude/verification && echo '{}' | tee 0123456789abcdef.json"
|
|
282
|
+
)
|
|
283
|
+
is True
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def test_cd_into_verdict_directory_then_python_write_text_is_flagged() -> None:
|
|
288
|
+
assert (
|
|
289
|
+
references_verdict_directory(
|
|
290
|
+
"cd ~/.claude/verification && "
|
|
291
|
+
"python -c \"import pathlib; "
|
|
292
|
+
"pathlib.Path('0123456789abcdef.json').write_text('{}')\""
|
|
293
|
+
)
|
|
294
|
+
is True
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def test_cd_into_verdict_directory_then_install_is_flagged() -> None:
|
|
299
|
+
assert (
|
|
300
|
+
references_verdict_directory(
|
|
301
|
+
"cd ~/.claude/verification && install /tmp/forged.json 0123456789abcdef.json"
|
|
302
|
+
)
|
|
303
|
+
is True
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def test_cd_into_verdict_directory_ampersand_abutting_tee_is_flagged() -> None:
|
|
308
|
+
assert (
|
|
309
|
+
references_verdict_directory(
|
|
310
|
+
"cd ~/.claude/verification&& echo {} | tee 8a482d8ecd29493f.json"
|
|
311
|
+
)
|
|
312
|
+
is True
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def test_cd_into_verdict_directory_pipe_abutting_cp_is_flagged() -> None:
|
|
317
|
+
assert (
|
|
318
|
+
references_verdict_directory(
|
|
319
|
+
"cd ~/.claude/verification|cp /tmp/f.json 8a482d8ecd29493f.json"
|
|
320
|
+
)
|
|
321
|
+
is True
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def test_cd_into_claude_home_ampersand_abutting_relative_write_is_flagged() -> None:
|
|
326
|
+
assert (
|
|
327
|
+
references_verdict_directory("cd ~/.claude&& echo x > verification/a.json")
|
|
328
|
+
is True
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def test_cd_into_claude_home_pipe_abutting_relative_write_is_flagged() -> None:
|
|
333
|
+
assert (
|
|
334
|
+
references_verdict_directory("cd ~/.claude|tee verification/a.json")
|
|
335
|
+
is True
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def test_add_content_obfuscated_path_write_is_flagged() -> None:
|
|
340
|
+
probe_command = (
|
|
341
|
+
"$p = bytes.fromhex('2e636c61756465').decode() ; Add-Content $p '{}'"
|
|
342
|
+
)
|
|
343
|
+
assert references_verdict_directory(probe_command) is True
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def test_char_cast_obfuscated_path_write_is_flagged() -> None:
|
|
347
|
+
probe_command = (
|
|
348
|
+
"$p = [char]46 + [char[]]@(99,108,97,117,100,101) ; Set-Content $p '{}'"
|
|
349
|
+
)
|
|
350
|
+
assert references_verdict_directory(probe_command) is True
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def test_chr_built_path_write_is_flagged() -> None:
|
|
354
|
+
probe_command = (
|
|
355
|
+
"python -c \"import os; "
|
|
356
|
+
"open(os.path.join(os.path.expanduser(chr(126)),chr(46)+'claude',"
|
|
357
|
+
"'verification','a.json'),'w')\""
|
|
358
|
+
)
|
|
359
|
+
assert references_verdict_directory(probe_command) is True
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def test_pure_chr_chain_verdict_path_forge_is_flagged() -> None:
|
|
363
|
+
chr_chain = "+".join(
|
|
364
|
+
f"chr({each_byte})"
|
|
365
|
+
for each_byte in b"/.claude/verification/a"
|
|
366
|
+
)
|
|
367
|
+
probe_command = f"python -c \"open({chr_chain},chr(119)).write(chr(48))\""
|
|
368
|
+
assert references_verdict_directory(probe_command) is True
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def test_pure_chr_chain_verification_segment_forge_is_flagged() -> None:
|
|
372
|
+
chr_chain = "+".join(f"chr({each_byte})" for each_byte in b"verification")
|
|
373
|
+
probe_command = f"python -c \"open({chr_chain},'w')\""
|
|
374
|
+
assert references_verdict_directory(probe_command) is True
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def test_bytes_fromhex_path_write_is_flagged() -> None:
|
|
378
|
+
probe_command = (
|
|
379
|
+
"python -c \"import os; "
|
|
380
|
+
"open(bytes.fromhex('2e636c61756465').decode(),'w')\""
|
|
381
|
+
)
|
|
382
|
+
assert references_verdict_directory(probe_command) is True
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def test_base64_decoded_path_write_is_flagged() -> None:
|
|
386
|
+
probe_command = (
|
|
387
|
+
"python -c \"import base64; "
|
|
388
|
+
"open(base64.b64decode('LmNsYXVkZQ==').decode(),'w')\""
|
|
389
|
+
)
|
|
390
|
+
assert references_verdict_directory(probe_command) is True
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def test_int_list_bytes_path_write_is_flagged() -> None:
|
|
394
|
+
claude_segment_codes = ",".join(str(each_byte) for each_byte in b".claude")
|
|
395
|
+
probe_command = (
|
|
396
|
+
f"python -c \"open(bytes([{claude_segment_codes}]).decode(),'w')\""
|
|
397
|
+
)
|
|
398
|
+
assert references_verdict_directory(probe_command) is True
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def test_int_list_bytearray_path_write_is_flagged() -> None:
|
|
402
|
+
verification_segment_codes = ",".join(
|
|
403
|
+
str(each_byte) for each_byte in b"verification"
|
|
404
|
+
)
|
|
405
|
+
probe_command = (
|
|
406
|
+
f"python -c \"open(bytearray([{verification_segment_codes}]).decode(),'w')\""
|
|
407
|
+
)
|
|
408
|
+
assert references_verdict_directory(probe_command) is True
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def test_int_list_full_verdict_path_forge_is_flagged() -> None:
|
|
412
|
+
verdict_path_codes = ",".join(
|
|
413
|
+
str(each_byte)
|
|
414
|
+
for each_byte in b"/home/u/.claude/verification/0123456789abcdef.json"
|
|
415
|
+
)
|
|
416
|
+
probe_command = (
|
|
417
|
+
f'python3 -c "f=open(bytes([{verdict_path_codes}]).decode(),'
|
|
418
|
+
"'w');f.write('{}')\""
|
|
419
|
+
)
|
|
420
|
+
assert references_verdict_directory(probe_command) is True
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def test_int_list_bytes_to_unrelated_file_is_not_flagged() -> None:
|
|
424
|
+
unrelated_segment_codes = ",".join(str(each_byte) for each_byte in b"ab")
|
|
425
|
+
probe_command = (
|
|
426
|
+
f"python -c \"open(bytes([{unrelated_segment_codes}]).decode(),'w')\""
|
|
427
|
+
)
|
|
428
|
+
assert references_verdict_directory(probe_command) is False
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def test_chr_without_write_primitive_is_not_flagged() -> None:
|
|
432
|
+
assert references_verdict_directory("python -c \"print(chr(126))\"") is False
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def test_chr_write_to_unrelated_path_is_not_flagged() -> None:
|
|
436
|
+
assert (
|
|
437
|
+
references_verdict_directory('python -c "print(chr(65))" > /tmp/out.txt')
|
|
438
|
+
is False
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def test_base64_decode_to_unrelated_file_is_not_flagged() -> None:
|
|
443
|
+
assert (
|
|
444
|
+
references_verdict_directory(
|
|
445
|
+
'python -c "import base64,sys; '
|
|
446
|
+
"open('decoded.bin','wb').write(base64.b64decode(sys.argv[1]))\""
|
|
447
|
+
)
|
|
448
|
+
is False
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def test_set_content_char_to_unrelated_file_is_not_flagged() -> None:
|
|
453
|
+
assert references_verdict_directory("Set-Content out.txt ([char]65)") is False
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def test_bytes_fromhex_to_unrelated_file_is_not_flagged() -> None:
|
|
457
|
+
assert (
|
|
458
|
+
references_verdict_directory(
|
|
459
|
+
"python -c \"open(bytes.fromhex('6162').decode(),'w')\""
|
|
460
|
+
)
|
|
461
|
+
is False
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def test_obfuscated_write_to_unrelated_path_with_incidental_verification_word_is_not_flagged() -> None:
|
|
466
|
+
assert (
|
|
467
|
+
references_verdict_directory(
|
|
468
|
+
"python -c \"open(chr(47)+chr(116)); print('verification done')\""
|
|
469
|
+
)
|
|
470
|
+
is False
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def test_obfuscated_write_with_decoded_segment_after_separator_stays_flagged() -> None:
|
|
475
|
+
assert (
|
|
476
|
+
references_verdict_directory(
|
|
477
|
+
"python -c \"open(bytes.fromhex('2e636c61756465').decode(),'w'); "
|
|
478
|
+
"print('saved')\""
|
|
479
|
+
)
|
|
480
|
+
is True
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def test_obfuscated_write_with_incidental_claude_word_after_separator_is_not_flagged() -> None:
|
|
485
|
+
assert (
|
|
486
|
+
references_verdict_directory(
|
|
487
|
+
"python -c \"open(chr(47)+chr(116)); print('claude run complete')\""
|
|
488
|
+
)
|
|
489
|
+
is False
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def test_base64_encoded_verification_segment_obfuscated_write_is_flagged() -> None:
|
|
494
|
+
probe_command = (
|
|
495
|
+
"python -c \"import base64; "
|
|
496
|
+
"open(base64.b64decode('dmVyaWZpY2F0aW9u').decode(),'w')\""
|
|
497
|
+
)
|
|
498
|
+
assert references_verdict_directory(probe_command) is True
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def test_hex_encoded_verification_segment_obfuscated_write_is_flagged() -> None:
|
|
502
|
+
probe_command = (
|
|
503
|
+
"python -c \"open(bytes.fromhex('766572696669636174696f6e').decode(),'w')\""
|
|
504
|
+
)
|
|
505
|
+
assert references_verdict_directory(probe_command) is True
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def test_split_cd_into_verdict_directory_then_bare_write_is_flagged() -> None:
|
|
509
|
+
assert (
|
|
510
|
+
references_verdict_directory(
|
|
511
|
+
"cd ~/.claude && cd verification && echo x > 0123456789abcdef.json"
|
|
512
|
+
)
|
|
513
|
+
is True
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def test_split_cd_into_unrelated_subdirectory_then_write_is_not_flagged() -> None:
|
|
518
|
+
assert (
|
|
519
|
+
references_verdict_directory(
|
|
520
|
+
"cd ~/.claude && cd hooks && echo x > note.txt"
|
|
521
|
+
)
|
|
522
|
+
is False
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def test_split_cd_into_verdict_directory_then_copy_is_flagged() -> None:
|
|
527
|
+
assert (
|
|
528
|
+
references_verdict_directory(
|
|
529
|
+
"cd ~/.claude && cd verification && cp /tmp/f.json out.json"
|
|
530
|
+
)
|
|
531
|
+
is True
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def test_split_cd_into_verdict_directory_then_move_is_flagged() -> None:
|
|
536
|
+
assert (
|
|
537
|
+
references_verdict_directory(
|
|
538
|
+
"cd ~/.claude && cd verification && mv /tmp/f.json out.json"
|
|
539
|
+
)
|
|
540
|
+
is True
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def test_split_cd_into_unrelated_subdirectory_then_copy_is_not_flagged() -> None:
|
|
545
|
+
assert (
|
|
546
|
+
references_verdict_directory("cd ~/.claude && cd hooks && cp a b") is False
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def test_concatenated_string_literal_verdict_path_write_is_flagged() -> None:
|
|
551
|
+
assert (
|
|
552
|
+
references_verdict_directory(
|
|
553
|
+
"python -c \"open(str(pathlib.Path.home())+'/.claude'+'/verification'"
|
|
554
|
+
"+'/a.json','w')\""
|
|
555
|
+
)
|
|
556
|
+
is True
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def test_concatenated_absolute_verdict_path_write_is_flagged() -> None:
|
|
561
|
+
assert (
|
|
562
|
+
references_verdict_directory(
|
|
563
|
+
"python -c \"open('/home/u/.claude'+'/verification/a.json','w')\""
|
|
564
|
+
)
|
|
565
|
+
is True
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def test_shell_variable_home_then_verdict_path_write_is_flagged() -> None:
|
|
570
|
+
assert (
|
|
571
|
+
references_verdict_directory(
|
|
572
|
+
"p=~/.claude; echo x > $p/verification/a.json"
|
|
573
|
+
)
|
|
574
|
+
is True
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def test_shell_variable_verdict_directory_name_write_is_flagged() -> None:
|
|
579
|
+
assert (
|
|
580
|
+
references_verdict_directory(
|
|
581
|
+
"VDIR=verification; cd ~/.claude && echo x > $VDIR/a.json"
|
|
582
|
+
)
|
|
583
|
+
is True
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def test_write_without_obfuscation_primitive_is_not_flagged() -> None:
|
|
588
|
+
assert (
|
|
589
|
+
references_verdict_directory("echo hello > /tmp/notes.txt")
|
|
590
|
+
is False
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def test_unrelated_claude_path_with_no_obfuscation_stays_unflagged() -> None:
|
|
595
|
+
assert (
|
|
596
|
+
references_verdict_directory(
|
|
597
|
+
"python $HOME/.claude/hooks/blocking/verified_commit_gate.py"
|
|
598
|
+
)
|
|
599
|
+
is False
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def test_cd_outside_claude_then_relative_write_is_not_flagged() -> None:
|
|
604
|
+
assert (
|
|
605
|
+
references_verdict_directory("cd /tmp/work && echo x > verification/a.json")
|
|
606
|
+
is False
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def test_sibling_verification_directory_is_not_flagged() -> None:
|
|
611
|
+
assert (
|
|
612
|
+
references_verdict_directory("cat ~/.claude/verification-docs/readme.md")
|
|
613
|
+
is False
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def test_commit_message_naming_verdict_path_is_not_flagged() -> None:
|
|
618
|
+
assert (
|
|
619
|
+
references_verdict_directory('git commit -m "fix .claude/verification gate"')
|
|
620
|
+
is False
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def test_verdict_write_with_path_separator_stays_flagged() -> None:
|
|
625
|
+
assert (
|
|
626
|
+
references_verdict_directory("echo x > ~/.claude/verification/a.json")
|
|
627
|
+
is True
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def test_benign_redirect_mentioning_both_words_in_message_is_not_flagged() -> None:
|
|
632
|
+
assert (
|
|
633
|
+
references_verdict_directory(
|
|
634
|
+
'echo "note: updated .claude docs about the verification flow"'
|
|
635
|
+
" > /tmp/notes.txt"
|
|
636
|
+
)
|
|
637
|
+
is False
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def test_benign_single_quoted_message_mentioning_both_words_is_not_flagged() -> None:
|
|
642
|
+
assert (
|
|
643
|
+
references_verdict_directory(
|
|
644
|
+
"echo 'see .claude and verification notes' > /tmp/out.txt"
|
|
645
|
+
)
|
|
646
|
+
is False
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def test_commit_message_naming_verdict_path_with_redirect_is_not_flagged() -> None:
|
|
651
|
+
assert (
|
|
652
|
+
references_verdict_directory(
|
|
653
|
+
'git commit -m "fix .claude/verification gate" > /tmp/commit.log'
|
|
654
|
+
)
|
|
655
|
+
is False
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def test_decision_for_bash_verdict_write_denies() -> None:
|
|
660
|
+
payload = {
|
|
661
|
+
"tool_name": "Bash",
|
|
662
|
+
"tool_input": {"command": "echo x > ~/.claude/verification/a.json"},
|
|
663
|
+
}
|
|
664
|
+
decision = decision_for_payload(payload)
|
|
665
|
+
assert decision is not None
|
|
666
|
+
assert decision["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def test_decision_for_powershell_verdict_write_denies() -> None:
|
|
670
|
+
payload = {
|
|
671
|
+
"tool_name": "PowerShell",
|
|
672
|
+
"tool_input": {
|
|
673
|
+
"command": "'x' | Set-Content $HOME/.claude/verification/a.json"
|
|
674
|
+
},
|
|
675
|
+
}
|
|
676
|
+
decision = decision_for_payload(payload)
|
|
677
|
+
assert decision is not None
|
|
678
|
+
assert decision["hookSpecificOutput"]["permissionDecision"] == "deny"
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def test_decision_for_unrelated_bash_command_is_none() -> None:
|
|
682
|
+
payload = {"tool_name": "Bash", "tool_input": {"command": "git status"}}
|
|
683
|
+
assert decision_for_payload(payload) is None
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def test_decision_for_non_shell_tool_is_none() -> None:
|
|
687
|
+
payload = {
|
|
688
|
+
"tool_name": "Read",
|
|
689
|
+
"tool_input": {"command": "echo x > ~/.claude/verification/a.json"},
|
|
690
|
+
}
|
|
691
|
+
assert decision_for_payload(payload) is None
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def _hooks_manifest_path() -> pathlib.Path:
|
|
695
|
+
return _HOOK_DIR.parent / "hooks.json"
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def _pretooluse_commands_for_matcher(matcher_substring: str) -> list[str]:
|
|
699
|
+
manifest_record = json.loads(_hooks_manifest_path().read_text(encoding="utf-8"))
|
|
700
|
+
matching_commands: list[str] = []
|
|
701
|
+
for each_group in manifest_record["hooks"]["PreToolUse"]:
|
|
702
|
+
if matcher_substring not in each_group.get("matcher", ""):
|
|
703
|
+
continue
|
|
704
|
+
for each_hook in each_group.get("hooks", []):
|
|
705
|
+
matching_commands.append(each_hook.get("command", ""))
|
|
706
|
+
return matching_commands
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def test_guard_is_registered_on_bash() -> None:
|
|
710
|
+
assert any(
|
|
711
|
+
"verdict_directory_write_blocker.py" in each_command
|
|
712
|
+
for each_command in _pretooluse_commands_for_matcher("Bash")
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def test_guard_is_registered_on_powershell() -> None:
|
|
717
|
+
assert any(
|
|
718
|
+
"verdict_directory_write_blocker.py" in each_command
|
|
719
|
+
for each_command in _pretooluse_commands_for_matcher("PowerShell")
|
|
720
|
+
)
|