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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# SAMPLE.toml — Reference for all supported TOML command definition fields.
#
# This file is not loaded by safe-chains. It documents every field the
# registry understands, when to use each one, and how they compose.
# Copy the pattern that matches your command and fill in the data.
#
# CORE PRINCIPLE: This is an allowlist. Only what you list is allowed.
# Omitted flags, subcommands, and arguments are implicitly not allowed.
# Never describe what is blocked — just list what is permitted.
#
# FIELD DEFAULTS (omit when the default is what you want):
# level = "Inert" (alternatives: "SafeRead", "SafeWrite")
# bare = true (command can run with no arguments)
# positional_style = false (unknown flags are rejected; if true, they're
# treated as positional arguments)
#
# CHOOSING THE RIGHT PATTERN:
#
# Is the command a simple tool with flags and positional args?
# → Use "Flat command" (grep, cat, jq, bat, etc.)
#
# Does the command have subcommands (git log, cargo build, etc.)?
# → Use "Structured command with subcommands"
#
# Does a subcommand require a specific flag to be safe (cargo fmt --check)?
# → Use "guard" on the sub
#
# Does a subcommand have its own sub-subcommands (npm config get)?
# → Use nested [[command.sub.sub]]
#
# Does a subcommand delegate to an inner command (rustup run stable echo)?
# → Use "delegate_skip" or "delegate_after"
#
# Does a subcommand accept anything (git help)?
# → Use "allow_all = true"
#
# Does a subcommand only allow specific argument values (npm run test)?
# → Use "first_arg" with patterns
#
# Does a subcommand require one of several flags (conda config --show)?
# → Use "require_any"
#
# Does a flag promote the safety level (sk --history → SafeWrite)?
# → Use "write_flags"
#
# Does the command have global flags before subcommands (jj --no-pager log)?
# → Use "wrapper" on a structured command to strip them first
#
# Does the command wrap and delegate to an inner command (timeout, nice)?
# → Use "wrapper" with skip flags and positional_skip
#
# Does the command need Rust code for validation (curl, perl, fzf)?
# → Use "handler" to reference a named Rust function
#
# ─────────────────────────────────────────────────────────────────────
# FLAT COMMAND — simple tool with flags and positional args
# ─────────────────────────────────────────────────────────────────────
#
# Use for: grep, cat, jq, ls, bat, wc, and most CLI tools.
# A flat command has no subcommands — just flags and file arguments.
# [[command]]
# name = "grep"
# aliases = ["egrep", "fgrep"] # optional; registers additional names
# url = "https://www.gnu.org/software/grep/manual/grep.html"
# level = "Inert" # Inert | SafeRead | SafeWrite
# bare = false # false = requires at least one argument
# # max_positional = 2 # optional; limits positional arg count
# # positional_style = true # optional; treats unknown -flags as args
# # numeric_dash = true # optional; accepts -NUMBER shorthand
# # (e.g. head -20 ≡ head -n 20).
# # Only for commands with POSIX -number legacy syntax.
# standalone = [ # flags that take no value
# "--count", "--help", "--recursive", "--version",
# "-c", "-h", "-i", "-r", "-V",
# ]
# valued = [ # flags that consume the next token as value
# "--after-context", "--max-count", # also accepts --max-count=5 syntax
# "-A", "-m",
# ]
# ─────────────────────────────────────────────────────────────────────
# STRUCTURED COMMAND — has subcommands
# ─────────────────────────────────────────────────────────────────────
#
# Use for: cargo, git, docker, npm, brew, etc.
# bare_flags are accepted when the command is invoked alone (e.g. cargo --help).
# Each [[command.sub]] defines one allowed subcommand.
# [[command]]
# name = "cargo"
# url = "https://doc.rust-lang.org/cargo/commands/"
# bare_flags = ["--help", "--version", "-V", "-h"] # allowed with just the flag
#
# [[command.sub]]
# name = "test"
# level = "SafeRead"
# standalone = ["--release", "--no-fail-fast", "-h"]
# valued = ["--jobs", "--package", "-j", "-p"]
#
# [[command.sub]]
# name = "build"
# level = "SafeWrite"
# standalone = ["--release", "-h"]
# valued = ["--jobs", "--target", "-j"]
# ─────────────────────────────────────────────────────────────────────
# GUARDED SUBCOMMAND — requires a specific flag to be present
# ─────────────────────────────────────────────────────────────────────
#
# Use when a subcommand is only safe with a particular flag.
# Example: "cargo fmt" is a write operation, but "cargo fmt --check" is read-only.
# Without the guard flag, the subcommand is not allowed.
#
# Internally, guard is a shorthand for require_any with a single flag.
# The difference: guard also accepts bare "help" as a positional argument
# (e.g. "cargo fmt help"), while require_any only accepts --help/-h.
# Use guard when there is exactly one required flag; use require_any
# when multiple flags are acceptable alternatives.
# [[command.sub]]
# name = "fmt"
# guard = "--check" # long flag that must be present
# # guard_short = "-c" # optional short form of the guard
# level = "Inert"
# bare = false # must have at least the guard flag
# standalone = ["--all", "--check", "-h"]
# valued = ["--package", "-p"]
# ─────────────────────────────────────────────────────────────────────
# NESTED SUBCOMMANDS — subcommand has its own subcommands
# ─────────────────────────────────────────────────────────────────────
#
# Use for: npm config get, docker compose ps, mise config ls, etc.
# The parent sub has no flags of its own — it just groups child subs.
# Bare invocation of the parent (e.g. "npm config") is rejected by default.
#
# Set nested_bare = true if bare invocation and --help/-h should be
# accepted. Use this for commands like "mise settings" that show output
# when invoked without a subcommand. This also allows --help/-h on the
# parent without routing to a child sub.
#
# If the parent sub accepts flags that can appear before the child sub
# (e.g. "git config --local --list"), add standalone/valued on the parent.
# These are skipped when locating the child sub name.
# [[command.sub]]
# name = "config"
# # nested_bare = true # set if "tool config" alone is safe
# # standalone = ["--local", "--global"] # flags allowed before the child sub
# # valued = ["--file", "-f"] # valued flags allowed before the child sub
#
# [[command.sub.sub]]
# name = "get"
# standalone = ["--help", "--json", "-h"]
#
# [[command.sub.sub]]
# name = "list"
# standalone = ["--help", "--json", "-h"]
# ─────────────────────────────────────────────────────────────────────
# FIRST ARG FILTER — restrict which argument values are accepted
# ─────────────────────────────────────────────────────────────────────
#
# Use when a subcommand accepts an argument but only specific values are
# safe. The first positional argument after the subcommand name must
# match one of the patterns. Supports trailing * for prefix matching.
# Example: "npm run test" and "npm run test:unit" are safe, but
# "npm run build" is not.
# --help/-h is always allowed without matching.
# [[command.sub]]
# name = "run"
# first_arg = ["test", "test:*"] # "test" exact or "test:..." prefix
# level = "SafeRead"
# ─────────────────────────────────────────────────────────────────────
# REQUIRE ANY — subcommand needs at least one of several flags
# ─────────────────────────────────────────────────────────────────────
#
# Use when a subcommand is only safe if at least one of several specific
# flags is present. Internally the same dispatch as guard, but accepts
# multiple alternatives. Use guard when there is exactly one required
# flag; use require_any when several flags are acceptable.
# Example: "conda config" is only safe with --show or --show-sources.
# Without one of those, it could be used for --set or --add operations.
# --help/-h is always allowed without the required flag.
# [[command.sub]]
# name = "config"
# bare = false
# require_any = ["--show", "--show-sources"]
# standalone = ["--help", "--json", "--show", "--show-sources", "-h"]
# valued = ["--file", "--name", "-f", "-n"]
# ─────────────────────────────────────────────────────────────────────
# ALLOW ALL — subcommand that accepts anything
# ─────────────────────────────────────────────────────────────────────
#
# Use for: help subcommands that accept any topic as an argument.
# Internally equivalent to a policy with positional_style = true and
# no flags listed, but allow_all is clearer about intent.
# [[command.sub]]
# name = "help"
# allow_all = true
# level = "Inert"
# ─────────────────────────────────────────────────────────────────────
# WRITE-FLAGGED — specific flags promote the safety level
# ─────────────────────────────────────────────────────────────────────
#
# Use when a command is normally Inert but certain flags cause writes.
# Example: sk (skim) is Inert, but --history writes to a file → SafeWrite.
# The base level applies when none of the write_flags are present.
# [[command.sub]]
# name = "run"
# write_flags = ["--history"] # if any of these appear → SafeWrite
# # level = "Inert" # base level when write_flags absent
# standalone = ["--help", "-h"]
# valued = ["--history", "--query", "-q"]
# ─────────────────────────────────────────────────────────────────────
# DELEGATION — subcommand wraps an inner command
# ─────────────────────────────────────────────────────────────────────
#
# delegate_skip: Skip N tokens after the subcommand name, then validate
# the rest as a complete command. Used for "rustup run stable echo hello"
# where skip=2 skips the toolchain name.
#
# delegate_after: Find a separator token (usually "--"), then validate
# everything after it as a complete command. Used for
# "mise exec -- git status" where the separator is "--".
# [[command.sub]]
# name = "run"
# delegate_skip = 2 # skip 2 tokens, validate the rest
#
# [[command.sub]]
# name = "exec"
# delegate_after = "--" # validate everything after "--"
# ─────────────────────────────────────────────────────────────────────
# STRUCTURED + WRAPPER — global flags stripped before sub dispatch
# ─────────────────────────────────────────────────────────────────────
#
# Use for: commands with global flags that appear before the subcommand.
# Example: "jj --no-pager log" or "xcrun --sdk macosx --find clang".
# The wrapper flags are stripped from the front, then the remaining
# tokens are dispatched to subcommands as normal.
# This reuses the wrapper concept on a structured command.
# [[command]]
# name = "jj"
# bare_flags = ["--help", "--version", "-h"]
# [command.wrapper]
# standalone = ["--no-pager", "--quiet", "--verbose"]
# valued = ["--color", "--repository", "-R"]
#
# [[command.sub]]
# name = "log"
# standalone = ["--help", "-h"]
#
# [[command.sub]]
# name = "diff"
# standalone = ["--help", "-h"]
# ─────────────────────────────────────────────────────────────────────
# WRAPPER — command that wraps an inner command
# ─────────────────────────────────────────────────────────────────────
#
# Use for: timeout, time, nice, ionice, dotenv, and similar commands
# that accept their own flags, optionally skip positional args (like a
# duration or priority), then delegate everything remaining as a
# complete inner command to be validated recursively.
#
# Fields:
# standalone — wrapper's own flags that take no value
# valued — wrapper's own flags that consume the next token
# positional_skip — number of positional args to skip before the
# inner command (e.g., 1 for timeout's duration)
# separator — stop scanning flags at this token (e.g., "--")
# bare_ok — if true, running the wrapper with no inner command
# is allowed (e.g., "env" alone lists variables)
# [[command]]
# name = "timeout"
# url = "https://www.gnu.org/software/coreutils/..."
# [command.wrapper]
# standalone = ["--preserve-status"]
# valued = ["--signal", "--kill-after", "-s", "-k"]
# positional_skip = 1 # skip the duration argument
#
# [[command]]
# name = "dotenv"
# url = "https://github.com/bkeepers/dotenv"
# [command.wrapper]
# valued = ["-c", "-e", "-f", "-v"]
# separator = "--" # stop flag scanning at "--"
# ─────────────────────────────────────────────────────────────────────
# CUSTOM HANDLER — requires Rust code for validation
# ─────────────────────────────────────────────────────────────────────
#
# Use as a last resort when the command needs logic that can't be
# expressed with the fields above. The handler name references a Rust
# function registered in custom_cmd_handlers() or custom_sub_handlers()
# in src/handlers/mod.rs. The function receives the token slice and
# returns a Verdict.
#
# Works at both levels:
# - Command level: the entire command uses a Rust handler
# - Sub level: one subcommand uses a Rust handler while siblings
# use declarative TOML fields
#
# Before reaching for this: can you express the rule as a guard, a
# nested sub, a delegation, or simply by listing the right flags?
# Most commands that seem to need custom code can be modeled with the
# declarative fields above.
# Command-level handler (entire command validated by Rust):
# [[command]]
# name = "curl"
# handler = "curl" # references Rust handler by name
# url = "https://curl.se/docs/manpage.html"
# Sub-level handler (one sub uses Rust, siblings use TOML):
# [[command.sub]]
# name = "exec"
# handler = "bundle_exec" # references Rust handler by name
# ─────────────────────────────────────────────────────────────────────
# SAFETY LEVELS
# ─────────────────────────────────────────────────────────────────────
#
# "Inert" — No side effects. Read-only or purely informational.
# Examples: ls, cat, grep, git log, cargo tree
#
# "SafeRead" — Runs code but doesn't produce build artifacts.
# Examples: cargo test, npm test, go vet, linters
#
# "SafeWrite" — Produces artifacts or modifies the working tree.
# Examples: cargo build, go build, cargo doc
#
# When commands are piped or chained, the highest level wins.
# Unlisted commands are always denied regardless of level.