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
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Jonathan Shook
//! The command allow-gates behind the dispatching tools.
//!
//! `ct-test` and `ct-each` can run another program, so each runs **only**
//! commands on a fixed, compiled-in list. The lists are intentionally **static
//! and immutable**: nothing a caller does at run time can extend them, so an
//! agent driving these tools cannot grant itself new commands. A command that
//! is not on the relevant list is refused, and nothing runs. There is no shell
//! mode anywhere in the suite — every dispatch is a direct argv launch.
//!
//! * `ct-test` gates on [`BUILTIN`]: read-only commands only.
//! * `ct-each` gates through [`is_allowed_for_each`]: [`BUILTIN`] plus
//! `ct-test` (itself gated, so still read-only), and — only behind an
//! explicit `--mutating` flag — the suite's own [`MUTATING_SUITE`] tools,
//! which carry their own `--expect`/`--dry-run` safety gates.
//!
//! Gating is by **program name** (the file-name component of the command). It
//! is a guard against unintended side effects, not a sandbox: it does not
//! inspect arguments or resolve which binary a name ultimately runs.
use Path;
/// Commands trusted as read-only — `ct-test`'s entire, fixed allowlist.
///
/// Deliberately small and conservative: names whose ordinary use has no side
/// effects. (`find` is excluded: `-delete`/`-exec` make it not read-only; the
/// umbrella `ct` and the dispatching/mutating `ct-test`/`ct-each`/`ct-edit`/
/// `ct-patch`/`ct-rules`/`ct-await` are excluded because they can change
/// state or dispatch — the read-only `ct-search`, `ct-outline`, `ct-tree`,
/// `ct-view`, `ct-check`, and `ct-deps` (whose `cargo metadata` source is
/// forced `--locked --offline`) are included.) There is no run-time
/// mechanism to add to this list.
pub const BUILTIN: & = &;
/// The suite's mutating tools, runnable by `ct-each` only behind its explicit
/// `--mutating` flag. Each carries its own `--expect`/`--dry-run` gates, so a
/// dispatched edit still has to assert its own effect before writing.
pub const MUTATING_SUITE: & = &;
/// The program name the gates check for a command: its file-name component,
/// so `ls`, `/bin/ls`, and `./ls` all gate on `ls`.
///
/// # Examples
///
/// ```
/// use coding_tools::allowlist::gated_name;
///
/// assert_eq!(gated_name("/bin/ls"), "ls");
/// assert_eq!(gated_name("./parse"), "parse");
/// ```
/// Whether `name` is on `ct-test`'s fixed read-only allowlist.
///
/// # Examples
///
/// ```
/// use coding_tools::allowlist::is_allowed;
///
/// assert!(is_allowed("grep")); // a built-in read-only command
/// assert!(is_allowed("ct-search")); // the suite's own read-only tools
/// assert!(!is_allowed("rm")); // not read-only, never runnable
/// assert!(!is_allowed("sh")); // no shell, ever
/// ```
/// Whether `name` is a permitted `ct-each` dispatch target.
///
/// The base set is [`BUILTIN`] plus `ct-test` (which only runs read-only
/// commands itself, so dispatching it stays read-only). With `mutating`, the
/// suite's [`MUTATING_SUITE`] tools are also permitted — and nothing else:
/// arbitrary mutating commands are never runnable.
///
/// # Examples
///
/// ```
/// use coding_tools::allowlist::is_allowed_for_each;
///
/// assert!(is_allowed_for_each("ct-view", false));
/// assert!(is_allowed_for_each("ct-test", false)); // itself gated read-only
/// assert!(!is_allowed_for_each("ct-edit", false)); // needs --mutating
/// assert!(is_allowed_for_each("ct-edit", true));
/// assert!(!is_allowed_for_each("rm", true)); // never, even with --mutating
/// assert!(!is_allowed_for_each("sh", true)); // no shell, ever
/// ```