gitx.do 0.1.1 → 0.1.2

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 (356) hide show
  1. package/README.md +40 -353
  2. package/dist/do/logger.d.ts +50 -0
  3. package/dist/do/logger.d.ts.map +1 -0
  4. package/dist/do/logger.js +122 -0
  5. package/dist/do/logger.js.map +1 -0
  6. package/dist/{durable-object → do}/schema.d.ts +3 -3
  7. package/dist/do/schema.d.ts.map +1 -0
  8. package/dist/{durable-object → do}/schema.js +4 -3
  9. package/dist/do/schema.js.map +1 -0
  10. package/dist/do/types.d.ts +267 -0
  11. package/dist/do/types.d.ts.map +1 -0
  12. package/dist/do/types.js +62 -0
  13. package/dist/do/types.js.map +1 -0
  14. package/dist/index.d.ts +14 -469
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +31 -483
  17. package/dist/index.js.map +1 -1
  18. package/package.json +13 -21
  19. package/dist/cli/commands/add.d.ts +0 -176
  20. package/dist/cli/commands/add.d.ts.map +0 -1
  21. package/dist/cli/commands/add.js +0 -979
  22. package/dist/cli/commands/add.js.map +0 -1
  23. package/dist/cli/commands/blame.d.ts +0 -259
  24. package/dist/cli/commands/blame.d.ts.map +0 -1
  25. package/dist/cli/commands/blame.js +0 -609
  26. package/dist/cli/commands/blame.js.map +0 -1
  27. package/dist/cli/commands/branch.d.ts +0 -249
  28. package/dist/cli/commands/branch.d.ts.map +0 -1
  29. package/dist/cli/commands/branch.js +0 -693
  30. package/dist/cli/commands/branch.js.map +0 -1
  31. package/dist/cli/commands/checkout.d.ts +0 -73
  32. package/dist/cli/commands/checkout.d.ts.map +0 -1
  33. package/dist/cli/commands/checkout.js +0 -725
  34. package/dist/cli/commands/checkout.js.map +0 -1
  35. package/dist/cli/commands/commit.d.ts +0 -182
  36. package/dist/cli/commands/commit.d.ts.map +0 -1
  37. package/dist/cli/commands/commit.js +0 -457
  38. package/dist/cli/commands/commit.js.map +0 -1
  39. package/dist/cli/commands/diff.d.ts +0 -464
  40. package/dist/cli/commands/diff.d.ts.map +0 -1
  41. package/dist/cli/commands/diff.js +0 -959
  42. package/dist/cli/commands/diff.js.map +0 -1
  43. package/dist/cli/commands/log.d.ts +0 -239
  44. package/dist/cli/commands/log.d.ts.map +0 -1
  45. package/dist/cli/commands/log.js +0 -535
  46. package/dist/cli/commands/log.js.map +0 -1
  47. package/dist/cli/commands/merge.d.ts +0 -106
  48. package/dist/cli/commands/merge.d.ts.map +0 -1
  49. package/dist/cli/commands/merge.js +0 -852
  50. package/dist/cli/commands/merge.js.map +0 -1
  51. package/dist/cli/commands/review.d.ts +0 -457
  52. package/dist/cli/commands/review.d.ts.map +0 -1
  53. package/dist/cli/commands/review.js +0 -558
  54. package/dist/cli/commands/review.js.map +0 -1
  55. package/dist/cli/commands/stash.d.ts +0 -157
  56. package/dist/cli/commands/stash.d.ts.map +0 -1
  57. package/dist/cli/commands/stash.js +0 -655
  58. package/dist/cli/commands/stash.js.map +0 -1
  59. package/dist/cli/commands/status.d.ts +0 -269
  60. package/dist/cli/commands/status.d.ts.map +0 -1
  61. package/dist/cli/commands/status.js +0 -492
  62. package/dist/cli/commands/status.js.map +0 -1
  63. package/dist/cli/commands/web.d.ts +0 -199
  64. package/dist/cli/commands/web.d.ts.map +0 -1
  65. package/dist/cli/commands/web.js +0 -697
  66. package/dist/cli/commands/web.js.map +0 -1
  67. package/dist/cli/fs-adapter.d.ts +0 -656
  68. package/dist/cli/fs-adapter.d.ts.map +0 -1
  69. package/dist/cli/fs-adapter.js +0 -1177
  70. package/dist/cli/fs-adapter.js.map +0 -1
  71. package/dist/cli/fsx-cli-adapter.d.ts +0 -359
  72. package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
  73. package/dist/cli/fsx-cli-adapter.js +0 -619
  74. package/dist/cli/fsx-cli-adapter.js.map +0 -1
  75. package/dist/cli/index.d.ts +0 -387
  76. package/dist/cli/index.d.ts.map +0 -1
  77. package/dist/cli/index.js +0 -579
  78. package/dist/cli/index.js.map +0 -1
  79. package/dist/cli/ui/components/DiffView.d.ts +0 -12
  80. package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
  81. package/dist/cli/ui/components/DiffView.js +0 -11
  82. package/dist/cli/ui/components/DiffView.js.map +0 -1
  83. package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -10
  84. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
  85. package/dist/cli/ui/components/ErrorDisplay.js +0 -11
  86. package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
  87. package/dist/cli/ui/components/FuzzySearch.d.ts +0 -15
  88. package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
  89. package/dist/cli/ui/components/FuzzySearch.js +0 -12
  90. package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
  91. package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -10
  92. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
  93. package/dist/cli/ui/components/LoadingSpinner.js +0 -10
  94. package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
  95. package/dist/cli/ui/components/NavigationList.d.ts +0 -14
  96. package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
  97. package/dist/cli/ui/components/NavigationList.js +0 -11
  98. package/dist/cli/ui/components/NavigationList.js.map +0 -1
  99. package/dist/cli/ui/components/ScrollableContent.d.ts +0 -13
  100. package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
  101. package/dist/cli/ui/components/ScrollableContent.js +0 -11
  102. package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
  103. package/dist/cli/ui/components/index.d.ts +0 -7
  104. package/dist/cli/ui/components/index.d.ts.map +0 -1
  105. package/dist/cli/ui/components/index.js +0 -9
  106. package/dist/cli/ui/components/index.js.map +0 -1
  107. package/dist/cli/ui/terminal-ui.d.ts +0 -85
  108. package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
  109. package/dist/cli/ui/terminal-ui.js +0 -121
  110. package/dist/cli/ui/terminal-ui.js.map +0 -1
  111. package/dist/do/BashModule.d.ts +0 -871
  112. package/dist/do/BashModule.d.ts.map +0 -1
  113. package/dist/do/BashModule.js +0 -1143
  114. package/dist/do/BashModule.js.map +0 -1
  115. package/dist/do/FsModule.d.ts +0 -612
  116. package/dist/do/FsModule.d.ts.map +0 -1
  117. package/dist/do/FsModule.js +0 -1120
  118. package/dist/do/FsModule.js.map +0 -1
  119. package/dist/do/GitModule.d.ts +0 -635
  120. package/dist/do/GitModule.d.ts.map +0 -1
  121. package/dist/do/GitModule.js +0 -784
  122. package/dist/do/GitModule.js.map +0 -1
  123. package/dist/do/GitRepoDO.d.ts +0 -281
  124. package/dist/do/GitRepoDO.d.ts.map +0 -1
  125. package/dist/do/GitRepoDO.js +0 -479
  126. package/dist/do/GitRepoDO.js.map +0 -1
  127. package/dist/do/bash-ast.d.ts +0 -246
  128. package/dist/do/bash-ast.d.ts.map +0 -1
  129. package/dist/do/bash-ast.js +0 -888
  130. package/dist/do/bash-ast.js.map +0 -1
  131. package/dist/do/container-executor.d.ts +0 -491
  132. package/dist/do/container-executor.d.ts.map +0 -1
  133. package/dist/do/container-executor.js +0 -731
  134. package/dist/do/container-executor.js.map +0 -1
  135. package/dist/do/index.d.ts +0 -53
  136. package/dist/do/index.d.ts.map +0 -1
  137. package/dist/do/index.js +0 -91
  138. package/dist/do/index.js.map +0 -1
  139. package/dist/do/tiered-storage.d.ts +0 -403
  140. package/dist/do/tiered-storage.d.ts.map +0 -1
  141. package/dist/do/tiered-storage.js +0 -689
  142. package/dist/do/tiered-storage.js.map +0 -1
  143. package/dist/do/withBash.d.ts +0 -231
  144. package/dist/do/withBash.d.ts.map +0 -1
  145. package/dist/do/withBash.js +0 -244
  146. package/dist/do/withBash.js.map +0 -1
  147. package/dist/do/withFs.d.ts +0 -237
  148. package/dist/do/withFs.d.ts.map +0 -1
  149. package/dist/do/withFs.js +0 -387
  150. package/dist/do/withFs.js.map +0 -1
  151. package/dist/do/withGit.d.ts +0 -180
  152. package/dist/do/withGit.d.ts.map +0 -1
  153. package/dist/do/withGit.js +0 -271
  154. package/dist/do/withGit.js.map +0 -1
  155. package/dist/durable-object/object-store.d.ts +0 -633
  156. package/dist/durable-object/object-store.d.ts.map +0 -1
  157. package/dist/durable-object/object-store.js +0 -1164
  158. package/dist/durable-object/object-store.js.map +0 -1
  159. package/dist/durable-object/schema.d.ts.map +0 -1
  160. package/dist/durable-object/schema.js.map +0 -1
  161. package/dist/durable-object/wal.d.ts +0 -416
  162. package/dist/durable-object/wal.d.ts.map +0 -1
  163. package/dist/durable-object/wal.js +0 -445
  164. package/dist/durable-object/wal.js.map +0 -1
  165. package/dist/mcp/adapter.d.ts +0 -772
  166. package/dist/mcp/adapter.d.ts.map +0 -1
  167. package/dist/mcp/adapter.js +0 -895
  168. package/dist/mcp/adapter.js.map +0 -1
  169. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
  170. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
  171. package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
  172. package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
  173. package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
  174. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
  175. package/dist/mcp/sandbox/object-store-proxy.js +0 -30
  176. package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
  177. package/dist/mcp/sandbox/template.d.ts +0 -17
  178. package/dist/mcp/sandbox/template.d.ts.map +0 -1
  179. package/dist/mcp/sandbox/template.js +0 -71
  180. package/dist/mcp/sandbox/template.js.map +0 -1
  181. package/dist/mcp/sandbox.d.ts +0 -764
  182. package/dist/mcp/sandbox.d.ts.map +0 -1
  183. package/dist/mcp/sandbox.js +0 -1362
  184. package/dist/mcp/sandbox.js.map +0 -1
  185. package/dist/mcp/sdk-adapter.d.ts +0 -835
  186. package/dist/mcp/sdk-adapter.d.ts.map +0 -1
  187. package/dist/mcp/sdk-adapter.js +0 -974
  188. package/dist/mcp/sdk-adapter.js.map +0 -1
  189. package/dist/mcp/tools/do.d.ts +0 -32
  190. package/dist/mcp/tools/do.d.ts.map +0 -1
  191. package/dist/mcp/tools/do.js +0 -117
  192. package/dist/mcp/tools/do.js.map +0 -1
  193. package/dist/mcp/tools.d.ts +0 -548
  194. package/dist/mcp/tools.d.ts.map +0 -1
  195. package/dist/mcp/tools.js +0 -3170
  196. package/dist/mcp/tools.js.map +0 -1
  197. package/dist/ops/blame.d.ts +0 -551
  198. package/dist/ops/blame.d.ts.map +0 -1
  199. package/dist/ops/blame.js +0 -1037
  200. package/dist/ops/blame.js.map +0 -1
  201. package/dist/ops/branch.d.ts +0 -766
  202. package/dist/ops/branch.d.ts.map +0 -1
  203. package/dist/ops/branch.js +0 -950
  204. package/dist/ops/branch.js.map +0 -1
  205. package/dist/ops/commit-traversal.d.ts +0 -349
  206. package/dist/ops/commit-traversal.d.ts.map +0 -1
  207. package/dist/ops/commit-traversal.js +0 -821
  208. package/dist/ops/commit-traversal.js.map +0 -1
  209. package/dist/ops/commit.d.ts +0 -555
  210. package/dist/ops/commit.d.ts.map +0 -1
  211. package/dist/ops/commit.js +0 -826
  212. package/dist/ops/commit.js.map +0 -1
  213. package/dist/ops/merge-base.d.ts +0 -397
  214. package/dist/ops/merge-base.d.ts.map +0 -1
  215. package/dist/ops/merge-base.js +0 -691
  216. package/dist/ops/merge-base.js.map +0 -1
  217. package/dist/ops/merge.d.ts +0 -855
  218. package/dist/ops/merge.d.ts.map +0 -1
  219. package/dist/ops/merge.js +0 -1551
  220. package/dist/ops/merge.js.map +0 -1
  221. package/dist/ops/tag.d.ts +0 -247
  222. package/dist/ops/tag.d.ts.map +0 -1
  223. package/dist/ops/tag.js +0 -649
  224. package/dist/ops/tag.js.map +0 -1
  225. package/dist/ops/tree-builder.d.ts +0 -178
  226. package/dist/ops/tree-builder.d.ts.map +0 -1
  227. package/dist/ops/tree-builder.js +0 -271
  228. package/dist/ops/tree-builder.js.map +0 -1
  229. package/dist/ops/tree-diff.d.ts +0 -291
  230. package/dist/ops/tree-diff.d.ts.map +0 -1
  231. package/dist/ops/tree-diff.js +0 -705
  232. package/dist/ops/tree-diff.js.map +0 -1
  233. package/dist/pack/delta.d.ts +0 -248
  234. package/dist/pack/delta.d.ts.map +0 -1
  235. package/dist/pack/delta.js +0 -740
  236. package/dist/pack/delta.js.map +0 -1
  237. package/dist/pack/format.d.ts +0 -446
  238. package/dist/pack/format.d.ts.map +0 -1
  239. package/dist/pack/format.js +0 -572
  240. package/dist/pack/format.js.map +0 -1
  241. package/dist/pack/full-generation.d.ts +0 -612
  242. package/dist/pack/full-generation.d.ts.map +0 -1
  243. package/dist/pack/full-generation.js +0 -1378
  244. package/dist/pack/full-generation.js.map +0 -1
  245. package/dist/pack/generation.d.ts +0 -441
  246. package/dist/pack/generation.d.ts.map +0 -1
  247. package/dist/pack/generation.js +0 -707
  248. package/dist/pack/generation.js.map +0 -1
  249. package/dist/pack/index.d.ts +0 -502
  250. package/dist/pack/index.d.ts.map +0 -1
  251. package/dist/pack/index.js +0 -833
  252. package/dist/pack/index.js.map +0 -1
  253. package/dist/refs/branch.d.ts +0 -683
  254. package/dist/refs/branch.d.ts.map +0 -1
  255. package/dist/refs/branch.js +0 -881
  256. package/dist/refs/branch.js.map +0 -1
  257. package/dist/refs/storage.d.ts +0 -833
  258. package/dist/refs/storage.d.ts.map +0 -1
  259. package/dist/refs/storage.js +0 -1023
  260. package/dist/refs/storage.js.map +0 -1
  261. package/dist/refs/tag.d.ts +0 -860
  262. package/dist/refs/tag.d.ts.map +0 -1
  263. package/dist/refs/tag.js +0 -996
  264. package/dist/refs/tag.js.map +0 -1
  265. package/dist/storage/backend.d.ts +0 -425
  266. package/dist/storage/backend.d.ts.map +0 -1
  267. package/dist/storage/backend.js +0 -41
  268. package/dist/storage/backend.js.map +0 -1
  269. package/dist/storage/fsx-adapter.d.ts +0 -204
  270. package/dist/storage/fsx-adapter.d.ts.map +0 -1
  271. package/dist/storage/fsx-adapter.js +0 -518
  272. package/dist/storage/fsx-adapter.js.map +0 -1
  273. package/dist/storage/lru-cache.d.ts +0 -691
  274. package/dist/storage/lru-cache.d.ts.map +0 -1
  275. package/dist/storage/lru-cache.js +0 -813
  276. package/dist/storage/lru-cache.js.map +0 -1
  277. package/dist/storage/object-index.d.ts +0 -585
  278. package/dist/storage/object-index.d.ts.map +0 -1
  279. package/dist/storage/object-index.js +0 -532
  280. package/dist/storage/object-index.js.map +0 -1
  281. package/dist/storage/r2-pack.d.ts +0 -1257
  282. package/dist/storage/r2-pack.d.ts.map +0 -1
  283. package/dist/storage/r2-pack.js +0 -1773
  284. package/dist/storage/r2-pack.js.map +0 -1
  285. package/dist/tiered/cdc-pipeline.d.ts +0 -1888
  286. package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
  287. package/dist/tiered/cdc-pipeline.js +0 -1880
  288. package/dist/tiered/cdc-pipeline.js.map +0 -1
  289. package/dist/tiered/migration.d.ts +0 -1104
  290. package/dist/tiered/migration.d.ts.map +0 -1
  291. package/dist/tiered/migration.js +0 -1217
  292. package/dist/tiered/migration.js.map +0 -1
  293. package/dist/tiered/parquet-writer.d.ts +0 -1145
  294. package/dist/tiered/parquet-writer.d.ts.map +0 -1
  295. package/dist/tiered/parquet-writer.js +0 -1183
  296. package/dist/tiered/parquet-writer.js.map +0 -1
  297. package/dist/tiered/read-path.d.ts +0 -835
  298. package/dist/tiered/read-path.d.ts.map +0 -1
  299. package/dist/tiered/read-path.js +0 -487
  300. package/dist/tiered/read-path.js.map +0 -1
  301. package/dist/types/capability.d.ts +0 -1385
  302. package/dist/types/capability.d.ts.map +0 -1
  303. package/dist/types/capability.js +0 -36
  304. package/dist/types/capability.js.map +0 -1
  305. package/dist/types/index.d.ts +0 -13
  306. package/dist/types/index.d.ts.map +0 -1
  307. package/dist/types/index.js +0 -18
  308. package/dist/types/index.js.map +0 -1
  309. package/dist/types/interfaces.d.ts +0 -673
  310. package/dist/types/interfaces.d.ts.map +0 -1
  311. package/dist/types/interfaces.js +0 -26
  312. package/dist/types/interfaces.js.map +0 -1
  313. package/dist/types/objects.d.ts +0 -692
  314. package/dist/types/objects.d.ts.map +0 -1
  315. package/dist/types/objects.js +0 -837
  316. package/dist/types/objects.js.map +0 -1
  317. package/dist/types/storage.d.ts +0 -603
  318. package/dist/types/storage.d.ts.map +0 -1
  319. package/dist/types/storage.js +0 -191
  320. package/dist/types/storage.js.map +0 -1
  321. package/dist/types/worker-loader.d.ts +0 -60
  322. package/dist/types/worker-loader.d.ts.map +0 -1
  323. package/dist/types/worker-loader.js +0 -62
  324. package/dist/types/worker-loader.js.map +0 -1
  325. package/dist/utils/hash.d.ts +0 -198
  326. package/dist/utils/hash.d.ts.map +0 -1
  327. package/dist/utils/hash.js +0 -272
  328. package/dist/utils/hash.js.map +0 -1
  329. package/dist/utils/sha1.d.ts +0 -325
  330. package/dist/utils/sha1.d.ts.map +0 -1
  331. package/dist/utils/sha1.js +0 -635
  332. package/dist/utils/sha1.js.map +0 -1
  333. package/dist/wire/capabilities.d.ts +0 -1044
  334. package/dist/wire/capabilities.d.ts.map +0 -1
  335. package/dist/wire/capabilities.js +0 -941
  336. package/dist/wire/capabilities.js.map +0 -1
  337. package/dist/wire/path-security.d.ts +0 -157
  338. package/dist/wire/path-security.d.ts.map +0 -1
  339. package/dist/wire/path-security.js +0 -307
  340. package/dist/wire/path-security.js.map +0 -1
  341. package/dist/wire/pkt-line.d.ts +0 -345
  342. package/dist/wire/pkt-line.d.ts.map +0 -1
  343. package/dist/wire/pkt-line.js +0 -381
  344. package/dist/wire/pkt-line.js.map +0 -1
  345. package/dist/wire/receive-pack.d.ts +0 -1059
  346. package/dist/wire/receive-pack.d.ts.map +0 -1
  347. package/dist/wire/receive-pack.js +0 -1414
  348. package/dist/wire/receive-pack.js.map +0 -1
  349. package/dist/wire/smart-http.d.ts +0 -799
  350. package/dist/wire/smart-http.d.ts.map +0 -1
  351. package/dist/wire/smart-http.js +0 -945
  352. package/dist/wire/smart-http.js.map +0 -1
  353. package/dist/wire/upload-pack.d.ts +0 -727
  354. package/dist/wire/upload-pack.d.ts.map +0 -1
  355. package/dist/wire/upload-pack.js +0 -1141
  356. package/dist/wire/upload-pack.js.map +0 -1
@@ -1,888 +0,0 @@
1
- /**
2
- * @fileoverview Bash AST Parser and Safety Analyzer
3
- *
4
- * This module provides a bash command parser that produces an Abstract Syntax Tree (AST)
5
- * for shell commands, along with safety analysis based on AST node inspection.
6
- *
7
- * The parser handles:
8
- * - Simple commands (ls, cat, etc.)
9
- * - Pipelines (cmd1 | cmd2)
10
- * - Command lists (cmd1 && cmd2, cmd1 || cmd2, cmd1 ; cmd2)
11
- * - Subshells ($(...) and `...`)
12
- * - Redirections (>, >>, <, 2>&1)
13
- * - Variable expansion ($VAR, ${VAR})
14
- * - Quoting (single, double, and escape)
15
- *
16
- * @module do/bash-ast
17
- *
18
- * @example
19
- * ```typescript
20
- * import { parseBashCommand, analyzeASTSafety } from './bash-ast'
21
- *
22
- * const ast = parseBashCommand('rm -rf /')
23
- * const safety = analyzeASTSafety(ast)
24
- * if (safety.dangerous) {
25
- * console.log(`Command blocked: ${safety.reason}`)
26
- * }
27
- * ```
28
- */
29
- /**
30
- * Tokenize a bash command string.
31
- */
32
- function tokenize(input) {
33
- const tokens = [];
34
- let pos = 0;
35
- const operators = ['&&', '||', ';;', ';&', ';;&', '|&', '|', ';', '&', '(', ')'];
36
- const redirectOps = ['>>>', '>>', '>&', '&>>', '&>', '<<-', '<<<', '<<', '<>', '<&', '<', '>|', '>'];
37
- while (pos < input.length) {
38
- // Skip whitespace
39
- if (/\s/.test(input[pos]) && input[pos] !== '\n') {
40
- pos++;
41
- continue;
42
- }
43
- // Newline
44
- if (input[pos] === '\n') {
45
- tokens.push({ type: 'newline', value: '\n', start: pos, end: pos + 1 });
46
- pos++;
47
- continue;
48
- }
49
- // Comments
50
- if (input[pos] === '#') {
51
- while (pos < input.length && input[pos] !== '\n') {
52
- pos++;
53
- }
54
- continue;
55
- }
56
- // Check for redirect operators (before general operators)
57
- let foundRedirect = false;
58
- for (const op of redirectOps) {
59
- if (input.slice(pos, pos + op.length) === op) {
60
- // Check for number prefix (like 2>)
61
- let actualStart = pos;
62
- if (pos > 0 && /\d/.test(input[pos - 1])) {
63
- // Check if previous char was actually a number redirect prefix
64
- const prevToken = tokens[tokens.length - 1];
65
- if (prevToken && prevToken.type === 'word' && /^\d+$/.test(prevToken.value)) {
66
- tokens.pop();
67
- actualStart = prevToken.start;
68
- }
69
- }
70
- tokens.push({ type: 'redirect', value: input.slice(actualStart, pos + op.length), start: actualStart, end: pos + op.length });
71
- pos += op.length;
72
- foundRedirect = true;
73
- break;
74
- }
75
- }
76
- if (foundRedirect)
77
- continue;
78
- // Check for operators
79
- let foundOp = false;
80
- for (const op of operators) {
81
- if (input.slice(pos, pos + op.length) === op) {
82
- tokens.push({ type: 'operator', value: op, start: pos, end: pos + op.length });
83
- pos += op.length;
84
- foundOp = true;
85
- break;
86
- }
87
- }
88
- if (foundOp)
89
- continue;
90
- // Word (including quoted strings)
91
- const wordStart = pos;
92
- let word = '';
93
- while (pos < input.length) {
94
- const ch = input[pos];
95
- // End of word
96
- if (/\s/.test(ch) || operators.some(op => input.slice(pos, pos + op.length) === op) ||
97
- redirectOps.some(op => input.slice(pos, pos + op.length) === op)) {
98
- break;
99
- }
100
- // Single quoted string
101
- if (ch === "'") {
102
- pos++;
103
- while (pos < input.length && input[pos] !== "'") {
104
- word += input[pos];
105
- pos++;
106
- }
107
- if (pos < input.length)
108
- pos++; // Skip closing quote
109
- continue;
110
- }
111
- // Double quoted string
112
- if (ch === '"') {
113
- pos++;
114
- while (pos < input.length && input[pos] !== '"') {
115
- if (input[pos] === '\\' && pos + 1 < input.length) {
116
- word += input[pos + 1];
117
- pos += 2;
118
- }
119
- else {
120
- word += input[pos];
121
- pos++;
122
- }
123
- }
124
- if (pos < input.length)
125
- pos++; // Skip closing quote
126
- continue;
127
- }
128
- // Backslash escape
129
- if (ch === '\\' && pos + 1 < input.length) {
130
- word += input[pos + 1];
131
- pos += 2;
132
- continue;
133
- }
134
- // Command substitution $()
135
- if (ch === '$' && pos + 1 < input.length && input[pos + 1] === '(') {
136
- let depth = 1;
137
- word += '$(';
138
- pos += 2;
139
- while (pos < input.length && depth > 0) {
140
- if (input[pos] === '(')
141
- depth++;
142
- else if (input[pos] === ')')
143
- depth--;
144
- if (depth > 0)
145
- word += input[pos];
146
- pos++;
147
- }
148
- word += ')';
149
- continue;
150
- }
151
- // Backtick substitution
152
- if (ch === '`') {
153
- word += ch;
154
- pos++;
155
- while (pos < input.length && input[pos] !== '`') {
156
- if (input[pos] === '\\' && pos + 1 < input.length) {
157
- word += input[pos] + input[pos + 1];
158
- pos += 2;
159
- }
160
- else {
161
- word += input[pos];
162
- pos++;
163
- }
164
- }
165
- if (pos < input.length) {
166
- word += '`';
167
- pos++;
168
- }
169
- continue;
170
- }
171
- // Variable expansion
172
- if (ch === '$') {
173
- word += ch;
174
- pos++;
175
- if (pos < input.length && input[pos] === '{') {
176
- word += '{';
177
- pos++;
178
- while (pos < input.length && input[pos] !== '}') {
179
- word += input[pos];
180
- pos++;
181
- }
182
- if (pos < input.length) {
183
- word += '}';
184
- pos++;
185
- }
186
- }
187
- else {
188
- while (pos < input.length && /[a-zA-Z0-9_]/.test(input[pos])) {
189
- word += input[pos];
190
- pos++;
191
- }
192
- }
193
- continue;
194
- }
195
- // Regular character
196
- word += ch;
197
- pos++;
198
- }
199
- if (word.length > 0) {
200
- tokens.push({ type: 'word', value: word, start: wordStart, end: pos });
201
- }
202
- }
203
- tokens.push({ type: 'eof', value: '', start: pos, end: pos });
204
- return tokens;
205
- }
206
- /**
207
- * Peek at the current token.
208
- */
209
- function peek(state) {
210
- return state.tokens[state.pos] ?? { type: 'eof', value: '', start: state.input.length, end: state.input.length };
211
- }
212
- /**
213
- * Consume the current token.
214
- */
215
- function consume(state) {
216
- return state.tokens[state.pos++] ?? { type: 'eof', value: '', start: state.input.length, end: state.input.length };
217
- }
218
- /**
219
- * Check if current token matches.
220
- */
221
- function match(state, type, value) {
222
- const token = peek(state);
223
- if (token.type !== type)
224
- return false;
225
- if (value !== undefined && token.value !== value)
226
- return false;
227
- return true;
228
- }
229
- /**
230
- * Parse a word token into a WordNode.
231
- */
232
- function parseWord(state) {
233
- const token = consume(state);
234
- let quoted = 'none';
235
- let expandable = false;
236
- // Check for quoting and expansion
237
- if (token.value.includes('$'))
238
- expandable = true;
239
- if (token.value.startsWith("'"))
240
- quoted = 'single';
241
- else if (token.value.startsWith('"'))
242
- quoted = 'double';
243
- else if (token.value.includes('\\'))
244
- quoted = 'escaped';
245
- return {
246
- type: 'word',
247
- raw: state.input.slice(token.start, token.end),
248
- value: token.value,
249
- quoted,
250
- expandable,
251
- start: token.start,
252
- end: token.end,
253
- };
254
- }
255
- /**
256
- * Parse a redirection.
257
- */
258
- function parseRedirect(state) {
259
- if (!match(state, 'redirect'))
260
- return null;
261
- const redirectToken = consume(state);
262
- const operator = redirectToken.value.replace(/^\d+/, '');
263
- const fd = /^\d+/.test(redirectToken.value) ? parseInt(redirectToken.value.match(/^\d+/)[0]) : undefined;
264
- // Get target
265
- if (!match(state, 'word')) {
266
- // Missing redirect target
267
- return null;
268
- }
269
- const target = parseWord(state);
270
- return {
271
- type: 'redirect',
272
- raw: state.input.slice(redirectToken.start, target.end),
273
- operator,
274
- target,
275
- fd,
276
- start: redirectToken.start,
277
- end: target.end,
278
- };
279
- }
280
- /**
281
- * Parse a simple command.
282
- */
283
- function parseCommand(state) {
284
- // Check for subshell
285
- if (match(state, 'operator', '(')) {
286
- const startToken = consume(state);
287
- const body = parseList(state);
288
- if (match(state, 'operator', ')')) {
289
- consume(state);
290
- }
291
- return {
292
- type: 'subshell',
293
- raw: state.input.slice(startToken.start, peek(state).start),
294
- body,
295
- style: '()',
296
- start: startToken.start,
297
- end: peek(state).start,
298
- };
299
- }
300
- // Skip newlines
301
- while (match(state, 'newline'))
302
- consume(state);
303
- if (!match(state, 'word'))
304
- return null;
305
- const startPos = peek(state).start;
306
- const assignments = [];
307
- const args = [];
308
- const redirects = [];
309
- // Check for assignments at the start
310
- while (match(state, 'word') && /^[a-zA-Z_][a-zA-Z0-9_]*=/.test(peek(state).value)) {
311
- const token = consume(state);
312
- const eqPos = token.value.indexOf('=');
313
- assignments.push({
314
- type: 'assignment',
315
- raw: token.value,
316
- name: token.value.slice(0, eqPos),
317
- value: {
318
- type: 'word',
319
- raw: token.value.slice(eqPos + 1),
320
- value: token.value.slice(eqPos + 1),
321
- quoted: 'none',
322
- expandable: token.value.slice(eqPos + 1).includes('$'),
323
- start: token.start + eqPos + 1,
324
- end: token.end,
325
- },
326
- start: token.start,
327
- end: token.end,
328
- });
329
- }
330
- // Get command name
331
- if (!match(state, 'word')) {
332
- // Assignment-only command
333
- if (assignments.length > 0) {
334
- const lastAssign = assignments[assignments.length - 1];
335
- return {
336
- type: 'command',
337
- raw: state.input.slice(startPos, lastAssign.end),
338
- name: { type: 'word', raw: '', value: '', quoted: 'none', expandable: false, start: startPos, end: startPos },
339
- args: [],
340
- redirects: [],
341
- assignments,
342
- background: false,
343
- start: startPos,
344
- end: lastAssign.end,
345
- };
346
- }
347
- return null;
348
- }
349
- const name = parseWord(state);
350
- // Parse arguments and redirects
351
- while (true) {
352
- const redirect = parseRedirect(state);
353
- if (redirect) {
354
- redirects.push(redirect);
355
- continue;
356
- }
357
- if (match(state, 'word')) {
358
- args.push(parseWord(state));
359
- continue;
360
- }
361
- break;
362
- }
363
- const endPos = args.length > 0 ? args[args.length - 1].end :
364
- redirects.length > 0 ? redirects[redirects.length - 1].end :
365
- name.end;
366
- return {
367
- type: 'command',
368
- raw: state.input.slice(startPos, endPos),
369
- name,
370
- args,
371
- redirects,
372
- assignments,
373
- background: false,
374
- start: startPos,
375
- end: endPos,
376
- };
377
- }
378
- /**
379
- * Parse a pipeline.
380
- */
381
- function parsePipeline(state) {
382
- // Check for negation
383
- const negated = match(state, 'word') && peek(state).value === '!';
384
- if (negated)
385
- consume(state);
386
- const commands = [];
387
- const first = parseCommand(state);
388
- if (!first)
389
- return null;
390
- commands.push(first);
391
- // Parse pipe chain
392
- while (match(state, 'operator', '|') || match(state, 'operator', '|&')) {
393
- consume(state);
394
- while (match(state, 'newline'))
395
- consume(state);
396
- const next = parseCommand(state);
397
- if (!next)
398
- break;
399
- commands.push(next);
400
- }
401
- const startPos = commands[0].start;
402
- const endPos = commands[commands.length - 1].end;
403
- return {
404
- type: 'pipeline',
405
- raw: state.input.slice(startPos, endPos),
406
- commands,
407
- negated,
408
- start: startPos,
409
- end: endPos,
410
- };
411
- }
412
- /**
413
- * Parse a command list.
414
- */
415
- function parseList(state) {
416
- const pipelines = [];
417
- const operators = [];
418
- // Skip initial newlines
419
- while (match(state, 'newline'))
420
- consume(state);
421
- const first = parsePipeline(state);
422
- if (first) {
423
- pipelines.push(first);
424
- while (true) {
425
- // Check for list operators
426
- if (match(state, 'operator', '&&') || match(state, 'operator', '||') ||
427
- match(state, 'operator', ';') || match(state, 'operator', '&')) {
428
- const op = consume(state).value;
429
- operators.push(op);
430
- // Mark previous command as background if &
431
- if (op === '&' && pipelines.length > 0) {
432
- const lastPipeline = pipelines[pipelines.length - 1];
433
- if (lastPipeline.commands.length > 0) {
434
- const lastCmd = lastPipeline.commands[lastPipeline.commands.length - 1];
435
- if (lastCmd.type === 'command') {
436
- lastCmd.background = true;
437
- }
438
- }
439
- }
440
- // Skip newlines after operator
441
- while (match(state, 'newline'))
442
- consume(state);
443
- const next = parsePipeline(state);
444
- if (next) {
445
- pipelines.push(next);
446
- }
447
- else {
448
- break;
449
- }
450
- }
451
- else if (match(state, 'newline')) {
452
- consume(state);
453
- while (match(state, 'newline'))
454
- consume(state);
455
- // Check if there's more
456
- const next = parsePipeline(state);
457
- if (next) {
458
- operators.push(';');
459
- pipelines.push(next);
460
- }
461
- else {
462
- break;
463
- }
464
- }
465
- else {
466
- break;
467
- }
468
- }
469
- }
470
- const startPos = pipelines.length > 0 ? pipelines[0].start : 0;
471
- const endPos = pipelines.length > 0 ? pipelines[pipelines.length - 1].end : 0;
472
- return {
473
- type: 'list',
474
- raw: state.input.slice(startPos, endPos),
475
- pipelines,
476
- operators,
477
- start: startPos,
478
- end: endPos,
479
- };
480
- }
481
- /**
482
- * Parse a bash command string into an AST.
483
- *
484
- * @param input - The bash command string to parse
485
- * @returns The parsed AST (ListNode at the top level)
486
- *
487
- * @example
488
- * ```typescript
489
- * const ast = parseBashCommand('ls -la | grep foo && echo done')
490
- * // Returns a ListNode with two pipelines
491
- * ```
492
- */
493
- export function parseBashCommand(input) {
494
- const tokens = tokenize(input);
495
- const state = { tokens, pos: 0, input };
496
- return parseList(state);
497
- }
498
- // ============================================================================
499
- // Safety Analyzer
500
- // ============================================================================
501
- /**
502
- * Commands considered dangerous by default.
503
- */
504
- const DANGEROUS_COMMANDS = new Set([
505
- 'rm', 'rmdir', 'dd', 'mkfs', 'fdisk', 'format',
506
- 'shutdown', 'reboot', 'halt', 'poweroff', 'init',
507
- 'kill', 'killall', 'pkill',
508
- 'chmod', 'chown', 'chgrp',
509
- 'mount', 'umount', 'mkswap', 'swapon', 'swapoff',
510
- ]);
511
- /**
512
- * Commands that are considered safe (read-only).
513
- */
514
- const SAFE_COMMANDS = new Set([
515
- 'cat', 'ls', 'pwd', 'echo', 'head', 'tail', 'wc',
516
- 'grep', 'find', 'which', 'whereis', 'type', 'file',
517
- 'date', 'whoami', 'hostname', 'uname', 'env', 'printenv',
518
- 'basename', 'dirname', 'realpath', 'readlink',
519
- ]);
520
- /**
521
- * Commands with low impact (create files/dirs).
522
- */
523
- const LOW_IMPACT_COMMANDS = new Set([
524
- 'touch', 'mkdir', 'cp', 'tee',
525
- ]);
526
- /**
527
- * Commands with medium impact (modify files).
528
- */
529
- const MEDIUM_IMPACT_COMMANDS = new Set([
530
- 'mv', 'sed', 'awk', 'sort', 'uniq', 'cut', 'paste',
531
- 'tr', 'patch', 'diff',
532
- ]);
533
- /**
534
- * Critical patterns that should ALWAYS be blocked, regardless of confirmation.
535
- * These patterns represent commands that could cause catastrophic, irreversible damage.
536
- */
537
- const CRITICAL_PATTERNS = [
538
- // rm -rf / or rm -rf /* (delete entire filesystem)
539
- { pattern: /\brm\s+(-[rfvI]+\s+)*\/\s*$/, message: 'Cannot execute rm targeting root filesystem' },
540
- { pattern: /\brm\s+(-[rfvI]+\s+)*\/\*/, message: 'Cannot execute rm with wildcard on root' },
541
- // rm -rf with --no-preserve-root (explicit bypass of safety)
542
- { pattern: /\brm\s+.*--no-preserve-root/, message: 'Cannot execute rm with --no-preserve-root' },
543
- // Fork bomb patterns
544
- { pattern: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/, message: 'Fork bomb detected' },
545
- { pattern: /\.\(\)\s*\{\s*\.\s*\|\s*\.\s*&\s*\}\s*;\s*\./, message: 'Fork bomb variant detected' },
546
- // Writing random data to block devices
547
- { pattern: /\bdd\s+.*if=\/dev\/(u?random|zero)\s+.*of=\/dev\/[hs]d[a-z]/, message: 'Cannot write to disk device' },
548
- { pattern: /\bdd\s+.*of=\/dev\/[hs]d[a-z].*if=\/dev\/(u?random|zero)/, message: 'Cannot write to disk device' },
549
- // Overwriting MBR
550
- { pattern: /\bdd\s+.*of=\/dev\/[hs]d[a-z]\s+.*bs=\d+\s+.*count=1/, message: 'Cannot overwrite disk boot sector' },
551
- // mkfs on system devices without confirmation
552
- { pattern: /\bmkfs(\.\w+)?\s+(-[a-zA-Z]+\s+)*\/dev\/[hs]d[a-z]\d*/, message: 'Cannot format disk device' },
553
- // Direct writes to /dev/sda, /dev/hda, /dev/nvme, etc.
554
- { pattern: />\s*\/dev\/[hs]d[a-z]/, message: 'Cannot redirect output to disk device' },
555
- { pattern: />\s*\/dev\/nvme\d+n\d+/, message: 'Cannot redirect output to NVMe device' },
556
- // Kernel panic triggers
557
- { pattern: /echo\s+[cso]\s*>\s*\/proc\/sysrq-trigger/, message: 'Cannot trigger kernel sysrq' },
558
- // Memory bomb / consuming all memory
559
- { pattern: /\bwhile\s*\(\s*true\s*\)\s*;\s*do\s+\w+\s*=\s*\$\w+\$\w+/, message: 'Potential memory bomb detected' },
560
- // Overwriting critical boot files
561
- { pattern: />\s*\/boot\//, message: 'Cannot write to /boot' },
562
- { pattern: /\brm\s+(-[rfvI]+\s+)*\/boot/, message: 'Cannot delete /boot' },
563
- // System destruction via mv
564
- { pattern: /\bmv\s+\/\s+/, message: 'Cannot move root filesystem' },
565
- { pattern: /\bmv\s+(-[a-zA-Z]+\s+)*\/\s+/, message: 'Cannot move root filesystem' },
566
- ];
567
- /**
568
- * Check if a command matches any critical pattern.
569
- * Returns the matching pattern info if found, null otherwise.
570
- */
571
- function matchesCriticalPattern(input) {
572
- for (const { pattern, message } of CRITICAL_PATTERNS) {
573
- if (pattern.test(input)) {
574
- return { pattern, message };
575
- }
576
- }
577
- return null;
578
- }
579
- /**
580
- * Check if a command argument represents a dangerous path.
581
- */
582
- function isDangerousPath(arg) {
583
- // Root path
584
- if (arg === '/' || arg === '/*')
585
- return true;
586
- // Device paths
587
- if (arg.startsWith('/dev/'))
588
- return true;
589
- // System directories
590
- const systemPaths = ['/etc', '/bin', '/sbin', '/usr', '/boot', '/lib', '/lib64', '/var', '/sys', '/proc'];
591
- for (const path of systemPaths) {
592
- if (arg === path || arg.startsWith(path + '/'))
593
- return true;
594
- }
595
- // Home directory
596
- if (arg === '~' || arg === '$HOME')
597
- return true;
598
- return false;
599
- }
600
- /**
601
- * Check if a redirect targets a dangerous path.
602
- */
603
- function isDangerousRedirect(redirect) {
604
- const target = redirect.target.value;
605
- // Device redirects
606
- if (target.startsWith('/dev/'))
607
- return true;
608
- // System file redirects
609
- if (target.startsWith('/etc/'))
610
- return true;
611
- return false;
612
- }
613
- /**
614
- * Extract all commands from an AST.
615
- */
616
- function extractCommands(ast) {
617
- const commands = [];
618
- function visit(node) {
619
- switch (node.type) {
620
- case 'command':
621
- if (node.name.value) {
622
- // Handle paths like /usr/bin/rm
623
- const name = node.name.value.split('/').pop() ?? node.name.value;
624
- commands.push(name);
625
- }
626
- break;
627
- case 'pipeline':
628
- node.commands.forEach(visit);
629
- break;
630
- case 'list':
631
- node.pipelines.forEach(visit);
632
- break;
633
- case 'subshell':
634
- visit(node.body);
635
- break;
636
- }
637
- }
638
- visit(ast);
639
- return commands;
640
- }
641
- /**
642
- * Analyze AST for safety issues.
643
- */
644
- function findSafetyIssues(ast, blockedCommands = new Set(), originalInput) {
645
- const issues = [];
646
- function visit(node) {
647
- switch (node.type) {
648
- case 'command': {
649
- const cmdName = node.name.value.split('/').pop() ?? node.name.value;
650
- // Check blocked commands
651
- if (blockedCommands.has(cmdName)) {
652
- issues.push({
653
- type: 'blocked_command',
654
- message: `Command '${cmdName}' is blocked`,
655
- severity: 'critical',
656
- start: node.start,
657
- end: node.end,
658
- });
659
- }
660
- // Check dangerous commands
661
- if (DANGEROUS_COMMANDS.has(cmdName)) {
662
- issues.push({
663
- type: 'dangerous_command',
664
- message: `Command '${cmdName}' is potentially dangerous`,
665
- severity: 'high',
666
- start: node.start,
667
- end: node.end,
668
- });
669
- }
670
- // Check rm with dangerous flags/paths
671
- if (cmdName === 'rm') {
672
- const hasForceFlags = node.args.some(arg => arg.value === '-rf' || arg.value === '-fr' ||
673
- arg.value.includes('r') && arg.value.includes('f') && arg.value.startsWith('-'));
674
- const hasDangerousPath = node.args.some(arg => isDangerousPath(arg.value));
675
- const hasWildcard = node.args.some(arg => arg.value.includes('*'));
676
- const hasRootPath = node.args.some(arg => arg.value === '/' || arg.value === '/*');
677
- const hasNoPreserveRoot = node.args.some(arg => arg.value === '--no-preserve-root');
678
- // Critical: rm targeting root or with --no-preserve-root
679
- if ((hasForceFlags && hasRootPath) || hasNoPreserveRoot) {
680
- issues.push({
681
- type: 'critical_pattern',
682
- message: `rm targeting root filesystem is always blocked`,
683
- severity: 'critical',
684
- critical: true,
685
- start: node.start,
686
- end: node.end,
687
- });
688
- }
689
- else if ((hasForceFlags && hasDangerousPath) || (hasForceFlags && hasWildcard)) {
690
- issues.push({
691
- type: 'data_destruction',
692
- message: `rm with recursive/force flags targeting dangerous path`,
693
- severity: 'critical',
694
- start: node.start,
695
- end: node.end,
696
- });
697
- }
698
- }
699
- // Check dd to device
700
- if (cmdName === 'dd') {
701
- const ofArg = node.args.find(arg => arg.value.startsWith('of='));
702
- if (ofArg && ofArg.value.includes('/dev/')) {
703
- // Check if it's writing to a block device (critical) vs a safe device like /dev/null
704
- const isSafeDevice = ['/dev/null', '/dev/zero', '/dev/random', '/dev/urandom'].some(d => ofArg.value.includes(d));
705
- if (!isSafeDevice) {
706
- issues.push({
707
- type: 'critical_pattern',
708
- message: `dd writing to device is always blocked`,
709
- severity: 'critical',
710
- critical: true,
711
- start: node.start,
712
- end: node.end,
713
- });
714
- }
715
- }
716
- }
717
- // Check chmod 777
718
- if (cmdName === 'chmod') {
719
- if (node.args.some(arg => arg.value === '777')) {
720
- issues.push({
721
- type: 'dangerous_pattern',
722
- message: `chmod 777 makes files world-writable`,
723
- severity: 'high',
724
- start: node.start,
725
- end: node.end,
726
- });
727
- }
728
- }
729
- // Check dangerous redirects
730
- for (const redirect of node.redirects) {
731
- if (isDangerousRedirect(redirect)) {
732
- issues.push({
733
- type: 'data_destruction',
734
- message: `Redirect to dangerous path: ${redirect.target.value}`,
735
- severity: 'high',
736
- start: redirect.start,
737
- end: redirect.end,
738
- });
739
- }
740
- }
741
- break;
742
- }
743
- case 'pipeline': {
744
- // Check for curl/wget piped to shell (critical - remote code execution)
745
- const cmds = node.commands;
746
- for (let i = 0; i < cmds.length - 1; i++) {
747
- const current = cmds[i];
748
- const next = cmds[i + 1];
749
- if (current.type === 'command' && next.type === 'command') {
750
- const currentName = current.name.value.split('/').pop();
751
- const nextName = next.name.value.split('/').pop();
752
- if ((currentName === 'curl' || currentName === 'wget') &&
753
- (nextName === 'bash' || nextName === 'sh' || nextName === 'zsh')) {
754
- issues.push({
755
- type: 'critical_pattern',
756
- message: `Piping ${currentName} output to shell is always blocked`,
757
- severity: 'critical',
758
- critical: true,
759
- start: node.start,
760
- end: node.end,
761
- });
762
- }
763
- }
764
- }
765
- node.commands.forEach(visit);
766
- break;
767
- }
768
- case 'list':
769
- node.pipelines.forEach(visit);
770
- break;
771
- case 'subshell':
772
- visit(node.body);
773
- break;
774
- }
775
- }
776
- visit(ast);
777
- // Check for critical patterns in the original input
778
- const textToCheck = originalInput ?? ast.raw;
779
- const criticalMatch = matchesCriticalPattern(textToCheck);
780
- if (criticalMatch) {
781
- issues.push({
782
- type: 'critical_pattern',
783
- message: criticalMatch.message,
784
- severity: 'critical',
785
- critical: true,
786
- start: 0,
787
- end: textToCheck.length,
788
- });
789
- }
790
- return issues;
791
- }
792
- /**
793
- * Determine impact level from issues.
794
- */
795
- function determineImpact(issues, commands) {
796
- // Check issues first
797
- if (issues.some(i => i.severity === 'critical'))
798
- return 'critical';
799
- if (issues.some(i => i.severity === 'high'))
800
- return 'high';
801
- if (issues.some(i => i.severity === 'medium'))
802
- return 'medium';
803
- if (issues.some(i => i.severity === 'low'))
804
- return 'low';
805
- // Check command types
806
- for (const cmd of commands) {
807
- if (DANGEROUS_COMMANDS.has(cmd))
808
- return 'high';
809
- if (MEDIUM_IMPACT_COMMANDS.has(cmd))
810
- return 'medium';
811
- if (LOW_IMPACT_COMMANDS.has(cmd))
812
- return 'low';
813
- }
814
- // All safe commands
815
- if (commands.every(cmd => SAFE_COMMANDS.has(cmd)))
816
- return 'none';
817
- return 'none';
818
- }
819
- /**
820
- * Determine safety level from issues.
821
- * - 'critical': Has issues marked as critical (cannot be executed even with confirm)
822
- * - 'dangerous': Has issues but none are critical (can be executed with confirm)
823
- * - 'safe': No issues found
824
- */
825
- function determineSafetyLevel(issues) {
826
- if (issues.some(i => i.critical === true)) {
827
- return 'critical';
828
- }
829
- if (issues.length > 0) {
830
- return 'dangerous';
831
- }
832
- return 'safe';
833
- }
834
- /**
835
- * Analyze a bash command AST for safety.
836
- *
837
- * @param ast - The parsed AST to analyze
838
- * @param blockedCommands - Set of commands that are blocked
839
- * @param originalInput - Original input string for pattern matching
840
- * @returns Safety analysis result
841
- *
842
- * @example
843
- * ```typescript
844
- * const ast = parseBashCommand('rm -rf /')
845
- * const safety = analyzeASTSafety(ast)
846
- * if (safety.dangerous) {
847
- * console.log(`Blocked: ${safety.reason}`)
848
- * }
849
- * ```
850
- */
851
- export function analyzeASTSafety(ast, blockedCommands = new Set(), originalInput) {
852
- const commands = extractCommands(ast);
853
- const issues = findSafetyIssues(ast, blockedCommands, originalInput);
854
- const impact = determineImpact(issues, commands);
855
- const safetyLevel = determineSafetyLevel(issues);
856
- const dangerous = issues.length > 0;
857
- return {
858
- dangerous,
859
- safetyLevel,
860
- reason: issues.length > 0 ? issues[0].message : undefined,
861
- commands,
862
- impact,
863
- issues,
864
- ast,
865
- };
866
- }
867
- /**
868
- * Parse and analyze a bash command string for safety.
869
- *
870
- * This is a convenience function that combines parsing and analysis.
871
- *
872
- * @param input - The bash command string to analyze
873
- * @param blockedCommands - Set of commands that are blocked
874
- * @returns Safety analysis result
875
- *
876
- * @example
877
- * ```typescript
878
- * const result = parseAndAnalyze('rm -rf /')
879
- * if (result.dangerous) {
880
- * console.log(`Command blocked: ${result.reason}`)
881
- * }
882
- * ```
883
- */
884
- export function parseAndAnalyze(input, blockedCommands = new Set()) {
885
- const ast = parseBashCommand(input);
886
- return analyzeASTSafety(ast, blockedCommands, input);
887
- }
888
- //# sourceMappingURL=bash-ast.js.map