@sema-agent/core 7.10.0 → 7.11.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 (49) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/agents/child-model-seat.d.ts +45 -18
  3. package/dist/agents/child-model-seat.js +12 -8
  4. package/dist/agents/subagent.js +6 -5
  5. package/dist/agents/teacher.js +2 -2
  6. package/dist/core/auto-mode-defaults.d.ts +15 -3
  7. package/dist/core/auto-mode-defaults.js +1 -0
  8. package/dist/core/auto-mode.d.ts +24 -20
  9. package/dist/core/auto-mode.js +12 -12
  10. package/dist/core/gate-fold.js +1 -0
  11. package/dist/core/gate-lanes.d.ts +6 -1
  12. package/dist/core/gate-lanes.js +45 -18
  13. package/dist/core/hooks.d.ts +13 -0
  14. package/dist/core/permission-rule-model.d.ts +5 -3
  15. package/dist/core/permission-rule-model.js +7 -3
  16. package/dist/core/persisted-rule-arms.js +4 -3
  17. package/dist/core/read-only-shell-table.d.ts +87 -0
  18. package/dist/core/read-only-shell-table.js +485 -0
  19. package/dist/core/read-only-shell.d.ts +42 -0
  20. package/dist/core/read-only-shell.js +316 -0
  21. package/dist/core/roles.d.ts +3 -2
  22. package/dist/core/runner/gate-exit.d.ts +5 -0
  23. package/dist/core/runner/prepare-caps-and-workflow.js +22 -11
  24. package/dist/core/runner/prepare-gate-stations.js +9 -0
  25. package/dist/core/runner/prepare-task.js +1 -1
  26. package/dist/core/runner/prepare-turn-wiring.js +1 -1
  27. package/dist/core/shell-lexer.d.ts +18 -0
  28. package/dist/core/shell-lexer.js +17 -10
  29. package/dist/core/shell-wrapper-table.js +8 -5
  30. package/dist/core/tool-policy.d.ts +4 -1
  31. package/dist/core/tool-policy.js +1 -1
  32. package/dist/core/tools.d.ts +28 -7
  33. package/dist/core/tools.js +44 -4
  34. package/dist/core/trace.d.ts +15 -0
  35. package/dist/engine/harness/agent-harness.d.ts +3 -1
  36. package/dist/engine/harness/agent-harness.js +1 -1
  37. package/dist/engine/harness/types.d.ts +4 -2
  38. package/dist/index.d.ts +3 -1
  39. package/dist/index.js +2 -0
  40. package/dist/orchestration/run-workflow-tool.d.ts +5 -2
  41. package/dist/orchestration/run-workflow-tool.js +2 -1
  42. package/dist/orchestration/workflow-governance.d.ts +3 -2
  43. package/dist/orchestration/workflow-primitives.d.ts +4 -1
  44. package/dist/orchestration/workflow-primitives.js +1 -6
  45. package/dist/orchestration/workflow.d.ts +12 -4
  46. package/dist/orchestration/workflow.js +24 -7
  47. package/dist/prompt-assembly/turn-snapshot.d.ts +4 -2
  48. package/package.json +1 -1
  49. package/test/export-surface.snapshot.json +35 -1
@@ -0,0 +1,485 @@
1
+ export const READ_ONLY_FLAG_ARITIES = ["none", "number", "string", "char", "{}", "EOF"];
2
+ export const FLAG_VALUE_ACCEPTS = {
3
+ none: () => false,
4
+ number: (value) => /^\d+$/.test(value),
5
+ string: () => true,
6
+ char: (value) => value.length === 1,
7
+ "{}": (value) => value === "{}",
8
+ EOF: (value) => value === "EOF",
9
+ };
10
+ const GIT_REFS = { "--all": "none", "--branches": "none", "--tags": "none", "--remotes": "none" };
11
+ const GIT_DATES = { "--since": "string", "--after": "string", "--until": "string", "--before": "string" };
12
+ const GIT_LOG_VIEW = { "--oneline": "none", "--graph": "none", "--decorate": "none", "--no-decorate": "none", "--date": "string", "--relative-date": "none" };
13
+ const GIT_COUNT = { "--max-count": "number", "-n": "number" };
14
+ const GIT_STAT = { "--stat": "none", "--numstat": "none", "--shortstat": "none", "--name-only": "none", "--name-status": "none" };
15
+ const GIT_COLOR = { "--color": "none", "--no-color": "none" };
16
+ const GIT_PATCH = { "--patch": "none", "-p": "none", "--no-patch": "none", "--no-ext-diff": "none", "-s": "none" };
17
+ const GIT_AUTHOR = { "--author": "string", "--committer": "string", "--grep": "string" };
18
+ const SIGNATURE_FORMAT = /%[-+ ]?G|%\(\*?signature/;
19
+ const NAMED_FORMATS = new Set(["oneline", "short", "medium", "full", "fuller", "email", "raw"]);
20
+ const unknownNamedFormat = (value) => value !== "" && !value.includes("%") && !value.startsWith("format:") && !value.startsWith("tformat:") && !NAMED_FORMATS.has(value);
21
+ const formatOperandDanger = (args) => {
22
+ for (let i = 0; i < args.length; i++) {
23
+ const word = args[i];
24
+ if (SIGNATURE_FORMAT.test(word))
25
+ return true;
26
+ for (const flag of ["--format", "--pretty", "--sort"]) {
27
+ let value;
28
+ if (word === flag && i + 1 < args.length)
29
+ value = args[i + 1];
30
+ else if (word.startsWith(`${flag}=`))
31
+ value = word.slice(flag.length + 1);
32
+ if (value === undefined)
33
+ continue;
34
+ if (SIGNATURE_FORMAT.test(value) || value.includes("signature"))
35
+ return true;
36
+ if (flag !== "--sort" && unknownNamedFormat(value))
37
+ return true;
38
+ }
39
+ }
40
+ return false;
41
+ };
42
+ const ghOperandDanger = (args) => {
43
+ for (const word of args) {
44
+ if (word === "")
45
+ continue;
46
+ let operand = word;
47
+ if (word.startsWith("-")) {
48
+ const eq = word.indexOf("=");
49
+ if (eq === -1)
50
+ continue;
51
+ operand = word.slice(eq + 1);
52
+ if (operand === "")
53
+ continue;
54
+ }
55
+ if (!operand.includes("/") && !operand.includes("://") && !operand.includes("@"))
56
+ continue;
57
+ if (operand.includes("://") || operand.includes("@"))
58
+ return true;
59
+ if ((operand.match(/\//g) ?? []).length >= 2)
60
+ return true;
61
+ }
62
+ return false;
63
+ };
64
+ const DOCKER_RETARGET_FLAGS = ["-H", "-c", "-r", "--host", "--context", "--config", "--tlscacert", "--tlscert", "--tlskey", "--url", "--connection", "--identity", "--remote", "--module", "--out"];
65
+ const DOCKER_RETARGET_LETTERS = new Set(DOCKER_RETARGET_FLAGS.filter((f) => f.length === 2).map((f) => f[1]));
66
+ export const dockerRetargetDanger = (args) => args.some((word) => {
67
+ if (DOCKER_RETARGET_FLAGS.some((f) => word === f || word.startsWith(`${f}=`) || (f.length === 2 && word.length > 2 && word.startsWith(f))))
68
+ return true;
69
+ const cluster = /^-([A-Za-z]+)/.exec(word)?.[1];
70
+ if (cluster !== undefined && cluster.length >= 2)
71
+ for (const letter of cluster)
72
+ if (DOCKER_RETARGET_LETTERS.has(letter))
73
+ return true;
74
+ return false;
75
+ });
76
+ const before = (s, sep) => s.split(sep)[0];
77
+ const GREP_FLAGS = { "-e": "string", "--regexp": "string", "-f": "string", "--file": "string", "-F": "none", "--fixed-strings": "none", "-G": "none", "--basic-regexp": "none", "-E": "none", "--extended-regexp": "none", "-P": "none", "--perl-regexp": "none", "-i": "none", "--ignore-case": "none", "--no-ignore-case": "none", "-v": "none", "--invert-match": "none", "-w": "none", "--word-regexp": "none", "-x": "none", "--line-regexp": "none", "-c": "none", "--count": "none", "--color": "string", "--colour": "string", "-L": "none", "--files-without-match": "none", "-l": "none", "--files-with-matches": "none", "-m": "number", "--max-count": "number", "-o": "none", "--only-matching": "none", "-q": "none", "--quiet": "none", "--silent": "none", "-s": "none", "--no-messages": "none", "-b": "none", "--byte-offset": "none", "-H": "none", "--with-filename": "none", "-h": "none", "--no-filename": "none", "--label": "string", "-n": "none", "--line-number": "none", "-T": "none", "--initial-tab": "none", "-u": "none", "--unix-byte-offsets": "none", "-Z": "none", "--null": "none", "-z": "none", "--null-data": "none", "-A": "number", "--after-context": "number", "-B": "number", "--before-context": "number", "-C": "number", "--context": "number", "--group-separator": "string", "--no-group-separator": "none", "-a": "none", "--text": "none", "--binary-files": "string", "-I": "none", "-D": "string", "--devices": "string", "-d": "string", "--directories": "string", "--exclude": "string", "--exclude-from": "string", "--exclude-dir": "string", "--include": "string", "-r": "none", "--recursive": "none", "-R": "none", "--dereference-recursive": "none", "--line-buffered": "none", "-U": "none", "--binary": "none", "--help": "none", "-V": "none", "--version": "none" };
78
+ const FD_FLAGS = { "-h": "none", "--help": "none", "-V": "none", "--version": "none", "-H": "none", "--hidden": "none", "-I": "none", "--no-ignore": "none", "--no-ignore-vcs": "none", "--no-ignore-parent": "none", "-s": "none", "--case-sensitive": "none", "-i": "none", "--ignore-case": "none", "-g": "none", "--glob": "none", "--regex": "none", "-F": "none", "--fixed-strings": "none", "-a": "none", "--absolute-path": "none", "-L": "none", "--follow": "none", "-p": "none", "--full-path": "none", "-0": "none", "--print0": "none", "-d": "number", "--max-depth": "number", "--min-depth": "number", "--exact-depth": "number", "-t": "string", "--type": "string", "-e": "string", "--extension": "string", "-S": "string", "--size": "string", "--changed-within": "string", "--changed-before": "string", "-o": "string", "--owner": "string", "-E": "string", "--exclude": "string", "--ignore-file": "string", "-c": "string", "--color": "string", "-j": "number", "--threads": "number", "--max-buffer-time": "string", "--max-results": "number", "-1": "none", "-q": "none", "--quiet": "none", "--show-errors": "none", "--strip-cwd-prefix": "none", "--one-file-system": "none", "--prune": "none", "--search-path": "string", "--base-directory": "string", "--path-separator": "string", "--batch-size": "number", "--no-require-git": "none", "--hyperlink": "string", "--and": "string", "--format": "string" };
79
+ const CHECKSUM_FLAGS = { "-b": "none", "--binary": "none", "-t": "none", "--text": "none", "-c": "none", "--check": "none", "--ignore-missing": "none", "--quiet": "none", "--status": "none", "--strict": "none", "-w": "none", "--warn": "none", "--tag": "none", "-z": "none", "--zero": "none", "--help": "none", "--version": "none" };
80
+ const TEST_INTEGER_OPERATORS = new Set(["-eq", "-ne", "-lt", "-le", "-gt", "-ge"]);
81
+ const TEST_INTEGER = /^-?(0[xX][0-9a-fA-F]+|[0-9]+#[0-9a-zA-Z]+|[0-9]+)$/;
82
+ export const READ_ONLY_COMMAND_TABLE = {
83
+ xargs: { safeFlags: { "-I": "{}", "-n": "number", "-P": "number", "-L": "number", "-s": "number", "-E": "EOF", "-0": "none", "-t": "none", "-r": "none", "-x": "none", "-d": "char" } },
84
+ "git diff": {
85
+ safeFlags: { ...GIT_STAT, ...GIT_COLOR, "--dirstat": "none", "--summary": "none", "--patch-with-stat": "none", "--word-diff": "none", "--word-diff-regex": "string", "--color-words": "none", "--no-renames": "none", "--no-ext-diff": "none", "--check": "none", "--ws-error-highlight": "string", "--full-index": "none", "--binary": "none", "--abbrev": "number", "--break-rewrites": "none", "--find-renames": "none", "--find-copies": "none", "--find-copies-harder": "none", "--irreversible-delete": "none", "--diff-algorithm": "string", "--histogram": "none", "--patience": "none", "--minimal": "none", "--ignore-space-at-eol": "none", "--ignore-space-change": "none", "--ignore-all-space": "none", "--ignore-blank-lines": "none", "--inter-hunk-context": "number", "--function-context": "none", "--exit-code": "none", "--quiet": "none", "--cached": "none", "--staged": "none", "--pickaxe-regex": "none", "--pickaxe-all": "none", "--no-index": "none", "--relative": "string", "--diff-filter": "string", "-p": "none", "-u": "none", "-s": "none", "-M": "none", "-C": "none", "-B": "none", "-D": "none", "-l": "none", "-S": "string", "-G": "string", "-O": "string", "-R": "none" },
86
+ },
87
+ "git log": {
88
+ safeFlags: { ...GIT_LOG_VIEW, ...GIT_REFS, ...GIT_DATES, ...GIT_COUNT, ...GIT_STAT, ...GIT_COLOR, ...GIT_PATCH, ...GIT_AUTHOR, "--abbrev-commit": "none", "--full-history": "none", "--dense": "none", "--sparse": "none", "--simplify-merges": "none", "--ancestry-path": "none", "--source": "none", "--first-parent": "none", "--merges": "none", "--no-merges": "none", "--reverse": "none", "--walk-reflogs": "none", "--skip": "number", "--max-age": "number", "--min-age": "number", "--no-min-parents": "none", "--no-max-parents": "none", "--follow": "none", "--no-walk": "none", "--left-right": "none", "--cherry-mark": "none", "--cherry-pick": "none", "--boundary": "none", "--topo-order": "none", "--date-order": "none", "--author-date-order": "none", "--pretty": "string", "--format": "string", "--diff-filter": "string", "-S": "string", "-G": "string", "--pickaxe-regex": "none", "--pickaxe-all": "none" },
89
+ dangerous: formatOperandDanger,
90
+ },
91
+ "git show": {
92
+ safeFlags: { ...GIT_LOG_VIEW, ...GIT_STAT, ...GIT_COLOR, ...GIT_PATCH, "--abbrev-commit": "none", "--word-diff": "none", "--word-diff-regex": "string", "--color-words": "none", "--pretty": "string", "--format": "string", "--first-parent": "none", "--raw": "none", "--diff-filter": "string", "-m": "none", "--quiet": "none" },
93
+ dangerous: formatOperandDanger,
94
+ },
95
+ "git shortlog": {
96
+ safeFlags: { ...GIT_REFS, ...GIT_DATES, "-s": "none", "--summary": "none", "-n": "none", "--numbered": "none", "-e": "none", "--email": "none", "-c": "none", "--committer": "none", "--group": "string", "--format": "string", "--no-merges": "none", "--author": "string" },
97
+ dangerous: formatOperandDanger,
98
+ },
99
+ "git reflog": {
100
+ safeFlags: { ...GIT_LOG_VIEW, ...GIT_REFS, ...GIT_DATES, ...GIT_COUNT, ...GIT_AUTHOR },
101
+ dangerous: (args) => {
102
+ const reading = new Set(["show", "list"]);
103
+ const writing = new Set(["expire", "delete", "exists", "drop", "write"]);
104
+ const first = args[0];
105
+ if (first !== undefined && first !== "" && !first.startsWith("-") && !reading.has(first))
106
+ return true;
107
+ return args.some((w) => writing.has(w));
108
+ },
109
+ },
110
+ "git stash list": { safeFlags: { ...GIT_LOG_VIEW, ...GIT_REFS, ...GIT_COUNT } },
111
+ "git ls-remote": {
112
+ safeFlags: { "--branches": "none", "-b": "none", "--tags": "none", "-t": "none", "--heads": "none", "-h": "none", "--refs": "none", "--quiet": "none", "-q": "none", "--exit-code": "none", "--get-url": "none", "--symref": "none", "--sort": "string" },
113
+ dangerous: (args) => {
114
+ if (args.some((w) => w === "-o" || w === "--server-option" || w.startsWith("--server-option=")))
115
+ return true;
116
+ let pastDashes = false;
117
+ for (let i = 0; i < args.length; i++) {
118
+ const w = args[i];
119
+ if (w === "")
120
+ continue;
121
+ if (!pastDashes && w === "--") {
122
+ pastDashes = true;
123
+ continue;
124
+ }
125
+ if (!pastDashes && w.startsWith("-") && w !== "-") {
126
+ if (w === "--sort")
127
+ i++;
128
+ continue;
129
+ }
130
+ return true;
131
+ }
132
+ return formatOperandDanger(args);
133
+ },
134
+ },
135
+ "git status": { safeFlags: { "--short": "none", "-s": "none", "--branch": "none", "-b": "none", "--porcelain": "none", "--long": "none", "--verbose": "none", "-v": "none", "--untracked-files": "string", "-u": "string", "--ignored": "none", "--ignore-submodules": "string", "--column": "none", "--no-column": "none", "--ahead-behind": "none", "--no-ahead-behind": "none", "--renames": "none", "--no-renames": "none", "--find-renames": "string", "-M": "string" } },
136
+ "git blame": { safeFlags: { ...GIT_COLOR, "-L": "string", "--porcelain": "none", "-p": "none", "--line-porcelain": "none", "--incremental": "none", "--root": "none", "--show-stats": "none", "--show-name": "none", "--show-number": "none", "-n": "none", "--show-email": "none", "-e": "none", "-f": "none", "--date": "string", "-w": "none", "--ignore-rev": "string", "--ignore-revs-file": "string", "-M": "none", "-C": "none", "--score-debug": "none", "--abbrev": "number", "-s": "none", "-l": "none", "-t": "none" } },
137
+ "git ls-files": { safeFlags: { "--cached": "none", "-c": "none", "--deleted": "none", "-d": "none", "--modified": "none", "-m": "none", "--others": "none", "-o": "none", "--ignored": "none", "-i": "none", "--stage": "none", "-s": "none", "--killed": "none", "-k": "none", "--unmerged": "none", "-u": "none", "--directory": "none", "--no-empty-directory": "none", "--eol": "none", "--full-name": "none", "--abbrev": "number", "--debug": "none", "-z": "none", "-t": "none", "-v": "none", "-f": "none", "--exclude": "string", "-x": "string", "--exclude-from": "string", "-X": "string", "--exclude-per-directory": "string", "--exclude-standard": "none", "--error-unmatch": "none", "--recurse-submodules": "none" } },
138
+ "git config --get": { safeFlags: { "--local": "none", "--global": "none", "--system": "none", "--worktree": "none", "--default": "string", "--type": "string", "--bool": "none", "--int": "none", "--bool-or-int": "none", "--path": "none", "--expiry-date": "none", "-z": "none", "--null": "none", "--name-only": "none", "--show-origin": "none", "--show-scope": "none" } },
139
+ "git remote show": {
140
+ safeFlags: { "-n": "none" },
141
+ dangerous: (args) => {
142
+ const dd = args.indexOf("--");
143
+ const flags = dd === -1 ? args : args.slice(0, dd);
144
+ const operands = dd === -1 ? [] : args.slice(dd + 1);
145
+ const names = flags.filter((w) => w !== "-n").concat(operands);
146
+ if (names.length !== 1)
147
+ return true;
148
+ if (!flags.includes("-n"))
149
+ return true;
150
+ return !/^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/.test(names[0]);
151
+ },
152
+ },
153
+ "git remote": { safeFlags: { "-v": "none", "--verbose": "none" }, dangerous: (args) => args.some((w) => w !== "-v" && w !== "--verbose") },
154
+ "git merge-base": { safeFlags: { "--is-ancestor": "none", "--fork-point": "none", "--octopus": "none", "--independent": "none", "--all": "none" } },
155
+ "git rev-parse": { safeFlags: { "--verify": "none", "--short": "string", "--abbrev-ref": "none", "--symbolic": "none", "--symbolic-full-name": "none", "--show-toplevel": "none", "--show-cdup": "none", "--show-prefix": "none", "--git-dir": "none", "--git-common-dir": "none", "--absolute-git-dir": "none", "--show-superproject-working-tree": "none", "--is-inside-work-tree": "none", "--is-inside-git-dir": "none", "--is-bare-repository": "none", "--is-shallow-repository": "none", "--is-shallow-update": "none", "--path-prefix": "none" } },
156
+ "git rev-list": {
157
+ safeFlags: { ...GIT_REFS, ...GIT_DATES, ...GIT_COUNT, ...GIT_AUTHOR, "--count": "none", "--reverse": "none", "--first-parent": "none", "--ancestry-path": "none", "--merges": "none", "--no-merges": "none", "--min-parents": "number", "--max-parents": "number", "--no-min-parents": "none", "--no-max-parents": "none", "--skip": "number", "--max-age": "number", "--min-age": "number", "--walk-reflogs": "none", "--oneline": "none", "--abbrev-commit": "none", "--pretty": "string", "--format": "string", "--abbrev": "number", "--full-history": "none", "--dense": "none", "--sparse": "none", "--source": "none", "--graph": "none" },
158
+ dangerous: formatOperandDanger,
159
+ },
160
+ "git describe": { safeFlags: { "--tags": "none", "--match": "string", "--exclude": "string", "--long": "none", "--abbrev": "number", "--always": "none", "--contains": "none", "--first-match": "none", "--exact-match": "none", "--candidates": "number", "--dirty": "none", "--broken": "none" } },
161
+ "git cat-file": { safeFlags: { "-t": "none", "-s": "none", "-p": "none", "-e": "none", "--batch-check": "none", "--allow-undetermined-type": "none" } },
162
+ "git for-each-ref": {
163
+ safeFlags: { "--format": "string", "--sort": "string", "--count": "number", "--contains": "string", "--no-contains": "string", "--merged": "string", "--no-merged": "string", "--points-at": "string" },
164
+ dangerous: formatOperandDanger,
165
+ },
166
+ "git grep": { safeFlags: { "-e": "string", "-E": "none", "--extended-regexp": "none", "-G": "none", "--basic-regexp": "none", "-F": "none", "--fixed-strings": "none", "-P": "none", "--perl-regexp": "none", "-i": "none", "--ignore-case": "none", "-v": "none", "--invert-match": "none", "-w": "none", "--word-regexp": "none", "-n": "none", "--line-number": "none", "-c": "none", "--count": "none", "-l": "none", "--files-with-matches": "none", "-L": "none", "--files-without-match": "none", "-h": "none", "-H": "none", "--heading": "none", "--break": "none", "--full-name": "none", "--color": "none", "--no-color": "none", "-o": "none", "--only-matching": "none", "-A": "number", "--after-context": "number", "-B": "number", "--before-context": "number", "-C": "number", "--context": "number", "--and": "none", "--or": "none", "--not": "none", "--max-depth": "number", "--untracked": "none", "--no-index": "none", "--recurse-submodules": "none", "--cached": "none", "--threads": "number", "-q": "none", "--quiet": "none" } },
167
+ "git stash show": { safeFlags: { ...GIT_STAT, ...GIT_COLOR, ...GIT_PATCH, "--word-diff": "none", "--word-diff-regex": "string", "--diff-filter": "string", "--abbrev": "number" } },
168
+ "git worktree list": { safeFlags: { "--porcelain": "none", "-v": "none", "--verbose": "none", "--expire": "string" } },
169
+ "git tag": {
170
+ safeFlags: { "-l": "none", "--list": "none", "-n": "number", "--contains": "string", "--no-contains": "string", "--merged": "string", "--no-merged": "string", "--sort": "string", "--format": "string", "--points-at": "string", "--column": "none", "--no-column": "none", "-i": "none", "--ignore-case": "none" },
171
+ dangerous: (args) => {
172
+ if (formatOperandDanger(args))
173
+ return true;
174
+ const valued = new Set(["--contains", "--no-contains", "--merged", "--no-merged", "--points-at", "--sort", "--format", "-n"]);
175
+ let i = 0;
176
+ let listing = false;
177
+ let pastDashes = false;
178
+ while (i < args.length) {
179
+ const w = args[i];
180
+ if (w === "") {
181
+ i++;
182
+ continue;
183
+ }
184
+ if (w === "--" && !pastDashes) {
185
+ pastDashes = true;
186
+ i++;
187
+ continue;
188
+ }
189
+ if (!pastDashes && w.startsWith("-")) {
190
+ if (w === "--list" || w === "-l")
191
+ listing = true;
192
+ else if (w[0] === "-" && w[1] !== "-" && w.length > 2 && !w.includes("=") && w.slice(1).includes("l"))
193
+ listing = true;
194
+ if (w.includes("="))
195
+ i++;
196
+ else if (valued.has(w))
197
+ i += 2;
198
+ else
199
+ i++;
200
+ }
201
+ else {
202
+ if (!listing)
203
+ return true;
204
+ i++;
205
+ }
206
+ }
207
+ return false;
208
+ },
209
+ },
210
+ "git branch": {
211
+ safeFlags: { "-l": "none", "--list": "none", "-a": "none", "--all": "none", "-r": "none", "--remotes": "none", "-v": "none", "-vv": "none", "--verbose": "none", "--color": "none", "--no-color": "none", "--column": "none", "--no-column": "none", "--abbrev": "number", "--no-abbrev": "none", "--contains": "string", "--no-contains": "string", "--merged": "none", "--no-merged": "none", "--points-at": "string", "--sort": "string", "--show-current": "none", "-i": "none", "--ignore-case": "none" },
212
+ dangerous: (args) => {
213
+ if (formatOperandDanger(args))
214
+ return true;
215
+ const valued = new Set(["--contains", "--no-contains", "--points-at", "--sort"]);
216
+ const optionalOperand = new Set(["--merged", "--no-merged"]);
217
+ let i = 0;
218
+ let lastFlag = "";
219
+ let listing = false;
220
+ let pastDashes = false;
221
+ while (i < args.length) {
222
+ const w = args[i];
223
+ if (w === "") {
224
+ i++;
225
+ continue;
226
+ }
227
+ if (w === "--" && !pastDashes) {
228
+ pastDashes = true;
229
+ lastFlag = "";
230
+ i++;
231
+ continue;
232
+ }
233
+ if (!pastDashes && w.startsWith("-")) {
234
+ if (w === "--list" || w === "-l")
235
+ listing = true;
236
+ else if (w[0] === "-" && w[1] !== "-" && w.length > 2 && !w.includes("=") && w.slice(1).includes("l"))
237
+ listing = true;
238
+ if (w.includes("=")) {
239
+ lastFlag = before(w, "=");
240
+ i++;
241
+ }
242
+ else if (valued.has(w)) {
243
+ lastFlag = w;
244
+ i += 2;
245
+ }
246
+ else {
247
+ lastFlag = w;
248
+ i++;
249
+ }
250
+ }
251
+ else {
252
+ if (!listing && !optionalOperand.has(lastFlag))
253
+ return true;
254
+ i++;
255
+ }
256
+ }
257
+ return false;
258
+ },
259
+ },
260
+ "gh pr view": { safeFlags: { "--json": "string", "--comments": "none", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
261
+ "gh pr list": { safeFlags: { "--state": "string", "-s": "string", "--author": "string", "--assignee": "string", "--label": "string", "--limit": "number", "-L": "number", "--base": "string", "--head": "string", "--search": "string", "--json": "string", "--draft": "none", "--app": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
262
+ "gh pr diff": { safeFlags: { "--color": "string", "--name-only": "none", "--patch": "none", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
263
+ "gh pr checks": { safeFlags: { "--watch": "none", "--required": "none", "--fail-fast": "none", "--json": "string", "--interval": "number", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
264
+ "gh issue view": { safeFlags: { "--json": "string", "--comments": "none", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
265
+ "gh issue list": { safeFlags: { "--state": "string", "-s": "string", "--assignee": "string", "--author": "string", "--label": "string", "--limit": "number", "-L": "number", "--milestone": "string", "--search": "string", "--json": "string", "--app": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
266
+ "gh repo view": { safeFlags: { "--json": "string" }, dangerous: ghOperandDanger },
267
+ "gh run list": { safeFlags: { "--branch": "string", "-b": "string", "--status": "string", "-s": "string", "--workflow": "string", "-w": "string", "--limit": "number", "-L": "number", "--json": "string", "--repo": "string", "-R": "string", "--event": "string", "-e": "string", "--user": "string", "-u": "string", "--created": "string", "--commit": "string", "-c": "string" }, dangerous: ghOperandDanger },
268
+ "gh run view": { safeFlags: { "--log": "none", "--log-failed": "none", "--exit-status": "none", "--verbose": "none", "-v": "none", "--json": "string", "--repo": "string", "-R": "string", "--job": "string", "-j": "string", "--attempt": "number", "-a": "number" }, dangerous: ghOperandDanger },
269
+ "gh auth status": { safeFlags: { "--active": "none", "-a": "none", "--hostname": "string", "-h": "string", "--json": "string" }, dangerous: ghOperandDanger },
270
+ "gh pr status": { safeFlags: { "--conflict-status": "none", "-c": "none", "--json": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
271
+ "gh issue status": { safeFlags: { "--json": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
272
+ "gh release list": { safeFlags: { "--exclude-drafts": "none", "--exclude-pre-releases": "none", "--json": "string", "--limit": "number", "-L": "number", "--order": "string", "-O": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
273
+ "gh release view": { safeFlags: { "--json": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
274
+ "gh workflow list": { safeFlags: { "--all": "none", "-a": "none", "--json": "string", "--limit": "number", "-L": "number", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
275
+ "gh workflow view": { safeFlags: { "--ref": "string", "-r": "string", "--yaml": "none", "-y": "none", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
276
+ "gh label list": { safeFlags: { "--json": "string", "--limit": "number", "-L": "number", "--order": "string", "--search": "string", "-S": "string", "--sort": "string", "--repo": "string", "-R": "string" }, dangerous: ghOperandDanger },
277
+ "gh search repos": { safeFlags: { "--archived": "none", "--created": "string", "--followers": "string", "--forks": "string", "--good-first-issues": "string", "--help-wanted-issues": "string", "--include-forks": "string", "--json": "string", "--language": "string", "--license": "string", "--limit": "number", "-L": "number", "--match": "string", "--number-topics": "string", "--order": "string", "--owner": "string", "--size": "string", "--sort": "string", "--stars": "string", "--topic": "string", "--updated": "string", "--visibility": "string" } },
278
+ "gh search issues": { safeFlags: { "--app": "string", "--assignee": "string", "--author": "string", "--closed": "string", "--commenter": "string", "--comments": "string", "--created": "string", "--include-prs": "none", "--interactions": "string", "--involves": "string", "--json": "string", "--label": "string", "--language": "string", "--limit": "number", "-L": "number", "--locked": "none", "--match": "string", "--mentions": "string", "--milestone": "string", "--no-assignee": "none", "--no-label": "none", "--no-milestone": "none", "--no-project": "none", "--order": "string", "--owner": "string", "--project": "string", "--reactions": "string", "--repo": "string", "-R": "string", "--sort": "string", "--state": "string", "--team-mentions": "string", "--updated": "string", "--visibility": "string" } },
279
+ "gh search prs": { safeFlags: { "--app": "string", "--assignee": "string", "--author": "string", "--base": "string", "-B": "string", "--checks": "string", "--closed": "string", "--commenter": "string", "--comments": "string", "--created": "string", "--draft": "none", "--head": "string", "-H": "string", "--interactions": "string", "--involves": "string", "--json": "string", "--label": "string", "--language": "string", "--limit": "number", "-L": "number", "--locked": "none", "--match": "string", "--mentions": "string", "--merged": "none", "--merged-at": "string", "--milestone": "string", "--no-assignee": "none", "--no-label": "none", "--no-milestone": "none", "--no-project": "none", "--order": "string", "--owner": "string", "--project": "string", "--reactions": "string", "--repo": "string", "-R": "string", "--review": "string", "--review-requested": "string", "--reviewed-by": "string", "--sort": "string", "--state": "string", "--team-mentions": "string", "--updated": "string", "--visibility": "string" } },
280
+ "gh search commits": { safeFlags: { "--author": "string", "--author-date": "string", "--author-email": "string", "--author-name": "string", "--committer": "string", "--committer-date": "string", "--committer-email": "string", "--committer-name": "string", "--hash": "string", "--json": "string", "--limit": "number", "-L": "number", "--merge": "none", "--order": "string", "--owner": "string", "--parent": "string", "--repo": "string", "-R": "string", "--sort": "string", "--tree": "string", "--visibility": "string" } },
281
+ "gh search code": { safeFlags: { "--extension": "string", "--filename": "string", "--json": "string", "--language": "string", "--limit": "number", "-L": "number", "--match": "string", "--owner": "string", "--repo": "string", "-R": "string", "--size": "string" } },
282
+ "docker logs": { safeFlags: { "--follow": "none", "-f": "none", "--tail": "string", "-n": "string", "--timestamps": "none", "-t": "none", "--since": "string", "--until": "string", "--details": "none" }, dangerous: dockerRetargetDanger },
283
+ "docker inspect": { safeFlags: { "--format": "string", "-f": "string", "--type": "string", "--size": "none", "-s": "none" }, dangerous: dockerRetargetDanger },
284
+ file: { safeFlags: { "--brief": "none", "-b": "none", "--mime": "none", "-i": "none", "--mime-type": "none", "--mime-encoding": "none", "--apple": "none", "--check-encoding": "none", "-c": "none", "--exclude": "string", "--exclude-quiet": "string", "--print0": "none", "-0": "none", "-F": "string", "--separator": "string", "--help": "none", "--version": "none", "-v": "none", "--no-dereference": "none", "-h": "none", "--dereference": "none", "-L": "none", "--keep-going": "none", "-k": "none", "--list": "none", "-l": "none", "--no-buffer": "none", "-n": "none", "--preserve-date": "none", "-p": "none", "--raw": "none", "-r": "none", "-s": "none", "--special-files": "none" } },
285
+ sort: { safeFlags: { "--ignore-leading-blanks": "none", "-b": "none", "--dictionary-order": "none", "-d": "none", "--ignore-case": "none", "-f": "none", "--general-numeric-sort": "none", "-g": "none", "--human-numeric-sort": "none", "-h": "none", "--ignore-nonprinting": "none", "-i": "none", "--month-sort": "none", "-M": "none", "--numeric-sort": "none", "-n": "none", "--random-sort": "none", "-R": "none", "--reverse": "none", "-r": "none", "--sort": "string", "--stable": "none", "-s": "none", "--unique": "none", "-u": "none", "--version-sort": "none", "-V": "none", "--zero-terminated": "none", "-z": "none", "--key": "string", "-k": "string", "--field-separator": "string", "-t": "string", "--check": "none", "-c": "none", "--check-char-order": "none", "-C": "none", "--merge": "none", "-m": "none", "--buffer-size": "string", "-S": "string", "--parallel": "number", "--batch-size": "number", "--help": "none", "--version": "none" } },
286
+ man: {
287
+ safeFlags: { "-a": "none", "--all": "none", "-d": "none", "-f": "none", "--whatis": "none", "-h": "none", "-k": "none", "--apropos": "none", "-w": "none", "-S": "string", "-s": "string" },
288
+ dangerous: (args) => {
289
+ const searchFlags = new Set(["-k", "-f", "--apropos", "--whatis"]);
290
+ const valuedFlags = new Set(["-S", "-s"]);
291
+ let searching = false;
292
+ for (const w of args) {
293
+ if (w === "--")
294
+ break;
295
+ if (!w.startsWith("-") || w === "-")
296
+ continue;
297
+ if (w.startsWith("--")) {
298
+ if (searchFlags.has(w))
299
+ searching = true;
300
+ }
301
+ else if (/[kf]/.test(w.slice(1)))
302
+ searching = true;
303
+ }
304
+ let searchSeen = false;
305
+ let pastDashes = false;
306
+ for (let i = 0; i < args.length; i++) {
307
+ const w = args[i];
308
+ if (!pastDashes && w === "--") {
309
+ pastDashes = true;
310
+ continue;
311
+ }
312
+ if (!(pastDashes || !w.startsWith("-") || w === "-")) {
313
+ if (searchFlags.has(w))
314
+ searchSeen = true;
315
+ else if (valuedFlags.has(w))
316
+ i++;
317
+ continue;
318
+ }
319
+ pastDashes = true;
320
+ if (searching && w.startsWith("-"))
321
+ return true;
322
+ if (!searchSeen && (w.includes("/") || w.includes("\\") || w.includes("~")))
323
+ return true;
324
+ }
325
+ return false;
326
+ },
327
+ },
328
+ help: { safeFlags: { "-d": "none" }, dangerous: (args) => args.some((w) => w.includes("/") || w.includes("\\") || w.includes("~")) },
329
+ netstat: { safeFlags: { "-a": "none", "-L": "none", "-l": "none", "-n": "none", "-f": "string", "-g": "none", "-i": "none", "-I": "string", "-s": "none", "-r": "none", "-m": "none", "-v": "none" } },
330
+ ps: {
331
+ safeFlags: { "-e": "none", "-A": "none", "-a": "none", "-d": "none", "-N": "none", "--deselect": "none", "-f": "none", "-F": "none", "-l": "none", "-j": "none", "-y": "none", "-w": "none", "-ww": "none", "--width": "number", "-c": "none", "-H": "none", "--forest": "none", "--headers": "none", "--no-headers": "none", "-n": "string", "--sort": "string", "-L": "none", "-T": "none", "-m": "none", "-C": "string", "-G": "string", "-g": "string", "-p": "string", "--pid": "string", "-q": "string", "--quick-pid": "string", "-s": "string", "--sid": "string", "-t": "string", "--tty": "string", "-U": "string", "-u": "string", "--user": "string", "--help": "none", "--info": "none", "-V": "none", "--version": "none" },
332
+ dangerous: (args) => args.some((w) => !w.startsWith("-") && /^[a-zA-Z]*e[a-zA-Z]*$/.test(w)),
333
+ },
334
+ base64: { respectsDoubleDash: false, safeFlags: { "-d": "none", "-D": "none", "--decode": "none", "-b": "number", "--break": "number", "-w": "number", "--wrap": "number", "-i": "string", "--input": "string", "--ignore-garbage": "none", "-h": "none", "--help": "none", "--version": "none" } },
335
+ grep: { safeFlags: GREP_FLAGS },
336
+ egrep: { safeFlags: GREP_FLAGS },
337
+ fgrep: { safeFlags: GREP_FLAGS },
338
+ rg: { safeFlags: { "-e": "string", "--regexp": "string", "-f": "string", "-i": "none", "--ignore-case": "none", "-S": "none", "--smart-case": "none", "-F": "none", "--fixed-strings": "none", "-w": "none", "--word-regexp": "none", "-v": "none", "--invert-match": "none", "-c": "none", "--count": "none", "-l": "none", "--files-with-matches": "none", "--files-without-match": "none", "-n": "none", "--line-number": "none", "-o": "none", "--only-matching": "none", "-A": "number", "--after-context": "number", "-B": "number", "--before-context": "number", "-C": "number", "--context": "number", "-H": "none", "-h": "none", "--heading": "none", "--no-heading": "none", "-q": "none", "--quiet": "none", "--column": "none", "-g": "string", "--glob": "string", "-t": "string", "--type": "string", "-T": "string", "--type-not": "string", "--type-list": "none", "--hidden": "none", "--no-ignore": "none", "-u": "none", "-m": "number", "--max-count": "number", "-d": "number", "--max-depth": "number", "-a": "none", "--text": "none", "-L": "none", "--follow": "none", "--color": "string", "--json": "none", "--stats": "none", "--help": "none", "--version": "none", "--debug": "none", "--": "none" } },
339
+ sha256sum: { safeFlags: CHECKSUM_FLAGS },
340
+ sha1sum: { safeFlags: CHECKSUM_FLAGS },
341
+ md5sum: { safeFlags: CHECKSUM_FLAGS },
342
+ tree: { safeFlags: { "-a": "none", "-d": "none", "-l": "none", "-f": "none", "-x": "none", "-L": "number", "-P": "string", "-I": "string", "--gitignore": "none", "--gitfile": "string", "--ignore-case": "none", "--matchdirs": "none", "--metafirst": "none", "--prune": "none", "--info": "none", "--infofile": "string", "--noreport": "none", "--charset": "string", "--filelimit": "number", "-q": "none", "-N": "none", "-Q": "none", "-p": "none", "-u": "none", "-g": "none", "-s": "none", "-h": "none", "--si": "none", "--du": "none", "-D": "none", "--timefmt": "string", "-F": "none", "--inodes": "none", "--device": "none", "-v": "none", "-t": "none", "-c": "none", "-U": "none", "-r": "none", "--dirsfirst": "none", "--filesfirst": "none", "--sort": "string", "-i": "none", "-A": "none", "-S": "none", "-n": "none", "-C": "none", "-X": "none", "-J": "none", "-H": "string", "--nolinks": "none", "--hintro": "string", "--houtro": "string", "-T": "string", "--hyperlink": "none", "--scheme": "string", "--authority": "string", "--fromfile": "none", "--fromtabfile": "none", "--fflinks": "none", "--help": "none", "--version": "none" } },
343
+ date: {
344
+ safeFlags: { "-d": "string", "--date": "string", "-r": "string", "--reference": "string", "-u": "none", "--utc": "none", "--universal": "none", "-I": "none", "--iso-8601": "string", "-R": "none", "--rfc-email": "none", "--rfc-3339": "string", "--debug": "none", "--help": "none", "--version": "none" },
345
+ dangerous: (args) => {
346
+ const valued = new Set(["-d", "--date", "-r", "--reference", "--rfc-3339"]);
347
+ let i = 0;
348
+ while (i < args.length) {
349
+ const w = args[i];
350
+ if (w.startsWith("--") && w.includes("="))
351
+ i++;
352
+ else if (w.startsWith("-"))
353
+ i += valued.has(w) ? 2 : 1;
354
+ else {
355
+ if (!w.startsWith("+"))
356
+ return true;
357
+ i++;
358
+ }
359
+ }
360
+ return false;
361
+ },
362
+ },
363
+ hostname: {
364
+ safeFlags: { "-f": "none", "--fqdn": "none", "--long": "none", "-s": "none", "--short": "none", "-i": "none", "--ip-address": "none", "-I": "none", "--all-ip-addresses": "none", "-a": "none", "--alias": "none", "-d": "none", "--domain": "none", "-A": "none", "--all-fqdns": "none", "-v": "none", "--verbose": "none", "-h": "none", "--help": "none", "-V": "none", "--version": "none" },
365
+ wordsShape: (args) => args.every((w) => /^-[a-zA-Z]$/.test(w) || /^--[a-zA-Z-]+$/.test(w)),
366
+ },
367
+ lsof: {
368
+ safeFlags: { "-?": "none", "-h": "none", "-v": "none", "-a": "none", "-b": "none", "-C": "none", "-l": "none", "-n": "none", "-N": "none", "-O": "none", "-P": "none", "-Q": "none", "-R": "none", "-t": "none", "-U": "none", "-V": "none", "-X": "none", "-H": "none", "-E": "none", "-F": "none", "-g": "none", "-i": "none", "-K": "none", "-L": "none", "-o": "none", "-r": "none", "-s": "none", "-S": "none", "-T": "none", "-x": "none", "-A": "string", "-c": "string", "-d": "string", "-e": "string", "-k": "string", "-p": "string", "-u": "string" },
369
+ dangerous: (args) => {
370
+ for (let i = 0; i < args.length; i++) {
371
+ const w = args[i];
372
+ if (w === "+m" || w.startsWith("+m"))
373
+ return true;
374
+ if (/^-[a-zA-Z]*i\S*@/.test(w) && /[a-zA-Z]/.test(before(w.slice(w.indexOf("@") + 1), ":")))
375
+ return true;
376
+ if (/^-[a-zA-Z]*i$/.test(w)) {
377
+ const next = args[i + 1] ?? "";
378
+ if (next.includes("@") && /[a-zA-Z]/.test(before(next.slice(next.indexOf("@") + 1), ":")))
379
+ return true;
380
+ }
381
+ }
382
+ return false;
383
+ },
384
+ },
385
+ pgrep: { safeFlags: { "-d": "string", "--delimiter": "string", "-l": "none", "--list-name": "none", "-a": "none", "--list-full": "none", "-v": "none", "--inverse": "none", "-w": "none", "--lightweight": "none", "-c": "none", "--count": "none", "-f": "none", "--full": "none", "-g": "string", "--pgroup": "string", "-G": "string", "--group": "string", "-i": "none", "--ignore-case": "none", "-n": "none", "--newest": "none", "-o": "none", "--oldest": "none", "-O": "string", "--older": "string", "-P": "string", "--parent": "string", "-s": "string", "--session": "string", "-t": "string", "--terminal": "string", "-u": "string", "--euid": "string", "-U": "string", "--uid": "string", "-x": "none", "--exact": "none", "-F": "string", "--pidfile": "string", "-L": "none", "--logpidfile": "none", "-r": "string", "--runstates": "string", "--ns": "string", "--nslist": "string", "--help": "none", "-V": "none", "--version": "none" } },
386
+ tput: {
387
+ safeFlags: { "-T": "string", "-V": "none", "-x": "none" },
388
+ dangerous: (args) => {
389
+ const terminalWriting = new Set(["init", "reset", "rs1", "rs2", "rs3", "is1", "is2", "is3", "iprog", "if", "rf", "clear", "flash", "mc0", "mc4", "mc5", "mc5i", "mc5p", "pfkey", "pfloc", "pfx", "pfxl", "smcup", "rmcup"]);
390
+ let i = 0;
391
+ let pastDashes = false;
392
+ while (i < args.length) {
393
+ const w = args[i];
394
+ if (w === "--") {
395
+ pastDashes = true;
396
+ i++;
397
+ }
398
+ else if (!pastDashes && w.startsWith("-")) {
399
+ if (w === "-S")
400
+ return true;
401
+ if (!w.startsWith("--") && w.length > 2 && w.includes("S"))
402
+ return true;
403
+ i += w === "-T" ? 2 : 1;
404
+ }
405
+ else {
406
+ if (terminalWriting.has(w))
407
+ return true;
408
+ i++;
409
+ }
410
+ }
411
+ return false;
412
+ },
413
+ },
414
+ ss: {
415
+ safeFlags: { "-h": "none", "--help": "none", "-V": "none", "--version": "none", "-n": "none", "--numeric": "none", "-a": "none", "--all": "none", "-l": "none", "--listening": "none", "-o": "none", "--options": "none", "-e": "none", "--extended": "none", "-m": "none", "--memory": "none", "-p": "none", "--processes": "none", "-i": "none", "--info": "none", "-s": "none", "--summary": "none", "-4": "none", "--ipv4": "none", "-6": "none", "--ipv6": "none", "-0": "none", "--packet": "none", "-t": "none", "--tcp": "none", "-M": "none", "--mptcp": "none", "-S": "none", "--sctp": "none", "-u": "none", "--udp": "none", "-d": "none", "--dccp": "none", "-w": "none", "--raw": "none", "-x": "none", "--unix": "none", "--tipc": "none", "--vsock": "none", "-f": "string", "--family": "string", "-A": "string", "--query": "string", "--socket": "string", "-Z": "none", "--context": "none", "-z": "none", "--contexts": "none", "-b": "none", "--bpf": "none", "-E": "none", "--events": "none", "-H": "none", "--no-header": "none", "-O": "none", "--oneline": "none", "--tipcinfo": "none", "--tos": "none", "--cgroup": "none", "--inet-sockopt": "none" },
416
+ dangerous: (args) => {
417
+ const keyword = /^(dst|src|dport|sport|and|or|not|eq|ne|ge|le|gt|lt|autobound|state|exclude|dev|fwmark|cgroup)$/;
418
+ const takesOperand = /^(state|exclude|dport|sport|dev|fwmark|cgroup)$/;
419
+ const valuedFlag = /^(-f|--family|-A|--query|--socket)$/;
420
+ const filter = [];
421
+ let pastDashes = false;
422
+ for (let i = 0; i < args.length; i++) {
423
+ const w = args[i];
424
+ if (!pastDashes && w === "--") {
425
+ pastDashes = true;
426
+ continue;
427
+ }
428
+ if (!pastDashes && w.startsWith("-")) {
429
+ if (valuedFlag.test(w))
430
+ i++;
431
+ continue;
432
+ }
433
+ filter.push(w);
434
+ }
435
+ const tokens = filter.join(" ").split(/[\s()=!<>&|,]+/).filter(Boolean);
436
+ let skipOperand = false;
437
+ for (const token of tokens) {
438
+ if (skipOperand) {
439
+ skipOperand = false;
440
+ continue;
441
+ }
442
+ if (keyword.test(token)) {
443
+ skipOperand = takesOperand.test(token);
444
+ continue;
445
+ }
446
+ if (/[g-zG-Z]/.test(token) || (/[a-fA-F]/.test(token) && (token.includes(".") || !token.includes(":"))))
447
+ return true;
448
+ }
449
+ return false;
450
+ },
451
+ },
452
+ fd: { safeFlags: FD_FLAGS },
453
+ fdfind: { safeFlags: FD_FLAGS },
454
+ pyright: { respectsDoubleDash: false, safeFlags: { "--outputjson": "none", "--pythonversion": "string", "--pythonplatform": "string", "--level": "string", "--stats": "none", "--verbose": "none", "--version": "none", "--dependencies": "none", "--warnings": "none" }, dangerous: (args) => args.some((w) => w === "--watch" || w === "-w") },
455
+ test: {
456
+ respectsDoubleDash: false,
457
+ safeFlags: { "-b": "string", "-c": "string", "-d": "string", "-e": "string", "-f": "string", "-g": "string", "-G": "string", "-h": "string", "-k": "string", "-L": "string", "-N": "string", "-O": "string", "-p": "string", "-r": "string", "-s": "string", "-S": "string", "-t": "string", "-u": "string", "-w": "string", "-x": "string", "-z": "string", "-n": "string", "-eq": "string", "-ne": "string", "-lt": "string", "-le": "string", "-gt": "string", "-ge": "string", "-nt": "string", "-ot": "string", "-ef": "string" },
458
+ dangerous: (args) => {
459
+ if (args.some((w) => w === "-v" || w === "-R" || w === "-a" || w === "-o" || /\[/.test(w)))
460
+ return true;
461
+ for (let i = 0; i < args.length; i++) {
462
+ const w = args[i];
463
+ if (TEST_INTEGER_OPERATORS.has(w))
464
+ for (const side of [args[i - 1], args[i + 1]])
465
+ if (side !== undefined && !TEST_INTEGER.test(side))
466
+ return true;
467
+ if (w === "-t") {
468
+ const fd = args[i + 1];
469
+ if (fd !== undefined && !TEST_INTEGER.test(fd))
470
+ return true;
471
+ }
472
+ }
473
+ return false;
474
+ },
475
+ },
476
+ };
477
+ export const READ_ONLY_BARE_PROGRAMS = ["docker ps", "docker images", "cal", "uptime", "cat", "head", "tail", "wc", "stat", "strings", "hexdump", "od", "nl", "id", "uname", "free", "df", "du", "locale", "groups", "nproc", "basename", "dirname", "realpath", "cut", "paste", "tr", "column", "tac", "rev", "fold", "expand", "unexpand", "fmt", "comm", "cmp", "numfmt", "readlink", "diff", "true", "false", "sleep", "which", "type", "expr", "seq", "tsort", "pr"];
478
+ export const READ_ONLY_GLOB_PROGRAMS = ["ls", "cat", "head", "tail", "wc", "stat", "grep", "egrep", "fgrep", "diff", "du", "df", "echo", "strings", "hexdump", "od", "nl", "cut", "column", "tr", "tac", "rev", "cmp", "basename", "dirname", "realpath", "readlink", "sha256sum", "sha1sum", "md5sum", "cd"];
479
+ export const READ_ONLY_EXACT_FORMS = [["claude", "-h"], ["claude", "--help"], ["node", "-v"], ["node", "--version"], ["python", "--version"], ["python3", "--version"], ["ip", "addr"]];
480
+ export const READ_ONLY_BARE_ONLY = ["pwd", "whoami", "alias"];
481
+ export const FIND_ACTION_PRIMARIES = ["-delete", "-exec", "-execdir", "-ok", "-okdir", "-fprint", "-fprint0", "-fls", "-fprintf", "-files0-from"];
482
+ export const FIND_VALUE_PRIMARIES = ["-name", "-iname", "-path", "-ipath", "-lname", "-ilname", "-regex", "-iregex", "-wholename", "-iwholename", "-samefile", "-newer", "-anewer", "-cnewer", "-mnewer", "-perm", "-user", "-group", "-uid", "-gid", "-size", "-type", "-xtype", "-fstype", "-inum", "-links", "-used", "-context", "-amin", "-cmin", "-mmin", "-atime", "-ctime", "-mtime", "-mindepth", "-maxdepth", "-printf", "-regextype", "-D", "-f", "-flags", "-Bnewer", "-Btime", "-Bmin", "-files0-from", "-xattrname"];
483
+ export const FIND_NEWER_PRIMARY = /^-newer[aBcm][aBcmt]$/;
484
+ export const READ_ONLY_ENV_NAMES = ["GOEXPERIMENT", "GOOS", "GOARCH", "CGO_ENABLED", "GO111MODULE", "RUST_BACKTRACE", "RUST_LOG", "NODE_ENV", "PYTHONUNBUFFERED", "PYTHONDONTWRITEBYTECODE", "PYTEST_DISABLE_PLUGIN_AUTOLOAD", "PYTEST_DEBUG", "ANTHROPIC_API_KEY", "LANG", "LANGUAGE", "LC_ALL", "LC_CTYPE", "LC_TIME", "CHARSET", "TERM", "COLORTERM", "NO_COLOR", "FORCE_COLOR", "TZ", "LS_COLORS", "LSCOLORS", "GREP_COLOR", "GREP_COLORS", "GCC_COLORS", "TIME_STYLE", "BLOCK_SIZE", "BLOCKSIZE", "COLUMNS", "LINES", "CLICOLOR", "CLICOLOR_FORCE", "CI", "DEBIAN_FRONTEND", "GIT_TERMINAL_PROMPT"];
485
+ export const XARGS_READ_ONLY_TARGETS = ["echo", "printf", "wc", "grep", "egrep", "fgrep", "head", "tail"];
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The READ-ONLY shell reader — "does this command line provably only READ?" — the allow-layer member
3
+ * the tool gate consults for the shell tool after every tightening lane and after the person's own allow
4
+ * rules, and before the classifier or a person is asked (`gate-lanes.ts`). Upstream (CC 2.1.250) has the
5
+ * same station: its shell tool's `isReadOnly` runs the command through a parser, splits it into its
6
+ * simple commands, and admits the whole line only when EVERY simple command is read-only by its tables;
7
+ * this module is that reading over this engine's own lexer ({@link readShellCommand}, the tightening
8
+ * reader's) and the transcribed tables (`read-only-shell-table.ts`).
9
+ *
10
+ * The reading itself is the contract on {@link readOnlyShellVerdict}.
11
+ *
12
+ * What the verdict is NOT: a security boundary of its own. The deny/ask lanes run before it and win; the
13
+ * mandated asks (an operator's per-call shell confirmation, a tool's own egress/irreversibility marks, an
14
+ * explicit ask rule, a hook's ask, governance) are never cleared by it — the gate's allow-layer predicate
15
+ * decides where it may speak, exactly as it decides for a person's stored allow rule.
16
+ */
17
+ import { type ShellCommandShape } from "./shell-lexer.js";
18
+ /** The reader's answer: read-only, or not — with the first reason the reading stopped on (a sentence a
19
+ * trace or a card can show; never adjudication input). */
20
+ export type ReadOnlyShellVerdict = {
21
+ readonly readOnly: true;
22
+ } | {
23
+ readonly readOnly: false;
24
+ readonly reason: string;
25
+ };
26
+ /**
27
+ * Read `command` as the allow layer must: read-only, or the first reason it is not. A shape already read by
28
+ * the caller may be handed in (the gate reads every shell command once).
29
+ *
30
+ * @contract read_only_shell.every_segment — a command line is read-only iff the lexer reads it whole (no
31
+ * unreadable segment, no group/subshell/control structure, no background `&`), no word anywhere carries a
32
+ * variable, arithmetic or command substitution, and EVERY segment is read-only on its own: its
33
+ * redirections only read or discard (`<` family, `/dev/null`, a descriptor duplication), its leading
34
+ * assignments name only variables from {@link READ_ONLY_ENV_NAMES} with literal values, and its program
35
+ * with its arguments is admitted by exactly one of three readings in order — a segment carrying a GLOB is
36
+ * admitted only under {@link READ_ONLY_GLOB_PROGRAMS}; else the structural reading (the bare-program set,
37
+ * the exact forms, `echo`/`ls`/`cd`/`find`/`printf`/`history`/`arch`/`ifconfig`); else the flag-vetted
38
+ * table with its per-row danger predicates, and the `uniq`/`jq` forms. A `cd` beside a `git` in one line
39
+ * is never read-only (the git command's repository is then not the session's). Anything the reading does
40
+ * not positively admit is NOT read-only — the answer is never a guess, and a `false` costs one question.
41
+ */
42
+ export declare function readOnlyShellVerdict(command: string, shape?: ShellCommandShape): ReadOnlyShellVerdict;