@aria-cli/tools 1.0.9 → 1.0.11

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 (241) hide show
  1. package/package.json +9 -5
  2. package/src/__tests__/web-fetch-download.test.ts +0 -433
  3. package/src/__tests__/web-tools.test.ts +0 -619
  4. package/src/ask-user-interaction.ts +0 -33
  5. package/src/cache/web-cache.ts +0 -110
  6. package/src/definitions/arion.ts +0 -118
  7. package/src/definitions/browser/browser.ts +0 -502
  8. package/src/definitions/browser/index.ts +0 -5
  9. package/src/definitions/browser/pw-downloads.ts +0 -142
  10. package/src/definitions/browser/pw-interactions.ts +0 -282
  11. package/src/definitions/browser/pw-responses.ts +0 -98
  12. package/src/definitions/browser/pw-session.ts +0 -405
  13. package/src/definitions/browser/pw-shared.ts +0 -85
  14. package/src/definitions/browser/pw-snapshot.ts +0 -383
  15. package/src/definitions/browser/pw-state.ts +0 -101
  16. package/src/definitions/browser/types.ts +0 -203
  17. package/src/definitions/code-intelligence.ts +0 -526
  18. package/src/definitions/core.ts +0 -118
  19. package/src/definitions/delegation.ts +0 -567
  20. package/src/definitions/deploy.ts +0 -73
  21. package/src/definitions/filesystem.ts +0 -217
  22. package/src/definitions/frg.ts +0 -67
  23. package/src/definitions/index.ts +0 -28
  24. package/src/definitions/memory.ts +0 -150
  25. package/src/definitions/messaging.ts +0 -734
  26. package/src/definitions/meta.ts +0 -392
  27. package/src/definitions/network.ts +0 -179
  28. package/src/definitions/outlook.ts +0 -318
  29. package/src/definitions/patch/apply-patch.ts +0 -235
  30. package/src/definitions/patch/fuzzy-match.ts +0 -217
  31. package/src/definitions/patch/index.ts +0 -1
  32. package/src/definitions/patch/patch-parser.ts +0 -297
  33. package/src/definitions/patch/sandbox-paths.ts +0 -129
  34. package/src/definitions/process/index.ts +0 -5
  35. package/src/definitions/process/process-registry.ts +0 -303
  36. package/src/definitions/process/process.ts +0 -456
  37. package/src/definitions/process/pty-keys.ts +0 -298
  38. package/src/definitions/process/session-slug.ts +0 -147
  39. package/src/definitions/quip.ts +0 -225
  40. package/src/definitions/search.ts +0 -67
  41. package/src/definitions/session-history.ts +0 -79
  42. package/src/definitions/shell.ts +0 -202
  43. package/src/definitions/slack.ts +0 -211
  44. package/src/definitions/web.ts +0 -119
  45. package/src/executors/apply-patch.ts +0 -1035
  46. package/src/executors/arion.ts +0 -199
  47. package/src/executors/code-intelligence.ts +0 -1179
  48. package/src/executors/deploy.ts +0 -1066
  49. package/src/executors/filesystem.ts +0 -1428
  50. package/src/executors/frg-freshness.ts +0 -743
  51. package/src/executors/frg.ts +0 -394
  52. package/src/executors/index.ts +0 -280
  53. package/src/executors/learning-meta.ts +0 -1367
  54. package/src/executors/lsp-client.ts +0 -355
  55. package/src/executors/memory.ts +0 -978
  56. package/src/executors/meta.ts +0 -293
  57. package/src/executors/process-registry.ts +0 -570
  58. package/src/executors/pty-session-store.ts +0 -43
  59. package/src/executors/pty.ts +0 -342
  60. package/src/executors/restart.ts +0 -133
  61. package/src/executors/search-freshness.ts +0 -249
  62. package/src/executors/search-types.ts +0 -98
  63. package/src/executors/search.ts +0 -89
  64. package/src/executors/self-diagnose.ts +0 -552
  65. package/src/executors/session-history.ts +0 -435
  66. package/src/executors/shell-safety.ts +0 -519
  67. package/src/executors/shell.ts +0 -1243
  68. package/src/executors/utils.ts +0 -40
  69. package/src/executors/web.ts +0 -786
  70. package/src/extraction/content-extraction.ts +0 -281
  71. package/src/extraction/index.ts +0 -5
  72. package/src/headless-control-contract.ts +0 -1149
  73. package/src/index.ts +0 -788
  74. package/src/local-control-http-auth.ts +0 -2
  75. package/src/mcp/client.ts +0 -218
  76. package/src/mcp/connection.ts +0 -568
  77. package/src/mcp/index.ts +0 -11
  78. package/src/mcp/jsonrpc.ts +0 -195
  79. package/src/mcp/types.ts +0 -199
  80. package/src/network-control-adapter.ts +0 -88
  81. package/src/network-runtime/address-types.ts +0 -218
  82. package/src/network-runtime/db-owner-fencing.ts +0 -91
  83. package/src/network-runtime/delivery-receipts.ts +0 -372
  84. package/src/network-runtime/direct-endpoint-authority.ts +0 -35
  85. package/src/network-runtime/index.ts +0 -316
  86. package/src/network-runtime/local-control-contract.ts +0 -784
  87. package/src/network-runtime/node-store-contract.ts +0 -46
  88. package/src/network-runtime/pair-route-contract.ts +0 -97
  89. package/src/network-runtime/peer-capabilities.ts +0 -48
  90. package/src/network-runtime/peer-principal-ref.ts +0 -20
  91. package/src/network-runtime/peer-state-machine.ts +0 -160
  92. package/src/network-runtime/protocol-schemas.ts +0 -265
  93. package/src/network-runtime/runtime-bootstrap-contract.ts +0 -83
  94. package/src/outlook/desktop-session.ts +0 -409
  95. package/src/policy.ts +0 -171
  96. package/src/providers/brave.ts +0 -80
  97. package/src/providers/duckduckgo.ts +0 -199
  98. package/src/providers/exa.ts +0 -85
  99. package/src/providers/firecrawl.ts +0 -77
  100. package/src/providers/index.ts +0 -8
  101. package/src/providers/jina.ts +0 -70
  102. package/src/providers/router.ts +0 -121
  103. package/src/providers/search-provider.ts +0 -74
  104. package/src/providers/tavily.ts +0 -74
  105. package/src/quip/desktop-session.ts +0 -435
  106. package/src/registry/index.ts +0 -1
  107. package/src/registry/registry.ts +0 -905
  108. package/src/runtime-socket-local-control-client.ts +0 -632
  109. package/src/security/dns-normalization.ts +0 -34
  110. package/src/security/dns-pinning.ts +0 -138
  111. package/src/security/external-content.ts +0 -129
  112. package/src/security/ssrf.ts +0 -207
  113. package/src/slack/desktop-session.ts +0 -493
  114. package/src/tool-factory.ts +0 -91
  115. package/src/types.ts +0 -1341
  116. package/src/utils/retry.ts +0 -163
  117. package/src/utils/safe-parse-json.ts +0 -176
  118. package/src/utils/url.ts +0 -20
  119. package/tests/benchmarks/registry.bench.ts +0 -57
  120. package/tests/cache/web-cache.test.ts +0 -147
  121. package/tests/critical-integration.test.ts +0 -1465
  122. package/tests/definitions/apply-patch.test.ts +0 -586
  123. package/tests/definitions/browser.test.ts +0 -495
  124. package/tests/definitions/delegation-pause-resume.test.ts +0 -758
  125. package/tests/definitions/execution.test.ts +0 -671
  126. package/tests/definitions/messaging-inbox-scope.test.ts +0 -229
  127. package/tests/definitions/messaging.test.ts +0 -1468
  128. package/tests/definitions/outlook.test.ts +0 -30
  129. package/tests/definitions/process.test.ts +0 -469
  130. package/tests/definitions/slack.test.ts +0 -28
  131. package/tests/definitions/tool-inventory.test.ts +0 -218
  132. package/tests/e2e/delegation-quest-orchestration.e2e.test.ts +0 -433
  133. package/tests/e2e/memory-tool-discovery-contract.e2e.test.ts +0 -81
  134. package/tests/executors/apply-patch.test.ts +0 -538
  135. package/tests/executors/arion.test.ts +0 -309
  136. package/tests/executors/conversation-primitives.test.ts +0 -250
  137. package/tests/executors/deploy.test.ts +0 -746
  138. package/tests/executors/filesystem-tools.test.ts +0 -357
  139. package/tests/executors/filesystem.test.ts +0 -959
  140. package/tests/executors/frg-freshness.test.ts +0 -136
  141. package/tests/executors/frg-merge.test.ts +0 -70
  142. package/tests/executors/frg-session-content.test.ts +0 -40
  143. package/tests/executors/frg.test.ts +0 -56
  144. package/tests/executors/memory-bugfixes.test.ts +0 -257
  145. package/tests/executors/memory-real-memoria.integration.test.ts +0 -316
  146. package/tests/executors/memory.test.ts +0 -853
  147. package/tests/executors/meta-tools.test.ts +0 -411
  148. package/tests/executors/meta.test.ts +0 -683
  149. package/tests/executors/path-containment.test.ts +0 -51
  150. package/tests/executors/process-registry.test.ts +0 -505
  151. package/tests/executors/pty.test.ts +0 -664
  152. package/tests/executors/quest-security.test.ts +0 -249
  153. package/tests/executors/read-file-media.test.ts +0 -230
  154. package/tests/executors/recall-knowledge-schema.test.ts +0 -209
  155. package/tests/executors/recall-tags.test.ts +0 -278
  156. package/tests/executors/remember-null-safety.contract.test.ts +0 -41
  157. package/tests/executors/restart.test.ts +0 -67
  158. package/tests/executors/search-unified.test.ts +0 -381
  159. package/tests/executors/session-history.test.ts +0 -340
  160. package/tests/executors/session-transcript.test.ts +0 -561
  161. package/tests/executors/shell-abort.test.ts +0 -416
  162. package/tests/executors/shell-env-blocklist.test.ts +0 -648
  163. package/tests/executors/shell-env-process.test.ts +0 -245
  164. package/tests/executors/shell-process-registry.test.ts +0 -334
  165. package/tests/executors/shell-tools.test.ts +0 -393
  166. package/tests/executors/shell.test.ts +0 -690
  167. package/tests/executors/web-abort-vs-timeout.test.ts +0 -213
  168. package/tests/executors/web-integration.test.ts +0 -633
  169. package/tests/executors/web-symlink.test.ts +0 -18
  170. package/tests/executors/web.test.ts +0 -1400
  171. package/tests/executors/write-stdin.test.ts +0 -145
  172. package/tests/extraction/content-extraction.test.ts +0 -153
  173. package/tests/guards/tools-default-test-lane.integration.test.ts +0 -21
  174. package/tests/guards/tools-package-test-commands.e2e.test.ts +0 -43
  175. package/tests/guards/tools-test-lane-manifest.contract.test.ts +0 -76
  176. package/tests/guards/tools-vitest-workspace-alias.contract.test.ts +0 -63
  177. package/tests/helpers/async-waits.ts +0 -53
  178. package/tests/integration/headless-control-contract.integration.test.ts +0 -153
  179. package/tests/integration/memory-tool-schema-parity.integration.test.ts +0 -67
  180. package/tests/integration/meta-tools-round-trip.integration.test.ts +0 -506
  181. package/tests/integration/quest-round-trip.test.ts +0 -303
  182. package/tests/integration/registry-executor-flow.test.ts +0 -85
  183. package/tests/integration.test.ts +0 -177
  184. package/tests/loading-tier.test.ts +0 -126
  185. package/tests/mcp/client-reconnect.test.ts +0 -267
  186. package/tests/mcp/connection.test.ts +0 -846
  187. package/tests/mcp/injectable-logger.test.ts +0 -83
  188. package/tests/mcp/jsonrpc.test.ts +0 -109
  189. package/tests/mcp/lifecycle.test.ts +0 -879
  190. package/tests/network-runtime/address-types.contract.test.ts +0 -143
  191. package/tests/network-runtime/continuity-bind-schema.contract.test.ts +0 -203
  192. package/tests/network-runtime/local-control-contract.test.ts +0 -869
  193. package/tests/network-runtime/local-control-invite-token.contract.test.ts +0 -146
  194. package/tests/network-runtime/node-store-contract.test.ts +0 -11
  195. package/tests/network-runtime/pair-protocol-nodeid.contract.test.ts +0 -15
  196. package/tests/network-runtime/peer-state-machine.contract.test.ts +0 -148
  197. package/tests/network-runtime/protocol-schemas.contract.test.ts +0 -512
  198. package/tests/network-runtime/relay-pending-nodeid.contract.test.ts +0 -62
  199. package/tests/network-runtime/runtime-bootstrap-contract.test.ts +0 -227
  200. package/tests/network-runtime/runtime-socket-local-control-client.test.ts +0 -621
  201. package/tests/network-runtime/wait-for-message-script.test.ts +0 -288
  202. package/tests/parallel.test.ts +0 -71
  203. package/tests/policy.test.ts +0 -184
  204. package/tests/print-default-test-lane.ts +0 -14
  205. package/tests/print-test-lane-manifest.ts +0 -22
  206. package/tests/providers/brave.test.ts +0 -159
  207. package/tests/providers/duckduckgo.test.ts +0 -207
  208. package/tests/providers/exa.test.ts +0 -175
  209. package/tests/providers/firecrawl.test.ts +0 -168
  210. package/tests/providers/jina.test.ts +0 -144
  211. package/tests/providers/router.test.ts +0 -328
  212. package/tests/providers/tavily.test.ts +0 -165
  213. package/tests/registry/discovery.test.ts +0 -154
  214. package/tests/registry/injectable-logger.test.ts +0 -230
  215. package/tests/registry/input-validation.test.ts +0 -361
  216. package/tests/registry/interface-completeness.test.ts +0 -85
  217. package/tests/registry/mcp-integration.test.ts +0 -103
  218. package/tests/registry/mcp-read-only-hint.test.ts +0 -60
  219. package/tests/registry/memoria-discovery.test.ts +0 -390
  220. package/tests/registry/nested-validation.test.ts +0 -283
  221. package/tests/registry/pseudo-tool-filtering.test.ts +0 -258
  222. package/tests/registry/registration-lifecycle.test.ts +0 -133
  223. package/tests/registry-validation.test.ts +0 -424
  224. package/tests/registry.test.ts +0 -460
  225. package/tests/security/dns-pinning.test.ts +0 -162
  226. package/tests/security/external-content.test.ts +0 -144
  227. package/tests/security/ssrf.test.ts +0 -118
  228. package/tests/shell-safety-integration.test.ts +0 -32
  229. package/tests/shell-safety.test.ts +0 -365
  230. package/tests/slack/desktop-session.test.ts +0 -50
  231. package/tests/test-lane-manifest.ts +0 -440
  232. package/tests/test-utils.ts +0 -27
  233. package/tests/tool-factory.test.ts +0 -188
  234. package/tests/utils/retry.test.ts +0 -231
  235. package/tests/utils/url.test.ts +0 -63
  236. package/tsconfig.cjs.json +0 -24
  237. package/tsconfig.json +0 -12
  238. package/vitest.config.ts +0 -55
  239. package/vitest.e2e.config.ts +0 -24
  240. package/vitest.integration.config.ts +0 -24
  241. package/vitest.native.config.ts +0 -24
@@ -1,519 +0,0 @@
1
- /**
2
- * @aria/tools - Shell command risk classifier
3
- *
4
- * Statically classifies shell commands into risk tiers to gate execution:
5
- * - "safe" : read-only, execute immediately without approval
6
- * - "moderate" : requires runtime policy handling (approval, allowlist, or autorun)
7
- * - "blocked" : catastrophic, hard-denied — never execute
8
- */
9
-
10
- export type ShellRisk = "safe" | "moderate" | "blocked";
11
-
12
- /** Read-only single-word commands that never modify state. */
13
- const SAFE_SINGLE = new Set([
14
- "ls",
15
- "cat",
16
- "head",
17
- "tail",
18
- "wc",
19
- "file",
20
- "stat",
21
- "grep",
22
- "rg",
23
- "find",
24
- "which",
25
- "whereis",
26
- "echo",
27
- "date",
28
- "whoami",
29
- "pwd",
30
- "printenv",
31
- "uname",
32
- "hostname",
33
- ]);
34
-
35
- /** Read-only multi-word command prefixes (order: longest match first). */
36
- const SAFE_MULTI: string[] = [
37
- "git stash list",
38
- "git status",
39
- "git log",
40
- "git diff",
41
- "git show",
42
- "git blame",
43
- "git branch",
44
- "git remote",
45
- "git tag",
46
- "node --version",
47
- "npm --version",
48
- "pnpm --version",
49
- "python --version",
50
- "pnpm list",
51
- "npm list",
52
- ];
53
-
54
- /** Patterns that are unconditionally blocked — catastrophic risk. */
55
- export const BLOCKED_PATTERNS: RegExp[] = [
56
- /rm\s+(?:--?[A-Za-z0-9-]+\s+)*\/(?:\s|$)/, // rm targeting filesystem root (/)
57
- /rm\s+(?:--?[A-Za-z0-9-]+\s+)*\/\*(?:\s|$)/, // rm targeting root wildcard (/*)
58
- /rm\s+(?:--?[A-Za-z0-9-]+\s+)*\.(?:\s|$)/, // rm . (current dir wipe, not ./subdir)
59
- /rm\s+(?:--?[A-Za-z0-9-]+\s+)*~(?:[a-zA-Z]\w*)?(?:\/\*)?(?:\s|$)/, // rm ~ (bare home), ~/* (home wildcard), ~user (other user home)
60
- /rm\s+(?:--?[A-Za-z0-9-]+\s+)*\$HOME\b/, // rm with $HOME variable
61
- /rm\s+(?:--?[A-Za-z0-9-]+\s+)*\*(?:\s|$)/, // rm with bare wildcard (rm -rf *)
62
- />\s*\/dev\/(?:sd[a-z]|nvme\d+|vd[a-z])\b/, // write to block devices
63
- /mkfs/, // format filesystems
64
- /dd\s+.*(?:if=|of=)/, // raw disk reads/writes
65
- /chmod\s+(?:-R\s+)?777\b/, // world-writable permissions
66
- /curl[\s\S]*\|\s*(ba)?sh/, // pipe-to-shell (including newline-obfuscated variants)
67
- /wget[\s\S]*\|\s*(ba)?sh/, // pipe-to-shell (including newline-obfuscated variants)
68
- /(?:^|[;&|]\s*|\$\(|`|\()\s*(?:(?:env|command)\s+)*eval\b/, // shell eval injection
69
- // Inline execution (sh -c, python -c, node -e, etc.) downgraded to moderate.
70
- // These are legitimate developer operations — dangerous payloads are still
71
- // caught by other blocked patterns (rm /, curl|sh, fork bombs, etc.).
72
- /(?:^|[;&|]\s*|\$\(|`|\()\s*(?:(?:env|command)\s+)*shutdown\b/, // system shutdown
73
- /(?:^|[;&|]\s*|\$\(|`|\()\s*(?:(?:env|command)\s+)*reboot\b/, // system reboot
74
- /(?:^|[;&|]\s*|\$\(|`|\()\s*(?:(?:env|command)\s+)*halt\b/, // system halt
75
- /(?:^|[;&|]\s*|\$\(|`|\()\s*(?:(?:env|command)\s+)*init\s+0\b/, // init runlevel poweroff
76
- /(?:^|[;&|]\s*|\$\(|`|\()\s*(?:(?:env|command)\s+)*systemctl\s+(?:poweroff|halt|reboot)\b/, // systemctl power controls
77
- // kill downgraded to moderate — legitimate process management (kill PID, kill -0)
78
- // is a normal developer operation. Catastrophic kill (kill -9 1) is still caught
79
- // by the PID-1 pattern below. See shell-safety.test.ts for coverage.
80
- /(?:^|[;&|]\s*|\$\(|`|\()\s*(?:(?:env|command)\s+)*kill\s+(?:-\d+\s+|-[A-Z]+\s+)*\b1\b/, // kill PID 1 (init) — catastrophic
81
- // ${...} parameter expansion downgraded to moderate — standard bash operations
82
- // like ${VAR}, ${#VAR} (length), ${VAR:-default} are common developer patterns.
83
- // Truly dangerous expansions (${VAR:=$(cmd)}) are caught by other patterns
84
- // (eval, curl|sh, etc.) or by the subshell/backtick check in classifyCommand.
85
- /:\(\)\{\s*:\|:&\s*\};:/, // fork bomb
86
- /\bsudo\b/, // privilege escalation
87
- /git\s+push\s+.*--force(?!-with-lease)\b/, // force push (allow --force-with-lease)
88
- /git\s+push(?:\s+-[A-Za-z]*f[A-Za-z]*\b|\s+.*\s-[A-Za-z]*f[A-Za-z]*\b)/, // short force flags (-f, -uf, etc.)
89
- /git\s+reset\s+--hard/, // hard reset
90
- ];
91
-
92
- /**
93
- * Returns true if the raw command text matches any blocked pattern.
94
- */
95
- function isBlocked(raw: string): boolean {
96
- const withoutHeredocs = stripHeredocBodies(raw);
97
- const withoutQuotedLiterals = stripSingleAndDoubleQuotedLiterals(withoutHeredocs);
98
- return BLOCKED_PATTERNS.some((re) => re.test(withoutQuotedLiterals));
99
- }
100
-
101
- /**
102
- * Strip heredoc bodies so that blocked-pattern checks don't fire on
103
- * data content inside heredocs. Supports both quoted and unquoted
104
- * delimiters: `<< 'EOF'`, `<< "EOF"`, `<< EOF`, `<<-EOF`.
105
- *
106
- * Only the body between the delimiter lines is replaced with spaces;
107
- * the shell command on the `<<` line and the closing delimiter are
108
- * preserved so other pattern checks still apply to the command itself.
109
- */
110
- function stripHeredocBodies(raw: string): string {
111
- // Match << (optional dash) then optional quotes around the delimiter word
112
- const heredocRe = /<<-?\s*(?:'([^']+)'|"([^"]+)"|(\w+))/g;
113
- let result = raw;
114
- let match: RegExpExecArray | null;
115
-
116
- // Collect all heredoc markers first, then strip from the end backwards
117
- // so index positions remain valid.
118
- const markers: Array<{ delimiter: string; bodyStart: number; bodyEnd: number }> = [];
119
-
120
- while ((match = heredocRe.exec(raw)) !== null) {
121
- const delimiter = match[1] ?? match[2] ?? match[3] ?? "";
122
- if (!delimiter) continue;
123
-
124
- // Body starts after the next newline following the << marker
125
- const afterMarker = raw.indexOf("\n", match.index);
126
- if (afterMarker === -1) continue;
127
- const bodyStart = afterMarker + 1;
128
-
129
- // Find the closing delimiter: must be on its own line (with optional
130
- // leading whitespace for <<- heredocs).
131
- const closingRe = new RegExp(
132
- `^\\s*${delimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`,
133
- "m",
134
- );
135
- const bodySlice = raw.slice(bodyStart);
136
- const closingMatch = closingRe.exec(bodySlice);
137
- if (!closingMatch) continue;
138
-
139
- const bodyEnd = bodyStart + closingMatch.index;
140
- markers.push({ delimiter, bodyStart, bodyEnd });
141
- }
142
-
143
- // Strip bodies from last to first to preserve indices
144
- for (let i = markers.length - 1; i >= 0; i--) {
145
- const { bodyStart, bodyEnd } = markers[i]!;
146
- const body = result.slice(bodyStart, bodyEnd);
147
- result = result.slice(0, bodyStart) + body.replace(/[^\n]/g, " ") + result.slice(bodyEnd);
148
- }
149
-
150
- return result;
151
- }
152
-
153
- /**
154
- * Remove single-quoted and double-quoted literal text from a command so
155
- * blocked-pattern checks don't fire on plain quoted prose like:
156
- * echo "please do not run rm -rf /"
157
- */
158
- function stripSingleAndDoubleQuotedLiterals(raw: string): string {
159
- let result = "";
160
- let inSingle = false;
161
- let inDouble = false;
162
- let escaped = false;
163
-
164
- for (let i = 0; i < raw.length; i++) {
165
- const ch = raw[i]!;
166
-
167
- if (inSingle) {
168
- if (ch === "'") {
169
- inSingle = false;
170
- result += " ";
171
- } else {
172
- result += " ";
173
- }
174
- continue;
175
- }
176
-
177
- if (inDouble) {
178
- if (escaped) {
179
- escaped = false;
180
- result += " ";
181
- continue;
182
- }
183
- if (ch === "\\") {
184
- escaped = true;
185
- result += " ";
186
- continue;
187
- }
188
- if (ch === '"') {
189
- inDouble = false;
190
- result += " ";
191
- } else {
192
- result += " ";
193
- }
194
- continue;
195
- }
196
-
197
- if (ch === "'") {
198
- inSingle = true;
199
- result += " ";
200
- continue;
201
- }
202
- if (ch === '"') {
203
- inDouble = true;
204
- result += " ";
205
- continue;
206
- }
207
-
208
- result += ch;
209
- }
210
-
211
- return result;
212
- }
213
-
214
- /**
215
- * Strip an optional absolute-path prefix from a token
216
- * so `/usr/bin/rm` is treated the same as `rm`.
217
- */
218
- function stripPathPrefix(token: string): string {
219
- const i = token.lastIndexOf("/");
220
- return i === -1 ? token : token.slice(i + 1);
221
- }
222
-
223
- function hasUnquotedSubshellOrBacktick(command: string): boolean {
224
- let inSingle = false;
225
- let inDouble = false;
226
- let escaped = false;
227
-
228
- for (let i = 0; i < command.length; i++) {
229
- const ch = command[i]!;
230
- const next = command[i + 1];
231
-
232
- if (inSingle) {
233
- if (ch === "'") inSingle = false;
234
- continue;
235
- }
236
-
237
- if (inDouble) {
238
- if (escaped) {
239
- escaped = false;
240
- continue;
241
- }
242
- if (ch === "\\") {
243
- escaped = true;
244
- continue;
245
- }
246
- if (ch === '"') {
247
- inDouble = false;
248
- continue;
249
- }
250
- if (ch === "`" || (ch === "$" && next === "(")) {
251
- return true;
252
- }
253
- continue;
254
- }
255
-
256
- if (escaped) {
257
- escaped = false;
258
- continue;
259
- }
260
- if (ch === "\\") {
261
- escaped = true;
262
- continue;
263
- }
264
- if (ch === "'") {
265
- inSingle = true;
266
- continue;
267
- }
268
- if (ch === '"') {
269
- inDouble = true;
270
- continue;
271
- }
272
- if (ch === "`" || (ch === "$" && next === "(")) {
273
- return true;
274
- }
275
- }
276
-
277
- return false;
278
- }
279
-
280
- function splitTopLevelChain(command: string): string[] {
281
- const parts: string[] = [];
282
- let current = "";
283
- let inSingle = false;
284
- let inDouble = false;
285
- let escaped = false;
286
-
287
- for (let i = 0; i < command.length; i++) {
288
- const ch = command[i]!;
289
- const next = command[i + 1];
290
-
291
- if (inSingle) {
292
- current += ch;
293
- if (ch === "'") inSingle = false;
294
- continue;
295
- }
296
-
297
- if (inDouble) {
298
- current += ch;
299
- if (escaped) {
300
- escaped = false;
301
- continue;
302
- }
303
- if (ch === "\\") {
304
- escaped = true;
305
- continue;
306
- }
307
- if (ch === '"') inDouble = false;
308
- continue;
309
- }
310
-
311
- if (escaped) {
312
- current += ch;
313
- escaped = false;
314
- continue;
315
- }
316
- if (ch === "\\") {
317
- current += ch;
318
- escaped = true;
319
- continue;
320
- }
321
- if (ch === "'") {
322
- current += ch;
323
- inSingle = true;
324
- continue;
325
- }
326
- if (ch === '"') {
327
- current += ch;
328
- inDouble = true;
329
- continue;
330
- }
331
-
332
- const isSeparator =
333
- ch === ";" ||
334
- ch === "\n" ||
335
- ch === "\r" ||
336
- (ch === "&" && next === "&") ||
337
- (ch === "|" && next === "|");
338
- if (isSeparator) {
339
- parts.push(current.trim());
340
- current = "";
341
- if ((ch === "&" || ch === "|") && next === ch) {
342
- i += 1;
343
- }
344
- continue;
345
- }
346
-
347
- current += ch;
348
- }
349
-
350
- parts.push(current.trim());
351
- return parts;
352
- }
353
-
354
- function splitTopLevelPipes(command: string): string[] {
355
- const parts: string[] = [];
356
- let current = "";
357
- let inSingle = false;
358
- let inDouble = false;
359
- let escaped = false;
360
-
361
- for (let i = 0; i < command.length; i++) {
362
- const ch = command[i]!;
363
- const next = command[i + 1];
364
- const prev = i > 0 ? command[i - 1] : "";
365
-
366
- if (inSingle) {
367
- current += ch;
368
- if (ch === "'") inSingle = false;
369
- continue;
370
- }
371
-
372
- if (inDouble) {
373
- current += ch;
374
- if (escaped) {
375
- escaped = false;
376
- continue;
377
- }
378
- if (ch === "\\") {
379
- escaped = true;
380
- continue;
381
- }
382
- if (ch === '"') inDouble = false;
383
- continue;
384
- }
385
-
386
- if (escaped) {
387
- current += ch;
388
- escaped = false;
389
- continue;
390
- }
391
- if (ch === "\\") {
392
- current += ch;
393
- escaped = true;
394
- continue;
395
- }
396
- if (ch === "'") {
397
- current += ch;
398
- inSingle = true;
399
- continue;
400
- }
401
- if (ch === '"') {
402
- current += ch;
403
- inDouble = true;
404
- continue;
405
- }
406
-
407
- if (ch === "|" && next !== "|" && prev !== "|") {
408
- parts.push(current.trim());
409
- current = "";
410
- continue;
411
- }
412
-
413
- current += ch;
414
- }
415
-
416
- parts.push(current.trim());
417
- return parts;
418
- }
419
-
420
- /**
421
- * Matches shell output redirection operators that WRITE to the filesystem.
422
- * Excludes 2>&1 (stderr-to-stdout merge) which is read-only.
423
- *
424
- * Matches: >, >>, 2> (not followed by &), &>
425
- * Does NOT match: 2>&1, <, <<
426
- */
427
- const REDIRECTION_RE = /(?:>>|(?:^|[^2])>(?!&)|2>(?!&)|&>)/;
428
-
429
- /**
430
- * Determine whether a single simple command (no pipes/chains) is safe.
431
- * Returns true only when the command prefix is in the safe lists
432
- * AND the segment contains no output redirection.
433
- */
434
- function isSegmentSafe(segment: string): boolean {
435
- const trimmed = segment.trim();
436
- if (trimmed === "") return false;
437
-
438
- // Output redirection makes any command non-safe
439
- if (REDIRECTION_RE.test(trimmed)) return false;
440
-
441
- // Multi-word safe match first (e.g. "git status")
442
- for (const prefix of SAFE_MULTI) {
443
- if (trimmed === prefix || trimmed.startsWith(prefix + " ")) return true;
444
- }
445
-
446
- // Single-word: first token, path-stripped
447
- const firstToken = trimmed.split(/\s+/)[0] ?? "";
448
- return SAFE_SINGLE.has(stripPathPrefix(firstToken));
449
- }
450
-
451
- /**
452
- * Classify a shell command's risk level.
453
- *
454
- * - "safe" — skip approval, execute immediately
455
- * - "moderate" — handle via runtime policy (approval, allowlist, or autorun)
456
- * - "blocked" — never execute, return error immediately
457
- */
458
- export function classifyCommand(command: string): ShellRisk {
459
- const trimmed = command.trim();
460
-
461
- // Empty / whitespace-only — can't determine intent
462
- if (trimmed === "") return "moderate";
463
-
464
- // 0. Strip heredoc bodies once, upfront, so every downstream check
465
- // (whole-command, per-segment, per-pipe) operates on the sanitised text.
466
- // This prevents heredoc *data* from triggering blocked patterns.
467
- const stripped = stripHeredocBodies(trimmed);
468
-
469
- // 1. Check blocked patterns on the entire raw command
470
- if (isBlocked(stripped)) return "blocked";
471
-
472
- // 2. Subshells / backticks — can't statically analyze safely.
473
- // Check before splitting to avoid quote-unaware false positives.
474
- if (hasUnquotedSubshellOrBacktick(stripped)) return "moderate";
475
-
476
- // 3. Split on chain operators (&&, ||, ;, newline) outside quoted strings.
477
- const chainSegments = splitTopLevelChain(stripped);
478
- for (const seg of chainSegments) {
479
- if (isBlocked(seg)) return "blocked";
480
- }
481
-
482
- // 4. Split each chain segment on top-level pipes and check.
483
- const allSegments: string[] = [];
484
- for (const seg of chainSegments) {
485
- const piped = splitTopLevelPipes(seg);
486
- for (const p of piped) {
487
- if (isBlocked(p)) return "blocked";
488
- allSegments.push(p);
489
- }
490
- }
491
-
492
- // 5. Env-var assignment prefix (VAR=val command)
493
- if (/^[A-Za-z_]\w*=/.test(trimmed)) return "moderate";
494
-
495
- // 6. Check if ALL segments are safe (filter empty segments from chain splitting)
496
- const nonEmpty = allSegments.filter((s) => s.trim() !== "");
497
- if (nonEmpty.length > 0 && nonEmpty.every(isSegmentSafe)) return "safe";
498
-
499
- // 7. Default — needs approval
500
- return "moderate";
501
- }
502
-
503
- const EXPLICIT_SHELLS = new Set(["sh", "bash", "zsh", "ksh", "dash", "ash", "fish"]);
504
-
505
- /**
506
- * Classify argv-based process execution (spawn/exec) with shell-aware behavior.
507
- *
508
- * When callers explicitly invoke a shell interpreter with `-c`, classify the
509
- * payload command text itself. This preserves catastrophic-command blocking
510
- * while avoiding false positives that would block every `bash -c ...` call.
511
- */
512
- export function classifyExecInvocation(program: string, args: string[] = []): ShellRisk {
513
- const shellName = stripPathPrefix(program).toLowerCase();
514
- if (EXPLICIT_SHELLS.has(shellName) && args[0] === "-c" && typeof args[1] === "string") {
515
- return classifyCommand(args[1]);
516
- }
517
-
518
- return classifyCommand([program, ...args].join(" "));
519
- }