mstro-app 0.1.47

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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -0
  3. package/bin/commands/config.js +145 -0
  4. package/bin/commands/login.js +313 -0
  5. package/bin/commands/logout.js +75 -0
  6. package/bin/commands/status.js +197 -0
  7. package/bin/commands/whoami.js +161 -0
  8. package/bin/configure-claude.js +298 -0
  9. package/bin/mstro.js +581 -0
  10. package/bin/postinstall.js +45 -0
  11. package/bin/release.sh +110 -0
  12. package/dist/server/cli/headless/claude-invoker.d.ts +17 -0
  13. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -0
  14. package/dist/server/cli/headless/claude-invoker.js +311 -0
  15. package/dist/server/cli/headless/claude-invoker.js.map +1 -0
  16. package/dist/server/cli/headless/index.d.ts +13 -0
  17. package/dist/server/cli/headless/index.d.ts.map +1 -0
  18. package/dist/server/cli/headless/index.js +10 -0
  19. package/dist/server/cli/headless/index.js.map +1 -0
  20. package/dist/server/cli/headless/mcp-config.d.ts +11 -0
  21. package/dist/server/cli/headless/mcp-config.d.ts.map +1 -0
  22. package/dist/server/cli/headless/mcp-config.js +76 -0
  23. package/dist/server/cli/headless/mcp-config.js.map +1 -0
  24. package/dist/server/cli/headless/output-utils.d.ts +33 -0
  25. package/dist/server/cli/headless/output-utils.d.ts.map +1 -0
  26. package/dist/server/cli/headless/output-utils.js +101 -0
  27. package/dist/server/cli/headless/output-utils.js.map +1 -0
  28. package/dist/server/cli/headless/prompt-utils.d.ts +21 -0
  29. package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -0
  30. package/dist/server/cli/headless/prompt-utils.js +84 -0
  31. package/dist/server/cli/headless/prompt-utils.js.map +1 -0
  32. package/dist/server/cli/headless/runner.d.ts +24 -0
  33. package/dist/server/cli/headless/runner.d.ts.map +1 -0
  34. package/dist/server/cli/headless/runner.js +99 -0
  35. package/dist/server/cli/headless/runner.js.map +1 -0
  36. package/dist/server/cli/headless/types.d.ts +106 -0
  37. package/dist/server/cli/headless/types.d.ts.map +1 -0
  38. package/dist/server/cli/headless/types.js +4 -0
  39. package/dist/server/cli/headless/types.js.map +1 -0
  40. package/dist/server/cli/improvisation-session-manager.d.ts +155 -0
  41. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -0
  42. package/dist/server/cli/improvisation-session-manager.js +415 -0
  43. package/dist/server/cli/improvisation-session-manager.js.map +1 -0
  44. package/dist/server/index.d.ts +2 -0
  45. package/dist/server/index.d.ts.map +1 -0
  46. package/dist/server/index.js +386 -0
  47. package/dist/server/index.js.map +1 -0
  48. package/dist/server/mcp/bouncer-cli.d.ts +3 -0
  49. package/dist/server/mcp/bouncer-cli.d.ts.map +1 -0
  50. package/dist/server/mcp/bouncer-cli.js +99 -0
  51. package/dist/server/mcp/bouncer-cli.js.map +1 -0
  52. package/dist/server/mcp/bouncer-integration.d.ts +36 -0
  53. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -0
  54. package/dist/server/mcp/bouncer-integration.js +301 -0
  55. package/dist/server/mcp/bouncer-integration.js.map +1 -0
  56. package/dist/server/mcp/security-audit.d.ts +52 -0
  57. package/dist/server/mcp/security-audit.d.ts.map +1 -0
  58. package/dist/server/mcp/security-audit.js +118 -0
  59. package/dist/server/mcp/security-audit.js.map +1 -0
  60. package/dist/server/mcp/security-patterns.d.ts +73 -0
  61. package/dist/server/mcp/security-patterns.d.ts.map +1 -0
  62. package/dist/server/mcp/security-patterns.js +247 -0
  63. package/dist/server/mcp/security-patterns.js.map +1 -0
  64. package/dist/server/mcp/server.d.ts +3 -0
  65. package/dist/server/mcp/server.d.ts.map +1 -0
  66. package/dist/server/mcp/server.js +146 -0
  67. package/dist/server/mcp/server.js.map +1 -0
  68. package/dist/server/routes/files.d.ts +9 -0
  69. package/dist/server/routes/files.d.ts.map +1 -0
  70. package/dist/server/routes/files.js +24 -0
  71. package/dist/server/routes/files.js.map +1 -0
  72. package/dist/server/routes/improvise.d.ts +3 -0
  73. package/dist/server/routes/improvise.d.ts.map +1 -0
  74. package/dist/server/routes/improvise.js +72 -0
  75. package/dist/server/routes/improvise.js.map +1 -0
  76. package/dist/server/routes/index.d.ts +10 -0
  77. package/dist/server/routes/index.d.ts.map +1 -0
  78. package/dist/server/routes/index.js +12 -0
  79. package/dist/server/routes/index.js.map +1 -0
  80. package/dist/server/routes/instances.d.ts +10 -0
  81. package/dist/server/routes/instances.d.ts.map +1 -0
  82. package/dist/server/routes/instances.js +47 -0
  83. package/dist/server/routes/instances.js.map +1 -0
  84. package/dist/server/routes/notifications.d.ts +3 -0
  85. package/dist/server/routes/notifications.d.ts.map +1 -0
  86. package/dist/server/routes/notifications.js +136 -0
  87. package/dist/server/routes/notifications.js.map +1 -0
  88. package/dist/server/services/analytics.d.ts +56 -0
  89. package/dist/server/services/analytics.d.ts.map +1 -0
  90. package/dist/server/services/analytics.js +240 -0
  91. package/dist/server/services/analytics.js.map +1 -0
  92. package/dist/server/services/auth.d.ts +26 -0
  93. package/dist/server/services/auth.d.ts.map +1 -0
  94. package/dist/server/services/auth.js +71 -0
  95. package/dist/server/services/auth.js.map +1 -0
  96. package/dist/server/services/client-id.d.ts +10 -0
  97. package/dist/server/services/client-id.d.ts.map +1 -0
  98. package/dist/server/services/client-id.js +61 -0
  99. package/dist/server/services/client-id.js.map +1 -0
  100. package/dist/server/services/credentials.d.ts +39 -0
  101. package/dist/server/services/credentials.d.ts.map +1 -0
  102. package/dist/server/services/credentials.js +110 -0
  103. package/dist/server/services/credentials.js.map +1 -0
  104. package/dist/server/services/files.d.ts +119 -0
  105. package/dist/server/services/files.d.ts.map +1 -0
  106. package/dist/server/services/files.js +560 -0
  107. package/dist/server/services/files.js.map +1 -0
  108. package/dist/server/services/instances.d.ts +52 -0
  109. package/dist/server/services/instances.d.ts.map +1 -0
  110. package/dist/server/services/instances.js +241 -0
  111. package/dist/server/services/instances.js.map +1 -0
  112. package/dist/server/services/pathUtils.d.ts +47 -0
  113. package/dist/server/services/pathUtils.d.ts.map +1 -0
  114. package/dist/server/services/pathUtils.js +124 -0
  115. package/dist/server/services/pathUtils.js.map +1 -0
  116. package/dist/server/services/platform.d.ts +72 -0
  117. package/dist/server/services/platform.d.ts.map +1 -0
  118. package/dist/server/services/platform.js +368 -0
  119. package/dist/server/services/platform.js.map +1 -0
  120. package/dist/server/services/sentry.d.ts +5 -0
  121. package/dist/server/services/sentry.d.ts.map +1 -0
  122. package/dist/server/services/sentry.js +71 -0
  123. package/dist/server/services/sentry.js.map +1 -0
  124. package/dist/server/services/terminal/pty-manager.d.ts +149 -0
  125. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -0
  126. package/dist/server/services/terminal/pty-manager.js +377 -0
  127. package/dist/server/services/terminal/pty-manager.js.map +1 -0
  128. package/dist/server/services/terminal/tmux-manager.d.ts +82 -0
  129. package/dist/server/services/terminal/tmux-manager.d.ts.map +1 -0
  130. package/dist/server/services/terminal/tmux-manager.js +352 -0
  131. package/dist/server/services/terminal/tmux-manager.js.map +1 -0
  132. package/dist/server/services/websocket/autocomplete.d.ts +50 -0
  133. package/dist/server/services/websocket/autocomplete.d.ts.map +1 -0
  134. package/dist/server/services/websocket/autocomplete.js +361 -0
  135. package/dist/server/services/websocket/autocomplete.js.map +1 -0
  136. package/dist/server/services/websocket/file-utils.d.ts +44 -0
  137. package/dist/server/services/websocket/file-utils.d.ts.map +1 -0
  138. package/dist/server/services/websocket/file-utils.js +272 -0
  139. package/dist/server/services/websocket/file-utils.js.map +1 -0
  140. package/dist/server/services/websocket/handler.d.ts +246 -0
  141. package/dist/server/services/websocket/handler.d.ts.map +1 -0
  142. package/dist/server/services/websocket/handler.js +1771 -0
  143. package/dist/server/services/websocket/handler.js.map +1 -0
  144. package/dist/server/services/websocket/index.d.ts +11 -0
  145. package/dist/server/services/websocket/index.d.ts.map +1 -0
  146. package/dist/server/services/websocket/index.js +14 -0
  147. package/dist/server/services/websocket/index.js.map +1 -0
  148. package/dist/server/services/websocket/types.d.ts +214 -0
  149. package/dist/server/services/websocket/types.d.ts.map +1 -0
  150. package/dist/server/services/websocket/types.js +4 -0
  151. package/dist/server/services/websocket/types.js.map +1 -0
  152. package/dist/server/utils/agent-manager.d.ts +69 -0
  153. package/dist/server/utils/agent-manager.d.ts.map +1 -0
  154. package/dist/server/utils/agent-manager.js +269 -0
  155. package/dist/server/utils/agent-manager.js.map +1 -0
  156. package/dist/server/utils/paths.d.ts +25 -0
  157. package/dist/server/utils/paths.d.ts.map +1 -0
  158. package/dist/server/utils/paths.js +38 -0
  159. package/dist/server/utils/paths.js.map +1 -0
  160. package/dist/server/utils/port-manager.d.ts +10 -0
  161. package/dist/server/utils/port-manager.d.ts.map +1 -0
  162. package/dist/server/utils/port-manager.js +60 -0
  163. package/dist/server/utils/port-manager.js.map +1 -0
  164. package/dist/server/utils/port.d.ts +26 -0
  165. package/dist/server/utils/port.d.ts.map +1 -0
  166. package/dist/server/utils/port.js +83 -0
  167. package/dist/server/utils/port.js.map +1 -0
  168. package/hooks/bouncer.sh +138 -0
  169. package/package.json +74 -0
  170. package/server/README.md +191 -0
  171. package/server/cli/headless/claude-invoker.ts +415 -0
  172. package/server/cli/headless/index.ts +39 -0
  173. package/server/cli/headless/mcp-config.ts +87 -0
  174. package/server/cli/headless/output-utils.ts +109 -0
  175. package/server/cli/headless/prompt-utils.ts +108 -0
  176. package/server/cli/headless/runner.ts +133 -0
  177. package/server/cli/headless/types.ts +118 -0
  178. package/server/cli/improvisation-session-manager.ts +531 -0
  179. package/server/index.ts +456 -0
  180. package/server/mcp/README.md +122 -0
  181. package/server/mcp/bouncer-cli.ts +127 -0
  182. package/server/mcp/bouncer-integration.ts +430 -0
  183. package/server/mcp/security-audit.ts +180 -0
  184. package/server/mcp/security-patterns.ts +290 -0
  185. package/server/mcp/server.ts +174 -0
  186. package/server/routes/files.ts +29 -0
  187. package/server/routes/improvise.ts +82 -0
  188. package/server/routes/index.ts +13 -0
  189. package/server/routes/instances.ts +54 -0
  190. package/server/routes/notifications.ts +158 -0
  191. package/server/services/analytics.ts +277 -0
  192. package/server/services/auth.ts +80 -0
  193. package/server/services/client-id.ts +68 -0
  194. package/server/services/credentials.ts +134 -0
  195. package/server/services/files.ts +710 -0
  196. package/server/services/instances.ts +275 -0
  197. package/server/services/pathUtils.ts +158 -0
  198. package/server/services/platform.test.ts +1314 -0
  199. package/server/services/platform.ts +435 -0
  200. package/server/services/sentry.ts +81 -0
  201. package/server/services/terminal/pty-manager.ts +464 -0
  202. package/server/services/terminal/tmux-manager.ts +426 -0
  203. package/server/services/websocket/autocomplete.ts +438 -0
  204. package/server/services/websocket/file-utils.ts +305 -0
  205. package/server/services/websocket/handler.test.ts +20 -0
  206. package/server/services/websocket/handler.ts +2047 -0
  207. package/server/services/websocket/index.ts +40 -0
  208. package/server/services/websocket/types.ts +339 -0
  209. package/server/tsconfig.json +19 -0
  210. package/server/utils/agent-manager.ts +323 -0
  211. package/server/utils/paths.ts +45 -0
  212. package/server/utils/port-manager.ts +70 -0
  213. package/server/utils/port.ts +102 -0
@@ -0,0 +1,290 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Security Patterns - Single Source of Truth
6
+ *
7
+ * Consolidated pattern definitions for fast-path security checks.
8
+ * All pattern-based security decisions use this module to avoid duplication.
9
+ *
10
+ * PHILOSOPHY:
11
+ * - Most operations should be evaluated by CONTEXT, not by path or extension
12
+ * - Only truly catastrophic operations (rm -rf /, fork bombs) are auto-denied
13
+ * - Sensitive operations (system paths, credentials) get AI review with context
14
+ * - The question is: "Does this operation make sense given user intent?"
15
+ */
16
+
17
+ export interface SecurityPattern {
18
+ pattern: RegExp;
19
+ reason?: string;
20
+ }
21
+
22
+ /**
23
+ * Sensitive paths that require AI context review
24
+ * These aren't auto-denied - they need context analysis to determine intent
25
+ */
26
+ export const SENSITIVE_PATHS: SecurityPattern[] = [
27
+ // System directories - might be legitimate (e.g., user asked to configure something)
28
+ { pattern: /^(Write|Edit):\s*\/etc\//i, reason: 'System configuration - verify user intent' },
29
+ { pattern: /^(Write|Edit):\s*\/(bin|sbin|usr\/bin|usr\/sbin)\//i, reason: 'System binaries - verify user intent' },
30
+ { pattern: /^(Write|Edit):\s*\/boot\//i, reason: 'Boot directory - verify user intent' },
31
+ { pattern: /^(Write|Edit):\s*\/root\//i, reason: 'Root home - verify user intent' },
32
+ { pattern: /^(Write|Edit):\s*\/System\//i, reason: 'macOS system - verify user intent' },
33
+ { pattern: /^(Write|Edit):\s*\/Library\/(LaunchDaemons|LaunchAgents)\//i, reason: 'macOS launch services - verify user intent' },
34
+
35
+ // Credential/security files - high sensitivity, need clear user intent
36
+ { pattern: /^(Write|Edit):\s*.*\/\.ssh\//i, reason: 'SSH configuration - verify user intent' },
37
+ { pattern: /^(Write|Edit):\s*.*\/\.gnupg\//i, reason: 'GPG keys - verify user intent' },
38
+ { pattern: /^(Write|Edit):\s*.*\/\.aws\/(credentials|config)/i, reason: 'AWS credentials - verify user intent' },
39
+ { pattern: /^(Write|Edit):\s*.*\/(\.env|\.env\.local|\.env\.production)$/i, reason: 'Environment secrets - verify user intent' },
40
+
41
+ // Shell profiles - common legitimate edits but also attack vector
42
+ { pattern: /^(Write|Edit):\s*.*\/(\.bash_profile|\.bashrc|\.zshrc|\.profile|\.zprofile)$/i, reason: 'Shell profile - verify user intent' },
43
+ ];
44
+
45
+ /**
46
+ * Critical threats - auto-deny regardless of context
47
+ *
48
+ * These are NOT about "dangerous commands" but about commands that:
49
+ * 1. Are NEVER legitimate in any dev workflow
50
+ * 2. Have catastrophic, irreversible consequences
51
+ * 3. The cost of false positive (blocking) is negligible
52
+ *
53
+ * Note: Most "dangerous" commands (curl|bash, rm -rf, sudo) go to Haiku
54
+ * for context review. Only truly never-legitimate commands are here.
55
+ */
56
+ export const CRITICAL_THREATS: SecurityPattern[] = [
57
+ // Deleting root or home - no legitimate dev task requires this
58
+ // If user really wants this, they can run it manually outside Claude
59
+ {
60
+ pattern: /rm\s+-rf\s+(\/|~)($|\s)/i,
61
+ reason: 'Deleting root (/) or home (~) directory is never a legitimate dev task'
62
+ },
63
+ {
64
+ pattern: /:\(\)\{.*\}|:\(\)\{.*:\|:/i,
65
+ reason: 'Fork bomb detected - would cause system resource exhaustion'
66
+ },
67
+ {
68
+ pattern: /dd\s+if=\/dev\/zero\s+of=\/dev\/sd/i,
69
+ reason: 'Attempting to overwrite disk device - would destroy data'
70
+ },
71
+ {
72
+ pattern: /mkfs\./i,
73
+ reason: 'Attempting to format filesystem - would destroy all data'
74
+ },
75
+ {
76
+ pattern: /eval.*\$\(.*base64.*\)/i,
77
+ reason: 'Obfuscated code execution via base64 - common malware technique'
78
+ },
79
+ {
80
+ pattern: />\s*\/dev\/sd[a-z]/i,
81
+ reason: 'Direct write to disk device - would corrupt filesystem'
82
+ },
83
+ {
84
+ pattern: /chmod\s+000\s+\//i,
85
+ reason: 'Attempting to make system directories inaccessible'
86
+ }
87
+ // NOTE: curl|bash is NOT here - it goes to Haiku for context review
88
+ // The question is "did a bad actor inject this?" not "is curl|bash dangerous?"
89
+ ];
90
+
91
+ /**
92
+ * Safe operations that can be immediately allowed (confidence: 95%)
93
+ * These are read-only or obviously safe operations that don't need context review
94
+ */
95
+ export const SAFE_OPERATIONS: SecurityPattern[] = [
96
+ // Read operations are always safe - no side effects
97
+ { pattern: /^Read:/i },
98
+ { pattern: /^Glob:/i },
99
+ { pattern: /^Grep:/i },
100
+
101
+ // Write/Edit to user home directory or subdirectories - user requested, allow it
102
+ // Excludes system paths which go through critical threats check
103
+ { pattern: /^Write:\s*\/Users\/[^/]+\//i }, // macOS home dirs - Write
104
+ { pattern: /^Edit:\s*\/Users\/[^/]+\//i }, // macOS home dirs - Edit
105
+ { pattern: /^Write:\s*\/home\/[^/]+\//i }, // Linux home dirs - Write
106
+ { pattern: /^Edit:\s*\/home\/[^/]+\//i }, // Linux home dirs - Edit
107
+
108
+ // Safe bash commands - common development workflows
109
+ // NOTE: curl|bash goes to Haiku for context review, not auto-allowed
110
+ { pattern: /^Bash:\s*(npm|yarn|pnpm|bun)\s+(install|ci|run|test|build|dev|start|lint|format)($|\s)/i },
111
+ { pattern: /^Bash:\s*git\s+(status|log|diff|show|branch|clone|pull|fetch|add|stash|checkout)($|\s)/i },
112
+ { pattern: /^Bash:\s*docker\s+(build|run|ps|logs|compose|images)($|\s)/i },
113
+ { pattern: /^Bash:\s*(pytest|cargo\s+(build|test|run|check)|go\s+(build|test|run|mod))($|\s)/i },
114
+ { pattern: /^Bash:\s*(mkdir|cd|ls|pwd|cat|head|tail|wc|sort|uniq|grep|find|which|echo|env)($|\s)/i },
115
+
116
+ // Cleanup of build artifacts - always safe, commonly requested
117
+ { pattern: /^Bash:\s*rm\s+-rf\s+(\.\/)?node_modules($|\s)/i },
118
+ { pattern: /^Bash:\s*rm\s+-rf\s+(\.\/)?dist($|\s)/i },
119
+ { pattern: /^Bash:\s*rm\s+-rf\s+(\.\/)?build($|\s)/i },
120
+ { pattern: /^Bash:\s*rm\s+-rf\s+(\.\/)?\.cache($|\s)/i },
121
+ { pattern: /^Bash:\s*rm\s+-rf\s+(\.\/)?\.next($|\s)/i },
122
+ { pattern: /^Bash:\s*rm\s+-rf\s+(\.\/)?target($|\s)/i },
123
+ { pattern: /^Bash:\s*rm\s+-rf\s+(\.\/)?__pycache__($|\s)/i },
124
+
125
+ // Write/Edit to temp directories - ephemeral, low risk
126
+ { pattern: /^(Write|Edit):\s*\/tmp\//i },
127
+ { pattern: /^(Write|Edit):\s*\/var\/tmp\//i },
128
+ ];
129
+
130
+ /**
131
+ * Patterns that trigger AI context review
132
+ * These operations need context analysis to determine if they align with user intent
133
+ *
134
+ * The AI should consider:
135
+ * 1. Did the user explicitly request this operation?
136
+ * 2. Does it make sense given the task at hand?
137
+ * 3. Is the content/action appropriate for the target?
138
+ */
139
+ export const NEEDS_AI_REVIEW: SecurityPattern[] = [
140
+ // Remote code execution patterns
141
+ {
142
+ pattern: /(curl|wget).*\|.*(?:bash|sh)/i,
143
+ reason: 'Pipe to shell - verify source is trusted and user intended this'
144
+ },
145
+
146
+ // Elevated privileges
147
+ {
148
+ pattern: /sudo/i,
149
+ reason: 'Elevated privileges - verify user intended this action'
150
+ },
151
+
152
+ // Destructive operations (except safe build artifact cleanup)
153
+ {
154
+ pattern: /rm\s+-rf/i,
155
+ reason: 'Recursive deletion - verify target matches user intent'
156
+ },
157
+
158
+ // ALL Write/Edit operations that aren't to /tmp go through context review
159
+ // This is the key change: we review based on context, not blanket allow/deny
160
+ {
161
+ pattern: /^(Write|Edit):\s*(?!\/tmp\/|\/var\/tmp\/)/i,
162
+ reason: 'File modification - verify aligns with user request'
163
+ },
164
+ ];
165
+
166
+ /**
167
+ * Check if operation matches any pattern in array
168
+ */
169
+ export function matchesPattern(operation: string, patterns: SecurityPattern[]): SecurityPattern | null {
170
+ for (const pattern of patterns) {
171
+ if (pattern.pattern.test(operation)) {
172
+ return pattern;
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+
178
+ /**
179
+ * Determine if operation requires AI context review
180
+ *
181
+ * The philosophy here is:
182
+ * - SAFE_OPERATIONS: No review needed (read-only, temp files, build artifact cleanup)
183
+ * - CRITICAL_THREATS: Auto-deny, no review (catastrophic operations)
184
+ * - Everything else: AI reviews context to determine if it matches user intent
185
+ */
186
+ const SAFE_RM_PATTERNS = [
187
+ /rm\s+-rf\s+(\.\/)?node_modules($|\s)/i,
188
+ /rm\s+-rf\s+(\.\/)?dist($|\s)/i,
189
+ /rm\s+-rf\s+(\.\/)?build($|\s)/i,
190
+ /rm\s+-rf\s+(\.\/)?\.cache($|\s)/i,
191
+ /rm\s+-rf\s+(\.\/)?\.next($|\s)/i,
192
+ /rm\s+-rf\s+(\.\/)?target($|\s)/i,
193
+ /rm\s+-rf\s+(\.\/)?__pycache__($|\s)/i,
194
+ ];
195
+
196
+ export function requiresAIReview(operation: string): boolean {
197
+ if (matchesPattern(operation, SAFE_OPERATIONS)) return false;
198
+ if (matchesPattern(operation, CRITICAL_THREATS)) return false;
199
+
200
+ if (matchesPattern(operation, NEEDS_AI_REVIEW)) {
201
+ return !SAFE_RM_PATTERNS.some(p => p.test(operation));
202
+ }
203
+
204
+ if (/\$\{.*\}|\$\(.*\)/.test(operation) || /\*\*?/.test(operation)) return true;
205
+ if (/^Bash:\s*\.\//.test(operation)) return true;
206
+
207
+ return false;
208
+ }
209
+
210
+ /**
211
+ * Check if operation targets a sensitive path
212
+ * Used to provide additional context to AI reviewer
213
+ */
214
+ export function isSensitivePath(operation: string): SecurityPattern | null {
215
+ return matchesPattern(operation, SENSITIVE_PATHS);
216
+ }
217
+
218
+ /**
219
+ * Classify operation risk level for context-aware review
220
+ *
221
+ * Risk levels indicate how much scrutiny the AI should apply:
222
+ * - critical: Catastrophic if wrong (rm -rf /, fork bombs) - auto-deny
223
+ * - high: Needs clear user intent (sudo, sensitive paths, credentials)
224
+ * - medium: Normal file operations - verify matches user request
225
+ * - low: Safe operations - minimal review needed
226
+ */
227
+ export function classifyRisk(operation: string): {
228
+ isDestructive: boolean;
229
+ riskLevel: 'low' | 'medium' | 'high' | 'critical';
230
+ reasons: string[];
231
+ } {
232
+ // Critical threats are auto-denied
233
+ const criticalThreat = matchesPattern(operation, CRITICAL_THREATS);
234
+ if (criticalThreat) {
235
+ return {
236
+ isDestructive: true,
237
+ riskLevel: 'critical',
238
+ reasons: [criticalThreat.reason || 'Critical threat detected']
239
+ };
240
+ }
241
+
242
+ // Sensitive paths need high scrutiny but aren't auto-denied
243
+ const sensitivePath = matchesPattern(operation, SENSITIVE_PATHS);
244
+ if (sensitivePath) {
245
+ return {
246
+ isDestructive: false, // Not inherently destructive, just sensitive
247
+ riskLevel: 'high',
248
+ reasons: [sensitivePath.reason || 'Sensitive path - requires clear user intent']
249
+ };
250
+ }
251
+
252
+ // Other patterns that need elevated review
253
+ const elevatedPatterns: SecurityPattern[] = [
254
+ { pattern: /sudo/i, reason: 'Elevated privileges requested' },
255
+ { pattern: /DROP\s+(TABLE|DATABASE)/i, reason: 'Database deletion' },
256
+ { pattern: /chmod\s+777/i, reason: 'Dangerous permissions' },
257
+ { pattern: /(curl|wget).*\|.*(bash|sh)/i, reason: 'Remote code execution' },
258
+ { pattern: /pkill|killall/i, reason: 'Process termination' },
259
+ ];
260
+
261
+ for (const pattern of elevatedPatterns) {
262
+ if (pattern.pattern.test(operation)) {
263
+ return {
264
+ isDestructive: true,
265
+ riskLevel: 'high',
266
+ reasons: [pattern.reason || 'Elevated risk operation']
267
+ };
268
+ }
269
+ }
270
+
271
+ // Medium risk: only recursive deletions outside safe dirs
272
+ // NOTE: Write/Edit are NOT flagged as risky - they're normal dev operations
273
+ if (/rm\s+-rf/i.test(operation)) {
274
+ // Check if it's actually safe (build artifacts, temp)
275
+ if (matchesPattern(operation, SAFE_OPERATIONS)) {
276
+ return { isDestructive: false, riskLevel: 'low', reasons: [] };
277
+ }
278
+ return {
279
+ isDestructive: true,
280
+ riskLevel: 'medium',
281
+ reasons: ['Recursive deletion']
282
+ };
283
+ }
284
+
285
+ return {
286
+ isDestructive: false,
287
+ riskLevel: 'low',
288
+ reasons: []
289
+ };
290
+ }
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env -S npx tsx
2
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
3
+ // Licensed under the MIT License. See LICENSE file for details.
4
+
5
+ /**
6
+ * MCP Bouncer Server
7
+ *
8
+ * Provides permission approval/denial for Claude Code tool use via MCP protocol.
9
+ * Integrates with Mstro's existing bouncer-integration.ts for security analysis.
10
+ *
11
+ * Usage:
12
+ * claude --print --permission-prompt-tool mcp__mstro-bouncer__approval_prompt \
13
+ * --mcp-config mstro-bouncer-mcp.json \
14
+ * "your prompt here"
15
+ */
16
+
17
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
18
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
19
+ import {
20
+ CallToolRequestSchema,
21
+ ListToolsRequestSchema,
22
+ } from '@modelcontextprotocol/sdk/types.js';
23
+ import { type BouncerReviewRequest, reviewOperation } from './bouncer-integration.js';
24
+
25
+ // Create MCP server
26
+ const server = new Server(
27
+ {
28
+ name: 'mstro-bouncer',
29
+ version: '1.0.0',
30
+ },
31
+ {
32
+ capabilities: {
33
+ tools: {},
34
+ },
35
+ }
36
+ );
37
+
38
+ /**
39
+ * List available tools (required by MCP protocol)
40
+ */
41
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
42
+ return {
43
+ tools: [
44
+ {
45
+ name: 'approval_prompt',
46
+ description: 'Analyze and approve/deny tool use requests from Claude Code. Integrates with Mstro security bouncer for AI-powered risk analysis.',
47
+ inputSchema: {
48
+ type: 'object',
49
+ properties: {
50
+ tool_name: {
51
+ type: 'string',
52
+ description: 'Name of the tool being requested (e.g., "Bash", "Write", "Read")',
53
+ },
54
+ input: {
55
+ type: 'object',
56
+ description: 'Tool input parameters as JSON object',
57
+ },
58
+ },
59
+ required: ['tool_name', 'input'],
60
+ },
61
+ },
62
+ ],
63
+ };
64
+ });
65
+
66
+ /**
67
+ * Handle tool calls (approval_prompt)
68
+ */
69
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
70
+ if (request.params.name !== 'approval_prompt') {
71
+ throw new Error(`Unknown tool: ${request.params.name}`);
72
+ }
73
+
74
+ const { tool_name, input } = request.params.arguments as {
75
+ tool_name: string;
76
+ input: Record<string, any>;
77
+ };
78
+
79
+ console.error(`[MCP Bouncer] Analyzing ${tool_name} request...`);
80
+
81
+ // Format operation string for bouncer analysis
82
+ // Example: "Bash: rm -rf node_modules"
83
+ let operationString = `${tool_name}:`;
84
+
85
+ // Extract file path with multiple property name support
86
+ // Claude Code may use file_path, filePath, or path depending on context
87
+ const getFilePath = (inp: Record<string, any>) =>
88
+ inp.file_path || inp.filePath || inp.path;
89
+
90
+ if (tool_name === 'Bash' && input.command) {
91
+ operationString += ` ${input.command}`;
92
+ } else if (['Write', 'Edit', 'Read'].includes(tool_name)) {
93
+ const filePath = getFilePath(input);
94
+ operationString += filePath ? ` ${filePath}` : ` ${JSON.stringify(input)}`;
95
+ } else {
96
+ // Generic format: include all input parameters
97
+ operationString += ` ${JSON.stringify(input)}`;
98
+ }
99
+
100
+ // Build bouncer request with context
101
+ const bouncerRequest: BouncerReviewRequest = {
102
+ operation: operationString,
103
+ context: {
104
+ purpose: `Tool use request from Claude`,
105
+ workingDirectory: process.cwd(),
106
+ toolName: tool_name,
107
+ toolInput: input,
108
+ },
109
+ };
110
+
111
+ try {
112
+ // Use existing Mstro bouncer for analysis
113
+ const decision = await reviewOperation(bouncerRequest);
114
+
115
+ console.error(`[MCP Bouncer] Decision: ${decision.decision} (${decision.confidence}% confidence)`);
116
+ console.error(`[MCP Bouncer] Reasoning: ${decision.reasoning}`);
117
+
118
+ // Format response for Claude Code
119
+ const response =
120
+ decision.decision === 'deny'
121
+ ? {
122
+ behavior: 'deny',
123
+ message: `🚫 ${decision.reasoning}${
124
+ decision.alternative ? `\n\nAlternative: ${decision.alternative}` : ''
125
+ }`,
126
+ }
127
+ : {
128
+ behavior: 'allow',
129
+ updatedInput: input,
130
+ message:
131
+ decision.decision === 'warn_allow'
132
+ ? `⚠️ Allowed with caution: ${decision.reasoning}`
133
+ : undefined,
134
+ };
135
+
136
+ return {
137
+ content: [
138
+ {
139
+ type: 'text',
140
+ text: JSON.stringify(response),
141
+ },
142
+ ],
143
+ };
144
+ } catch (error: any) {
145
+ console.error(`[MCP Bouncer] Error: ${error.message}`);
146
+
147
+ // Fail-safe: deny on error
148
+ return {
149
+ content: [
150
+ {
151
+ type: 'text',
152
+ text: JSON.stringify({
153
+ behavior: 'deny',
154
+ message: `Security analysis failed: ${error.message}. Denying for safety.`,
155
+ }),
156
+ },
157
+ ],
158
+ };
159
+ }
160
+ });
161
+
162
+ /**
163
+ * Start the MCP server
164
+ */
165
+ async function main() {
166
+ const transport = new StdioServerTransport();
167
+ await server.connect(transport);
168
+ console.error('[MCP Bouncer] Server started and ready');
169
+ }
170
+
171
+ main().catch((error) => {
172
+ console.error('[MCP Bouncer] Fatal error:', error);
173
+ process.exit(1);
174
+ });
@@ -0,0 +1,29 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * File Routes
6
+ *
7
+ * Handles file listing and autocomplete.
8
+ */
9
+
10
+ import { Hono } from 'hono'
11
+ import type { FileService } from '../services/files.js'
12
+
13
+ export function createFileRoutes(fileService: FileService) {
14
+ const routes = new Hono()
15
+
16
+ routes.get('/', (c) => {
17
+ try {
18
+ const filter = c.req.query('filter') || ''
19
+ const baseDir = (c.req.query('baseDir') || 'scores') as 'working' | 'scores'
20
+
21
+ const files = fileService.getAllFiles(baseDir, filter)
22
+ return c.json({ files })
23
+ } catch (error) {
24
+ return c.json({ error: (error as Error).message }, 500)
25
+ }
26
+ })
27
+
28
+ return routes
29
+ }
@@ -0,0 +1,82 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Improvise History Routes
6
+ *
7
+ * Handles improvise session history retrieval.
8
+ */
9
+
10
+ import { join } from 'node:path'
11
+ import { Hono } from 'hono'
12
+
13
+ export function createImproviseRoutes(workingDir: string) {
14
+ const routes = new Hono()
15
+
16
+ routes.get('/sessions', async (c) => {
17
+ try {
18
+ const sessionsDir = join(workingDir, '.mstro', 'improvise')
19
+ const { readdirSync, existsSync, readFileSync } = await import('node:fs')
20
+
21
+ if (!existsSync(sessionsDir)) {
22
+ return c.json({ sessions: [] })
23
+ }
24
+
25
+ // Look for history-*.json files in the improvise directory
26
+ const historyFiles = readdirSync(sessionsDir)
27
+ .filter((name: string) => name.startsWith('history-') && name.endsWith('.json'))
28
+ .sort((a: string, b: string) => {
29
+ // Sort by timestamp in filename (newer first)
30
+ const timestampA = parseInt(a.replace('history-', '').replace('.json', ''), 10)
31
+ const timestampB = parseInt(b.replace('history-', '').replace('.json', ''), 10)
32
+ return timestampB - timestampA
33
+ })
34
+
35
+ const sessions = historyFiles.map((filename: string) => {
36
+ const historyPath = join(sessionsDir, filename)
37
+
38
+ try {
39
+ const historyData = JSON.parse(readFileSync(historyPath, 'utf-8'))
40
+ const firstPrompt = historyData.movements?.[0]?.userPrompt || ''
41
+
42
+ return {
43
+ sessionId: historyData.sessionId,
44
+ startedAt: historyData.startedAt,
45
+ lastActivityAt: historyData.lastActivityAt,
46
+ totalTokens: historyData.totalTokens,
47
+ movementCount: historyData.movements?.length || 0,
48
+ title: firstPrompt.slice(0, 80) + (firstPrompt.length > 80 ? '...' : ''),
49
+ movements: historyData.movements || []
50
+ }
51
+ } catch {
52
+ return null
53
+ }
54
+ }).filter(Boolean)
55
+
56
+ return c.json({ sessions })
57
+ } catch (error) {
58
+ return c.json({ error: (error as Error).message }, 500)
59
+ }
60
+ })
61
+
62
+ routes.get('/sessions/:sessionId', async (c) => {
63
+ try {
64
+ const { sessionId } = c.req.param()
65
+ // Extract timestamp from sessionId (e.g., "improv-1234567890" -> "1234567890")
66
+ const timestamp = sessionId.replace('improv-', '')
67
+ const historyPath = join(workingDir, '.mstro', 'improvise', `history-${timestamp}.json`)
68
+ const { existsSync, readFileSync } = await import('node:fs')
69
+
70
+ if (!existsSync(historyPath)) {
71
+ return c.json({ error: 'Session not found' }, 404)
72
+ }
73
+
74
+ const historyData = JSON.parse(readFileSync(historyPath, 'utf-8'))
75
+ return c.json(historyData)
76
+ } catch (error) {
77
+ return c.json({ error: (error as Error).message }, 500)
78
+ }
79
+ })
80
+
81
+ return routes
82
+ }
@@ -0,0 +1,13 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Routes Index
6
+ *
7
+ * Re-exports all route creators for easy importing.
8
+ */
9
+
10
+ export { createFileRoutes } from './files.js'
11
+ export { createImproviseRoutes } from './improvise.js'
12
+ export { createInstanceRoutes, createShutdownRoute } from './instances.js'
13
+ export { createNotificationRoutes } from './notifications.js'
@@ -0,0 +1,54 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Instance Management Routes
6
+ *
7
+ * Handles server instance discovery and management.
8
+ */
9
+
10
+ import { Hono } from 'hono'
11
+ import { getClientId } from '../services/client-id.js'
12
+ import { InstanceRegistry } from '../services/instances.js'
13
+
14
+ export function createInstanceRoutes(instanceRegistry: InstanceRegistry) {
15
+ const routes = new Hono()
16
+
17
+ routes.get('/', (c) => {
18
+ try {
19
+ const instances = InstanceRegistry.getAllInstances()
20
+ return c.json({ instances })
21
+ } catch (error) {
22
+ return c.json({ error: (error as Error).message }, 500)
23
+ }
24
+ })
25
+
26
+ routes.get('/current', (c) => {
27
+ try {
28
+ const instance = instanceRegistry.getCurrentInstance()
29
+ return c.json({
30
+ instance,
31
+ clientId: getClientId()
32
+ })
33
+ } catch (error) {
34
+ return c.json({ error: (error as Error).message }, 500)
35
+ }
36
+ })
37
+
38
+ return routes
39
+ }
40
+
41
+ export function createShutdownRoute(instanceRegistry: InstanceRegistry) {
42
+ const routes = new Hono()
43
+
44
+ routes.post('/', (c) => {
45
+ setTimeout(() => {
46
+ instanceRegistry.unregister()
47
+ process.exit(0)
48
+ }, 100)
49
+
50
+ return c.json({ success: true, message: 'Shutting down...' })
51
+ })
52
+
53
+ return routes
54
+ }