@lannguyensi/harness 0.53.0 → 0.55.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +70 -1
  2. package/dist/cli/delegate/index.d.ts +15 -2
  3. package/dist/cli/delegate/index.js +128 -4
  4. package/dist/cli/delegate/index.js.map +1 -1
  5. package/dist/cli/doctor/bypass-without-auto-approve.js +10 -4
  6. package/dist/cli/doctor/bypass-without-auto-approve.js.map +1 -1
  7. package/dist/cli/dry-run.js +32 -1
  8. package/dist/cli/dry-run.js.map +1 -1
  9. package/dist/cli/index.js +13 -5
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/cli/init/profiles.js +89 -8
  12. package/dist/cli/init/profiles.js.map +1 -1
  13. package/dist/cli/init/templates.js +163 -3
  14. package/dist/cli/init/templates.js.map +1 -1
  15. package/dist/cli/pack/hook-pre-tool-use.js +217 -95
  16. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  17. package/dist/cli/record/index.d.ts +8 -0
  18. package/dist/cli/record/index.js +55 -7
  19. package/dist/cli/record/index.js.map +1 -1
  20. package/dist/cli/validate/checks.js +61 -9
  21. package/dist/cli/validate/checks.js.map +1 -1
  22. package/dist/io/extract.d.ts +29 -0
  23. package/dist/io/extract.js +36 -0
  24. package/dist/io/extract.js.map +1 -1
  25. package/dist/io/read-regular-file.d.ts +33 -2
  26. package/dist/io/read-regular-file.js +41 -8
  27. package/dist/io/read-regular-file.js.map +1 -1
  28. package/dist/policies/index.d.ts +1 -1
  29. package/dist/policies/index.js +1 -1
  30. package/dist/policies/index.js.map +1 -1
  31. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +71 -4
  32. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +75 -4
  33. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +1 -1
  34. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +1 -1
  35. package/dist/policy-packs/builtin/understanding-before-execution/index.js +1 -1
  36. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -1
  37. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js +9 -3
  38. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js.map +1 -1
  39. package/dist/runtime/destructive-shell-floor.d.ts +19 -0
  40. package/dist/runtime/destructive-shell-floor.js +597 -0
  41. package/dist/runtime/destructive-shell-floor.js.map +1 -0
  42. package/dist/runtime/intercept.d.ts +10 -7
  43. package/dist/runtime/intercept.js +108 -9
  44. package/dist/runtime/intercept.js.map +1 -1
  45. package/dist/runtime/read-only-bash.d.ts +54 -0
  46. package/dist/runtime/read-only-bash.js +775 -7
  47. package/dist/runtime/read-only-bash.js.map +1 -1
  48. package/dist/runtime/risk-classifier.js +72 -13
  49. package/dist/runtime/risk-classifier.js.map +1 -1
  50. package/dist/runtime/when-eval.d.ts +18 -5
  51. package/dist/runtime/when-eval.js +64 -11
  52. package/dist/runtime/when-eval.js.map +1 -1
  53. package/dist/runtime/workflow-policies.d.ts +43 -1
  54. package/dist/runtime/workflow-policies.js +158 -1
  55. package/dist/runtime/workflow-policies.js.map +1 -1
  56. package/dist/schema/extract.d.ts +4 -0
  57. package/dist/schema/extract.js +64 -1
  58. package/dist/schema/extract.js.map +1 -1
  59. package/dist/schema/index.d.ts +13 -0
  60. package/dist/schema/policies.d.ts +19 -0
  61. package/dist/schema/policies.js +17 -1
  62. package/dist/schema/policies.js.map +1 -1
  63. package/package.json +1 -1
@@ -0,0 +1,597 @@
1
+ // Built-in destructive-shell floor for the Risk Classifier (task 2929c5b7).
2
+ //
3
+ // WHY THIS IS CODE AND NOT ONLY A MANIFEST PATTERN. `when-eval.ts` no
4
+ // longer lets an UNCLASSIFIED action satisfy `risk.severity_at_least:
5
+ // critical` on its own. That change ships in the binary and takes effect
6
+ // for every install on upgrade. Its compensating half, explicit
7
+ // classification for the mutating heads that used to ride the blanket
8
+ // fail-close (`dd`, `truncate`, `shred`, `mkfs`, `find -delete`, ...),
9
+ // would take effect only for installs that ADOPT the new `dangerous-shell`
10
+ // template patterns, because `harness init` writes the manifest once and
11
+ // never rewrites it. An existing manifest carrying the original four
12
+ // patterns would have kept the loosened fallback and lost the hard block
13
+ // in the same upgrade. So the recognition ships HERE, composed into
14
+ // `classifyRisk` like the existing built-in floors, and the template
15
+ // patterns are its operator-visible, operator-editable mirror.
16
+ //
17
+ // FLOOR SEMANTICS, same as every other built-in floor: it composes under
18
+ // highest-severity-wins. It can only RAISE an action's severity, never
19
+ // lower one an operator classifier already assigned, and an operator
20
+ // pattern can always raise above it.
21
+ //
22
+ // SCOPE, deliberately narrow. This is not a general "is this command
23
+ // dangerous" oracle and must not grow into one: it recognises a closed set
24
+ // of heads whose DESTRUCTIVE INVOCATION SHAPE is decidable from argv
25
+ // tokens alone. Anything else stays unclassified and rides the "high" rung
26
+ // fallback (approval-gated), which is the honest answer. A truncating
27
+ // redirection (`cmd > file`) is NOT recognised here for the same reason it
28
+ // is not a template pattern: this module has no redirection model, and a
29
+ // text heuristic for a bare `>` over-matches `2>&1`, heredocs, and
30
+ // comparison operators. See docs/risk-gate.md.
31
+ //
32
+ // TWO KNOWN OVER-BLOCKS, both in the raise-severity direction and both
33
+ // accepted. (1) The boundary split is not quote-aware, so a floored head
34
+ // name inside a torn-open quoted string is examined as if it were a real
35
+ // invocation. (2) `shred` and `mkfs` are matched on their HEAD TOKEN
36
+ // ALONE, with no argument shape to check, so any such fragment starting
37
+ // with one of them classifies `critical`. The single exception carved out
38
+ // of (2) is an invocation whose only argument is a help/version flag
39
+ // (`isLoneVersionOrHelp`), which cannot destroy anything.
40
+ //
41
+ // HEAD-RESOLUTION SPELLINGS THIS FLOOR DOES NOT REACH, by design and
42
+ // measured: a brace group (`{ dd of=y; }`), a backtick substitution
43
+ // (`` `dd of=y` ``), `eval "dd of=y"`, an `-exec sh -c '<cmd>'` payload
44
+ // (only a direct `-exec rm` is recognised), and `git -c k=v push --force`
45
+ // (`-c` is deliberately absent from the git global-flag skip list because
46
+ // config injection can execute code, so the subcommand walk stops there).
47
+ // Each stays UNCLASSIFIED, which is approval-gated rather than silently
48
+ // allowed, and each is pinned as such in
49
+ // tests/runtime/destructive-shell-floor.test.ts so a future change to the
50
+ // split or the wrapper peel cannot flip one without a test noticing.
51
+ // Closing them would mean a real shell parser; see docs/risk-gate.md's
52
+ // "Not recognized, by design".
53
+ import { GIT_GLOBAL_NO_VALUE_FLAGS, GIT_GLOBAL_VALUE_TAKING_FLAGS, GIT_TOKEN_RE, peelWrapperPrefixes, } from "./command-normalize.js";
54
+ import { decodeShellWord } from "./shell-word.js";
55
+ // Shell boundary characters. The command text is split on these before any
56
+ // head resolution, so a destructive invocation after `;`, `&&`, `||`, `|`,
57
+ // `&`, a subshell paren, or a newline is still examined. Splitting is
58
+ // deliberately NOT quote-aware: over-splitting inside a quoted string only
59
+ // produces MORE candidate heads to examine, which is the safe direction
60
+ // for a floor that raises severity. Under-splitting is what would hide a
61
+ // destructive tail, and that cannot happen here.
62
+ const BOUNDARY_SPLIT_RE = /[;&|()\n]+/;
63
+ /** Multi-call binaries: `busybox dd if=... of=...` really runs `dd`. */
64
+ const MULTICALL_HEADS = new Set(["busybox", "toybox"]);
65
+ /** Shells whose `-c` argument is a nested command string. */
66
+ const SHELL_HEADS = new Set([
67
+ "sh", "bash", "zsh", "dash", "ksh", "ash",
68
+ ]);
69
+ /** Depth bound for `sh -c "sh -c ..."` nesting. */
70
+ const MAX_NESTING_DEPTH = 4;
71
+ /**
72
+ * Recognise every built-in destructive invocation in `command`.
73
+ *
74
+ * Pure and linear in the length of the command: whitespace tokenisation,
75
+ * set lookups, and a bounded wrapper peel. No regex with a backtracking
76
+ * risk runs over the command text (`BOUNDARY_SPLIT_RE` is a character
77
+ * class), so this may be called on the UNCAPPED command string, which it
78
+ * must be, so a destructive tail past the classifier's 16 KiB ReDoS cap
79
+ * cannot be laundered by a long benign head.
80
+ */
81
+ export function classifyDestructiveShellFloor(command) {
82
+ const hits = [];
83
+ scanCommand(command, 0, hits);
84
+ return dedupe(hits);
85
+ }
86
+ function dedupe(hits) {
87
+ const seen = new Set();
88
+ const out = [];
89
+ for (const hit of hits) {
90
+ if (seen.has(hit.reason))
91
+ continue;
92
+ seen.add(hit.reason);
93
+ out.push(hit);
94
+ }
95
+ return out;
96
+ }
97
+ function scanCommand(command, depth, hits) {
98
+ if (depth > MAX_NESTING_DEPTH)
99
+ return;
100
+ // WHOLE-STRING pass first, and only for the `<shell> -c "<command>"`
101
+ // shape. The boundary split below is not quote-aware, so it would tear
102
+ // `sh -c "dd if=x of=y; rm -rf /"` apart mid-quote and leave the nested
103
+ // `dd` unrecognisable. Resolving the shell head before splitting keeps
104
+ // the nested command intact for the recursive scan.
105
+ const wholeTokens = tokenize(command);
106
+ const wholeHead = resolveHead(wholeTokens);
107
+ if (wholeHead !== null && SHELL_HEADS.has(wholeHead.name)) {
108
+ scanShellDashC(wholeTokens.slice(wholeHead.idx + 1), depth, hits);
109
+ }
110
+ for (const piece of command.split(BOUNDARY_SPLIT_RE)) {
111
+ const tokens = tokenize(piece);
112
+ if (tokens.length === 0)
113
+ continue;
114
+ scanInvocation(tokens, depth, hits);
115
+ }
116
+ }
117
+ function tokenize(s) {
118
+ return s.trim().split(/\s+/).filter((t) => t !== "");
119
+ }
120
+ /**
121
+ * Resolve the real head of one invocation: peel `VAR=x` assignments and
122
+ * the `sudo`/`env`/`command`/`timeout`/... wrapper chain, then any
123
+ * `busybox`/`toybox` multi-call prefix. Returns the head's literal name
124
+ * (decoded, path prefix stripped) and its token index, or `null` when no
125
+ * head token survives.
126
+ */
127
+ function resolveHead(rawTokens) {
128
+ // Basename + decode every token that is not a flag and not a `NAME=value`
129
+ // assignment, so `/bin/dd`, `/usr/bin/env`, and `"sudo"` all resolve like
130
+ // their bare spellings. Assignments and `of=`-style operands keep their
131
+ // text (their `=` is load-bearing), and flags keep theirs (a flag has no
132
+ // path prefix to strip).
133
+ const peelTokens = rawTokens.map((t) => ({ text: headNameOf(t) }));
134
+ let idx = 0;
135
+ for (let guard = 0; guard <= MAX_NESTING_DEPTH; guard += 1) {
136
+ const before = idx;
137
+ // Shared wrapper vocabulary (`peelWrapperPrefixes`, command-normalize.ts)
138
+ // rather than a second hand-rolled copy of it: a wrapper added there
139
+ // benefits this floor automatically.
140
+ idx = peelWrapperPrefixes(peelTokens, idx).idx;
141
+ const peeled = peelTokens[idx]?.text;
142
+ if (peeled !== undefined && MULTICALL_HEADS.has(peeled)) {
143
+ idx += 1;
144
+ continue;
145
+ }
146
+ if (idx === before)
147
+ break;
148
+ }
149
+ const name = peelTokens[idx]?.text;
150
+ return name === undefined ? null : { name, idx };
151
+ }
152
+ /** Resolve one boundary-delimited invocation's head, then dispatch on it. */
153
+ function scanInvocation(rawTokens, depth, hits) {
154
+ const resolved = resolveHead(rawTokens);
155
+ if (resolved === null)
156
+ return;
157
+ const head = resolved.name;
158
+ const args = rawTokens.slice(resolved.idx + 1);
159
+ if (SHELL_HEADS.has(head)) {
160
+ scanShellDashC(args, depth, hits);
161
+ return;
162
+ }
163
+ if (head === "dd")
164
+ return scanDd(args, hits);
165
+ if (head === "truncate")
166
+ return scanTruncate(args, hits);
167
+ if (head === "shred") {
168
+ if (isLoneVersionOrHelp(args))
169
+ return;
170
+ hits.push({
171
+ severity: "critical",
172
+ categories: ["destructive", "data_loss", "irreversible_action"],
173
+ reason: "built-in destructive floor: shred overwrites a file in place",
174
+ });
175
+ return;
176
+ }
177
+ if (head === "mkfs" || head.startsWith("mkfs.")) {
178
+ if (isLoneVersionOrHelp(args))
179
+ return;
180
+ hits.push({
181
+ severity: "critical",
182
+ categories: ["destructive", "data_loss", "infrastructure_change"],
183
+ reason: "built-in destructive floor: mkfs formats a filesystem",
184
+ });
185
+ return;
186
+ }
187
+ if (head === "find")
188
+ return scanFind(args, hits);
189
+ if (GIT_TOKEN_RE.test(head))
190
+ return scanGit(args, hits);
191
+ if (head === "chmod" || head === "chown")
192
+ return scanChmodChown(head, args, hits);
193
+ if (head === "sed")
194
+ return scanSed(args, hits);
195
+ if (head === "curl")
196
+ return scanCurl(args, hits);
197
+ }
198
+ /**
199
+ * `sh -c "<command>"` / `bash -lc '<command>'`: the nested command string
200
+ * is re-scanned from the top. The `-c` value is reconstructed by rejoining
201
+ * the tokens after the flag run (the whitespace tokenizer split the quoted
202
+ * string apart) and stripping one layer of surrounding quotes.
203
+ */
204
+ function scanShellDashC(args, depth, hits) {
205
+ let i = 0;
206
+ let sawDashC = false;
207
+ for (; i < args.length; i += 1) {
208
+ const t = decodeShellWord(args[i]);
209
+ if (!t.startsWith("-"))
210
+ break;
211
+ if (t === "--") {
212
+ i += 1;
213
+ break;
214
+ }
215
+ if (!t.startsWith("--") && t.includes("c")) {
216
+ sawDashC = true;
217
+ i += 1;
218
+ break;
219
+ }
220
+ if (t === "--command") {
221
+ sawDashC = true;
222
+ i += 1;
223
+ break;
224
+ }
225
+ }
226
+ if (!sawDashC)
227
+ return;
228
+ const nested = stripOuterQuotes(args.slice(i).join(" "));
229
+ if (nested === "")
230
+ return;
231
+ scanCommand(nested, depth + 1, hits);
232
+ }
233
+ function stripOuterQuotes(s) {
234
+ const t = s.trim();
235
+ if (t.length >= 2) {
236
+ const first = t[0];
237
+ const last = t[t.length - 1];
238
+ if ((first === "'" || first === '"') && first === last)
239
+ return t.slice(1, -1);
240
+ }
241
+ return t;
242
+ }
243
+ /**
244
+ * The literal name a head token invokes: decoded, with any path prefix
245
+ * stripped. `NAME=value` assignment tokens and flags are returned
246
+ * unchanged (stripping a `/` out of an assignment's VALUE would corrupt
247
+ * the token `peelWrapperPrefixes` needs to recognise, and a flag has no
248
+ * path prefix).
249
+ */
250
+ function headNameOf(token) {
251
+ const decoded = decodeShellWord(token);
252
+ if (decoded.startsWith("-"))
253
+ return decoded;
254
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(decoded))
255
+ return decoded;
256
+ const slash = decoded.lastIndexOf("/");
257
+ return slash === -1 ? decoded : decoded.slice(slash + 1);
258
+ }
259
+ /** `true` when `arg`, raw or decoded, satisfies `pred`. */
260
+ function eitherForm(arg, pred) {
261
+ return pred(arg) || pred(decodeShellWord(arg));
262
+ }
263
+ /** `true` when `t` is a single-dash short-flag cluster containing `ch`. */
264
+ function shortClusterHas(t, ch) {
265
+ return t.startsWith("-") && !t.startsWith("--") && t.slice(1).includes(ch);
266
+ }
267
+ /**
268
+ * Flags that mean "print and exit", never "act". Same set and same
269
+ * precedent as `VERSION_OR_HELP_FLAGS` in `read-only-bash.ts` (kept as a
270
+ * local copy rather than an import so this module stays free of that
271
+ * 2000-line predicate; the two sets are five literals and are pinned
272
+ * against each other in tests/runtime/destructive-shell-floor.test.ts).
273
+ */
274
+ const VERSION_OR_HELP_FLAGS = new Set([
275
+ "--version", "-V", "-v", "--help", "-h",
276
+ ]);
277
+ /**
278
+ * `true` for an invocation whose ONLY argument is a help/version flag.
279
+ *
280
+ * The argument-free heads below (`shred`, `mkfs`) are matched on the head
281
+ * token alone, because every real invocation of them destroys something.
282
+ * That over-blocks the one shape that destroys nothing: `shred --help`.
283
+ * The LONE requirement is what keeps this safe: `shred -v secret.txt` is
284
+ * a real shred with a verbose flag and keeps its `critical`, and a bare
285
+ * `shred -v` has no operand to destroy.
286
+ *
287
+ * NOTE, the same over-block in general: an argument-free head match fires
288
+ * on ANY boundary-split fragment whose first token resolves to that head,
289
+ * so a fragment like `echo mkfs` never reaches here (its head is `echo`)
290
+ * but `mkfs` inside a quoted string the non-quote-aware split tore open
291
+ * does. That is the deliberate direction for a floor that only RAISES
292
+ * severity, and is the same trade the split itself documents.
293
+ */
294
+ function isLoneVersionOrHelp(args) {
295
+ return args.length === 1 && VERSION_OR_HELP_FLAGS.has(decodeShellWord(args[0]));
296
+ }
297
+ // `dd` writes only when it is given an output file. `dd if=/dev/sda | gzip`
298
+ // (a read into a pipe) is deliberately NOT floored: it mutates nothing, and
299
+ // flooring it would hard-block a backup on a production-resolved session,
300
+ // the exact false positive this task exists to remove.
301
+ function scanDd(args, hits) {
302
+ if (!args.some((a) => eitherForm(a, (t) => t.startsWith("of="))))
303
+ return;
304
+ hits.push({
305
+ severity: "critical",
306
+ categories: ["destructive", "data_loss"],
307
+ reason: "built-in destructive floor: dd with an of= write target",
308
+ });
309
+ }
310
+ // `truncate -s <size>` / `--size=<size>`, including the glued `-s0` form
311
+ // and any cluster carrying `s` (`-cs 0`). Without a size there is nothing
312
+ // to truncate to.
313
+ function scanTruncate(args, hits) {
314
+ const sized = args.some((a) => eitherForm(a, (t) => t === "--size" || t.startsWith("--size=") || shortClusterHas(t, "s")));
315
+ if (!sized)
316
+ return;
317
+ hits.push({
318
+ severity: "critical",
319
+ categories: ["destructive", "data_loss"],
320
+ reason: "built-in destructive floor: truncate with a size argument",
321
+ });
322
+ }
323
+ // `find ... -delete` and `find ... -exec/-execdir rm`. Both spellings are
324
+ // compared raw OR decoded, the measured bypass class from task fdee7d0f
325
+ // (`-"delete"`, `-'delete'`, `-\delete` all reach find as `-delete`).
326
+ function scanFind(args, hits) {
327
+ for (let i = 0; i < args.length; i += 1) {
328
+ const a = args[i];
329
+ if (eitherForm(a, (t) => t === "-delete")) {
330
+ hits.push({
331
+ severity: "critical",
332
+ categories: ["destructive", "data_loss"],
333
+ reason: "built-in destructive floor: find -delete",
334
+ });
335
+ continue;
336
+ }
337
+ if (eitherForm(a, (t) => t === "-exec" || t === "-execdir")) {
338
+ const payload = args[i + 1];
339
+ if (payload !== undefined && headNameOf(payload) === "rm") {
340
+ hits.push({
341
+ severity: "critical",
342
+ categories: ["destructive", "data_loss"],
343
+ reason: "built-in destructive floor: find -exec rm",
344
+ });
345
+ }
346
+ }
347
+ }
348
+ }
349
+ /**
350
+ * git's own global options sit BETWEEN the binary and the subcommand
351
+ * (`git -C /repo push -f`), so they are skipped before the subcommand is
352
+ * read, reusing command-normalize.ts's exported flag sets. `-c` is
353
+ * deliberately absent from `GIT_GLOBAL_VALUE_TAKING_FLAGS` (config
354
+ * injection can execute code); a command carrying it stops the walk and
355
+ * simply yields no subcommand here.
356
+ */
357
+ function scanGit(args, hits) {
358
+ let i = 0;
359
+ while (i < args.length) {
360
+ const t = decodeShellWord(args[i]);
361
+ if (!t.startsWith("-"))
362
+ break;
363
+ if (GIT_GLOBAL_NO_VALUE_FLAGS.has(t)) {
364
+ i += 1;
365
+ continue;
366
+ }
367
+ const eq = t.indexOf("=");
368
+ const name = eq === -1 ? t : t.slice(0, eq);
369
+ if (GIT_GLOBAL_VALUE_TAKING_FLAGS.has(name)) {
370
+ i += eq === -1 ? 2 : 1;
371
+ continue;
372
+ }
373
+ break;
374
+ }
375
+ const sub = args[i] === undefined ? "" : decodeShellWord(args[i]);
376
+ const subArgs = args.slice(i + 1);
377
+ const push = (categories, reason) => {
378
+ hits.push({ severity: "high", categories, reason: `built-in destructive floor: ${reason}` });
379
+ };
380
+ if (sub === "reset" && subArgs.some((a) => eitherForm(a, (t) => t === "--hard"))) {
381
+ push(["destructive", "data_loss"], "git reset --hard");
382
+ return;
383
+ }
384
+ if (sub === "push") {
385
+ const forced = subArgs.some((a) => eitherForm(a, (t) => t === "--force" ||
386
+ t === "--force-with-lease" ||
387
+ t.startsWith("--force-with-lease=") ||
388
+ t.startsWith("+") ||
389
+ shortClusterHas(t, "f")));
390
+ if (forced)
391
+ push(["destructive", "production_mutation", "deployment_change"], "git push --force");
392
+ return;
393
+ }
394
+ if (sub === "clean") {
395
+ const forced = subArgs.some((a) => eitherForm(a, (t) => t === "--force" || shortClusterHas(t, "f")));
396
+ if (forced)
397
+ push(["destructive", "data_loss"], "git clean --force");
398
+ return;
399
+ }
400
+ if (sub === "checkout" || sub === "restore") {
401
+ const operands = subArgs.map((a) => decodeShellWord(a)).filter((t) => t !== "--");
402
+ if (operands.length === 1 && operands[0] === ".") {
403
+ push(["destructive", "data_loss"], `git ${sub} . discards every uncommitted change`);
404
+ }
405
+ }
406
+ }
407
+ // `chmod -R` / `chown -R` (and `--recursive`), cluster-aware so `-Rf` and
408
+ // `-fR` are both caught. Categories: `mass_update` only, deliberately NOT
409
+ // `destructive`/`data_loss`: a recursive mode or owner change rewrites
410
+ // metadata across a tree (worth a `high` gate) but is reversible by the
411
+ // inverse command, so the profile stays `reversible: true`.
412
+ function scanChmodChown(head, args, hits) {
413
+ const recursive = args.some((a) => eitherForm(a, (t) => t === "--recursive" || shortClusterHas(t, "R")));
414
+ if (!recursive)
415
+ return;
416
+ hits.push({
417
+ severity: "high",
418
+ categories: ["mass_update"],
419
+ reason: `built-in destructive floor: ${head} -R rewrites a whole tree`,
420
+ });
421
+ }
422
+ // `sed -i` in every spelling: `-i`, `-i.bak`, the cluster `-ni`,
423
+ // `--in-place`, `--in-place=SUFFIX`.
424
+ function scanSed(args, hits) {
425
+ const inPlace = args.some((a) => eitherForm(a, (t) => t === "--in-place" || t.startsWith("--in-place=") || shortClusterHas(t, "i")));
426
+ if (!inPlace)
427
+ return;
428
+ hits.push({
429
+ severity: "high",
430
+ categories: ["destructive", "data_loss"],
431
+ reason: "built-in destructive floor: sed in-place edit",
432
+ });
433
+ }
434
+ /** curl flags that write a LOCAL file. */
435
+ const CURL_LOCAL_WRITE_LONG_FLAGS = new Set([
436
+ "--output", "--remote-name", "--dump-header", "--cookie-jar", "--config",
437
+ "--create-dirs", "--output-dir", "--etag-save", "--trace", "--trace-ascii",
438
+ "--stderr", "--remote-header-name",
439
+ ]);
440
+ const CURL_LOCAL_WRITE_SHORT_CHARS = ["o", "O", "D", "c", "K"];
441
+ /** curl flags that send a REQUEST BODY (making the request a write). */
442
+ const CURL_BODY_LONG_FLAGS = new Set([
443
+ "--data", "--data-raw", "--data-binary", "--data-urlencode", "--data-ascii",
444
+ "--json", "--form", "--form-string", "--form-escape", "--upload-file",
445
+ ]);
446
+ const CURL_BODY_SHORT_CHARS = ["d", "F", "T"];
447
+ /**
448
+ * `-H`/`--header`: curl reads the VALUE from a local file when it starts
449
+ * with `@` (`-H @/etc/passwd` sends that file's lines as request
450
+ * headers).
451
+ */
452
+ const CURL_HEADER_FLAG = { long: "--header", short: "H" };
453
+ /**
454
+ * `-b`/`--cookie` uses a DIFFERENT rule than `-H`, measured on curl 8.7.1:
455
+ * a value containing no `=` is read as a cookie-jar FILENAME, `@`-prefixed
456
+ * or not (`curl -b jar.txt` reads the jar; `curl -b @jar.txt` opens a file
457
+ * literally named `@jar.txt`, the `@` is not a file marker for this
458
+ * flag). A value containing `=` is sent as inline cookie data
459
+ * (`-b 'name=value'`) and stays inert.
460
+ */
461
+ const CURL_COOKIE_FLAG = { long: "--cookie", short: "b" };
462
+ /**
463
+ * The value curl reads for one flag, whichever of the three ways it is
464
+ * spelled: `=`-joined (`--header=@f`), glued into a short-flag cluster
465
+ * (`-H@f`, `-sH@f`), or the next argv token (`-H @f`, `--header @f`).
466
+ * `undefined` when this token does not carry the flag at all.
467
+ *
468
+ * Short-flag lookup is a plain cluster search, so a value character that
469
+ * happens to sit inside ANOTHER flag's glued value can be misread as this
470
+ * flag. That direction only ever adds a candidate value to inspect, which
471
+ * for a floor that exclusively RAISES severity is the safe one.
472
+ */
473
+ function curlFlagValue(token, next, longName, shortChar) {
474
+ const nextValue = () => next === undefined ? undefined : decodeShellWord(next);
475
+ if (token.startsWith("--")) {
476
+ const eq = token.indexOf("=");
477
+ if ((eq === -1 ? token : token.slice(0, eq)) !== longName)
478
+ return undefined;
479
+ return eq === -1 ? nextValue() : token.slice(eq + 1);
480
+ }
481
+ if (!token.startsWith("-"))
482
+ return undefined;
483
+ const at = token.indexOf(shortChar, 1);
484
+ if (at === -1)
485
+ return undefined;
486
+ const glued = token.slice(at + 1);
487
+ return glued.length > 0 ? glued : nextValue();
488
+ }
489
+ /** `true` when `t` is `-w`/`--write-out` in any spelling. */
490
+ function isCurlWriteOutFlag(t) {
491
+ return t === "--write-out" || t.startsWith("--write-out=") || shortClusterHas(t, "w");
492
+ }
493
+ // What the read-only side no longer tries to decide. Decision D-013 removed
494
+ // the `curl` `low` floor entirely: a per-flag allowlist has to know every
495
+ // curl flag's write capability across curl versions, and two rounds of it
496
+ // leaked. This scan carries the opposite, tractable obligation: it names
497
+ // only capability it CAN name, and everything it does not name stays
498
+ // unclassified (approval-gated) rather than floored.
499
+ function scanCurl(args, hits) {
500
+ let localWrite = false;
501
+ let body = false;
502
+ let atFile = false;
503
+ // `-w`/`--write-out`'s format string writes a LOCAL FILE when it carries
504
+ // curl 8.3.0's `%output{FILE}` directive (verified on curl 8.7.1), which
505
+ // is the hole review round 3 found in the removed read-only floor: it
506
+ // allowlisted `-w` as inert. The format string is operator text that this
507
+ // module's whitespace tokenizer may have split across several tokens
508
+ // (`-w 'code=%{http_code} %output{/etc/x}'`), so `%output` is looked for
509
+ // in EVERY token once a write-out flag is present, not only in the one
510
+ // token that follows the flag. That over-matches a URL or an unrelated
511
+ // argument carrying the literal `%output`, which is the safe direction.
512
+ const decoded = args.map((a) => decodeShellWord(a));
513
+ if (decoded.some(isCurlWriteOutFlag) && decoded.some((t) => t.includes("%output"))) {
514
+ localWrite = true;
515
+ }
516
+ // `-H @FILE`: a LOCAL file read into the outgoing request. The `@` sits
517
+ // at the very start of the value, so the first token of the value is
518
+ // always the right place to look and the precise per-flag extraction is
519
+ // hole-free here.
520
+ //
521
+ // `-b`/`--cookie` is checked separately, by its own rule (see
522
+ // CURL_COOKIE_FLAG above): a value with no `=` is a filename curl reads,
523
+ // regardless of an `@` prefix.
524
+ for (let i = 0; i < args.length; i += 1) {
525
+ const headerValue = curlFlagValue(decoded[i], args[i + 1], CURL_HEADER_FLAG.long, CURL_HEADER_FLAG.short);
526
+ if (headerValue !== undefined && headerValue.startsWith("@"))
527
+ atFile = true;
528
+ const cookieValue = curlFlagValue(decoded[i], args[i + 1], CURL_COOKIE_FLAG.long, CURL_COOKIE_FLAG.short);
529
+ // An empty value (-b '') only switches curl's cookie engine on without
530
+ // opening a file; it is raised anyway, an accepted over-block in the safe
531
+ // direction.
532
+ if (cookieValue !== undefined && !cookieValue.includes("="))
533
+ atFile = true;
534
+ }
535
+ for (let i = 0; i < args.length; i += 1) {
536
+ const t = decodeShellWord(args[i]);
537
+ if (!t.startsWith("-") || t === "-" || t === "--")
538
+ continue;
539
+ const eq = t.indexOf("=");
540
+ const name = eq === -1 ? t : t.slice(0, eq);
541
+ if (name === "--request" || (t.startsWith("-X") && !t.startsWith("--"))) {
542
+ let value;
543
+ if (name === "--request") {
544
+ value = eq === -1 ? (args[i + 1] === undefined ? undefined : decodeShellWord(args[i + 1])) : t.slice(eq + 1);
545
+ }
546
+ else {
547
+ const glued = t.slice(2);
548
+ value = glued.length > 0 ? glued : args[i + 1] === undefined ? undefined : decodeShellWord(args[i + 1]);
549
+ }
550
+ const method = (value ?? "").toUpperCase();
551
+ if (method !== "" && method !== "GET" && method !== "HEAD")
552
+ body = true;
553
+ continue;
554
+ }
555
+ if (CURL_LOCAL_WRITE_LONG_FLAGS.has(name)) {
556
+ localWrite = true;
557
+ continue;
558
+ }
559
+ if (CURL_BODY_LONG_FLAGS.has(name)) {
560
+ body = true;
561
+ continue;
562
+ }
563
+ if (t.startsWith("--"))
564
+ continue;
565
+ if (CURL_LOCAL_WRITE_SHORT_CHARS.some((c) => shortClusterHas(t, c)))
566
+ localWrite = true;
567
+ if (CURL_BODY_SHORT_CHARS.some((c) => shortClusterHas(t, c)))
568
+ body = true;
569
+ }
570
+ if (localWrite) {
571
+ hits.push({
572
+ severity: "high",
573
+ categories: ["destructive", "data_loss"],
574
+ reason: "built-in destructive floor: curl writes a local file",
575
+ });
576
+ }
577
+ if (body) {
578
+ hits.push({
579
+ severity: "high",
580
+ categories: ["production_mutation", "network_exfiltration"],
581
+ reason: "built-in destructive floor: curl sends a request body or a non-GET/HEAD method",
582
+ });
583
+ }
584
+ if (atFile) {
585
+ // `network_exfiltration` alone, not the request-body pair: reading a
586
+ // local file into a header or a cookie ships data OUT, but it does not
587
+ // by itself mutate the remote resource, so `production_mutation` would
588
+ // be a claim this scan cannot make. Without an irreversible category
589
+ // the profile stays `reversible: true`, which is honest for a read.
590
+ hits.push({
591
+ severity: "high",
592
+ categories: ["network_exfiltration"],
593
+ reason: "built-in destructive floor: curl reads a local file into the request (@ value on -H, or a no-= value on -b)",
594
+ });
595
+ }
596
+ }
597
+ //# sourceMappingURL=destructive-shell-floor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"destructive-shell-floor.js","sourceRoot":"","sources":["../../src/runtime/destructive-shell-floor.ts"],"names":[],"mappings":"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"}
@@ -44,13 +44,16 @@ export interface PolicyDecision {
44
44
  */
45
45
  recordHint?: string;
46
46
  /**
47
- * True when the policy's `when:` block matched ONLY because the action
48
- * was unclassified (the "unknown is not safe" fail-close rule in
49
- * `when-eval.ts`). Absent when the policy has no `when:` block, when
50
- * the match was a genuine classification hit, or when `unclassifiedFallback`
51
- * was false. Present in the audit record and the non-ux block-time deny
52
- * message so an operator can distinguish a real critical-severity match
53
- * from a fail-closed unclassified command at a glance.
47
+ * True when the policy's `when:` block matched because the action was
48
+ * unclassified (the fail-close rule in `when-eval.ts`: "unknown is not
49
+ * safe" for `risk.category_in` / `action.reversible`, the "treated as
50
+ * high" rung for `risk.severity_at_least`). Absent when the policy has
51
+ * no `when:` block, when the match was a genuine classification hit,
52
+ * or when `unclassifiedFallback` was false. Present in the audit
53
+ * record, the non-ux block-time deny message, AND (task 2929c5b7) the
54
+ * ux-declared `cannot:` deny message, so an operator — or the agent
55
+ * reading its own deny — can distinguish a real critical-severity
56
+ * match from a fail-closed unclassified command at a glance.
54
57
  */
55
58
  whenUnclassifiedFallback?: boolean;
56
59
  evaluatedAt: string;