just-bash-mcp 2.9.4 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,228 +3,292 @@
3
3
  * Handles environment variable parsing and configuration building
4
4
  */
5
5
 
6
- import { readFileSync } from "node:fs";
7
- import { dirname, join } from "node:path";
8
- import { fileURLToPath } from "node:url";
9
- import {
10
- type BashLogger,
11
- type BashOptions,
12
- type CommandName,
13
- type DefenseInDepthConfig,
14
- type MountConfig,
15
- type NetworkConfig,
16
- OverlayFs,
17
- ReadWriteFs,
18
- SecurityViolationLogger,
19
- createConsoleViolationCallback,
20
- } from "just-bash";
21
-
22
- // Re-export upstream types used by other modules
23
- export type { DefenseInDepthConfig };
6
+ import {type BashLogger, type BashOptions, type CommandName, type MountConfig, type NetworkConfig, OverlayFs, ReadWriteFs, SecurityViolationLogger, createConsoleViolationCallback} from 'just-bash'
7
+ import {readFileSync} from 'node:fs'
8
+ import {dirname, join} from 'node:path'
9
+ import {fileURLToPath} from 'node:url'
10
+
11
+ export type SecurityViolationType = string
12
+
13
+ export interface SecurityViolation {
14
+ timestamp: number
15
+ type: SecurityViolationType
16
+ message: string
17
+ path: string
18
+ stack?: string
19
+ executionId?: string
20
+ }
21
+
22
+ export interface DefenseInDepthStats {
23
+ violationsBlocked: number
24
+ violations: SecurityViolation[]
25
+ activeTimeMs: number
26
+ refCount: number
27
+ }
28
+
29
+ export interface ViolationSummary {
30
+ type: SecurityViolationType
31
+ count: number
32
+ firstSeen: number
33
+ lastSeen: number
34
+ paths: string[]
35
+ }
36
+
37
+ export interface DefenseInDepthConfig {
38
+ enabled?: boolean
39
+ auditMode?: boolean
40
+ onViolation?: (violation: SecurityViolation) => void
41
+ excludeViolationTypes?: SecurityViolationType[]
42
+ }
43
+
44
+ export interface SecurityViolationLoggerLike {
45
+ record(violation: SecurityViolation): void
46
+ getViolations(): SecurityViolation[]
47
+ getViolationsByType(type: SecurityViolationType): SecurityViolation[]
48
+ getSummary(): ViolationSummary[]
49
+ getTotalCount(): number
50
+ hasViolations(): boolean
51
+ clear(): void
52
+ createCallback(): (violation: SecurityViolation) => void
53
+ }
54
+
55
+ const SecurityViolationLoggerCtor = SecurityViolationLogger as unknown as new () => SecurityViolationLoggerLike
56
+ const createConsoleViolationCallbackTyped = createConsoleViolationCallback as unknown as () => (violation: SecurityViolation) => void
24
57
 
25
58
  export interface TraceEvent {
26
- category: string;
27
- name: string;
28
- durationMs: number;
29
- details?: Record<string, unknown>;
59
+ category: string
60
+ name: string
61
+ durationMs: number
62
+ details?: Record<string, unknown>
30
63
  }
31
64
 
32
- export type TraceCallback = (event: TraceEvent) => void;
65
+ export type TraceCallback = (event: TraceEvent) => void
33
66
 
34
67
  // ============================================================================
35
68
  // Types
36
69
  // ============================================================================
37
70
 
38
- export type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
71
+ export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS'
39
72
 
40
73
  // ============================================================================
41
74
  // Environment Variable Parsing
42
75
  // ============================================================================
43
76
 
44
77
  function parseEnvString(key: string, defaultValue: string): string {
45
- return process.env[key] || defaultValue;
78
+ return process.env[key] || defaultValue
46
79
  }
47
80
 
48
81
  function parseEnvBoolean(key: string, defaultValue: boolean): boolean {
49
- return process.env[key] === "true" ? true : defaultValue;
82
+ return process.env[key] === 'true' ? true : defaultValue
50
83
  }
51
84
 
52
85
  function parseEnvInt(key: string, defaultValue: number): number {
53
- const value = process.env[key];
54
- if (!value) return defaultValue;
55
- const parsed = Number.parseInt(value, 10);
56
- return Number.isNaN(parsed) ? defaultValue : parsed;
86
+ const value = process.env[key]
87
+ if (!value) return defaultValue
88
+ const parsed = Number.parseInt(value, 10)
89
+ return Number.isNaN(parsed) ? defaultValue : parsed
57
90
  }
58
91
 
59
92
  function parseEnvStringArray(key: string): string[] {
60
- return process.env[key]?.split(",").filter(Boolean) || [];
93
+ return process.env[key]?.split(',').filter(Boolean) || []
61
94
  }
62
95
 
63
96
  // ============================================================================
64
97
  // Configuration Constants
65
98
  // ============================================================================
66
99
 
100
+ export interface JavaScriptToolInvocation {
101
+ path: string
102
+ argsJson: string
103
+ }
104
+
105
+ export interface JavaScriptToolHandler {
106
+ (input: JavaScriptToolInvocation): Promise<string>
107
+ }
108
+
67
109
  export interface Config {
68
- readonly VERSION: string;
69
- readonly SERVER_NAME: string;
70
- readonly OVERLAY_ROOT: string | undefined;
71
- readonly READ_WRITE_ROOT: string | undefined;
72
- readonly MOUNTS_CONFIG: string | undefined;
73
- readonly INITIAL_CWD: string;
74
- readonly ALLOW_NETWORK: boolean;
75
- readonly ALLOWED_URL_PREFIXES: string[];
76
- readonly ALLOWED_METHODS: HttpMethod[];
77
- readonly MAX_REDIRECTS: number;
78
- readonly NETWORK_TIMEOUT_MS: number;
79
- readonly MAX_RESPONSE_SIZE: number | undefined;
80
- readonly MAX_CALL_DEPTH: number;
81
- readonly MAX_COMMAND_COUNT: number;
82
- readonly MAX_LOOP_ITERATIONS: number;
83
- readonly MAX_AWK_ITERATIONS: number;
84
- readonly MAX_SED_ITERATIONS: number;
85
- readonly MAX_JQ_ITERATIONS: number;
86
- readonly MAX_GLOB_OPERATIONS: number;
87
- readonly MAX_STRING_LENGTH: number;
88
- readonly MAX_ARRAY_ELEMENTS: number;
89
- readonly MAX_HEREDOC_SIZE: number;
90
- readonly MAX_SUBSTITUTION_DEPTH: number;
91
- readonly MAX_SQLITE_TIMEOUT_MS: number;
92
- readonly MAX_PYTHON_TIMEOUT_MS: number;
93
- readonly MAX_OUTPUT_LENGTH: number;
94
- readonly MAX_FILE_READ_SIZE: number | undefined;
95
- readonly ENABLE_LOGGING: boolean;
96
- readonly ENABLE_TRACING: boolean;
97
- readonly ENABLE_PYTHON: boolean;
98
- readonly ENABLE_DEFENSE_IN_DEPTH: boolean;
99
- readonly DEFENSE_IN_DEPTH_AUDIT: boolean;
100
- readonly DEFENSE_IN_DEPTH_LOG: boolean;
101
- readonly OVERLAY_READ_ONLY: boolean;
102
- readonly ALLOWED_COMMANDS: CommandName[] | undefined;
110
+ readonly VERSION: string
111
+ readonly SERVER_NAME: string
112
+ readonly OVERLAY_ROOT: string | undefined
113
+ readonly READ_WRITE_ROOT: string | undefined
114
+ readonly MOUNTS_CONFIG: string | undefined
115
+ readonly INITIAL_CWD: string
116
+ readonly ALLOW_NETWORK: boolean
117
+ readonly ALLOWED_URL_PREFIXES: string[]
118
+ readonly ALLOWED_METHODS: HttpMethod[]
119
+ readonly MAX_REDIRECTS: number
120
+ readonly NETWORK_TIMEOUT_MS: number
121
+ readonly MAX_RESPONSE_SIZE: number | undefined
122
+ readonly MAX_CALL_DEPTH: number
123
+ readonly MAX_COMMAND_COUNT: number
124
+ readonly MAX_LOOP_ITERATIONS: number
125
+ readonly MAX_AWK_ITERATIONS: number
126
+ readonly MAX_SED_ITERATIONS: number
127
+ readonly MAX_JQ_ITERATIONS: number
128
+ readonly MAX_GLOB_OPERATIONS: number
129
+ readonly MAX_STRING_LENGTH: number
130
+ readonly MAX_ARRAY_ELEMENTS: number
131
+ readonly MAX_HEREDOC_SIZE: number
132
+ readonly MAX_SUBSTITUTION_DEPTH: number
133
+ readonly MAX_SQLITE_TIMEOUT_MS: number
134
+ readonly MAX_PYTHON_TIMEOUT_MS: number
135
+ readonly MAX_OUTPUT_LENGTH: number
136
+ readonly MAX_FILE_READ_SIZE: number | undefined
137
+ readonly ENABLE_LOGGING: boolean
138
+ readonly ENABLE_TRACING: boolean
139
+ readonly ENABLE_PYTHON: boolean
140
+ readonly ENABLE_JAVASCRIPT: boolean
141
+ readonly JAVASCRIPT_BOOTSTRAP: string | undefined
142
+ readonly ENABLE_DEFENSE_IN_DEPTH: boolean
143
+ readonly DEFENSE_IN_DEPTH_AUDIT: boolean
144
+ readonly DEFENSE_IN_DEPTH_LOG: boolean
145
+ readonly OVERLAY_READ_ONLY: boolean
146
+ readonly ALLOWED_COMMANDS: CommandName[] | undefined
103
147
  }
104
148
 
105
149
  function readPackageVersion(relativePath: string): string {
106
- try {
107
- const __dirname = dirname(fileURLToPath(import.meta.url));
108
- const packagePath = join(__dirname, relativePath);
109
- const packageJson = JSON.parse(readFileSync(packagePath, "utf-8")) as { version?: string };
110
- return packageJson.version || "unknown";
111
- } catch {
112
- return "unknown";
113
- }
150
+ try {
151
+ const __dirname = dirname(fileURLToPath(import.meta.url))
152
+ const packagePath = join(__dirname, relativePath)
153
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8')) as {version?: string}
154
+ return packageJson.version || 'unknown'
155
+ } catch {
156
+ return 'unknown'
157
+ }
114
158
  }
115
159
 
116
- export const WRAPPER_VERSION = readPackageVersion("../../package.json");
117
- export const UPSTREAM_JUST_BASH_VERSION = readPackageVersion(
118
- "../../node_modules/just-bash/package.json",
119
- );
160
+ export const WRAPPER_VERSION = readPackageVersion('../../package.json')
161
+ export const UPSTREAM_JUST_BASH_VERSION = readPackageVersion('../../node_modules/just-bash/package.json')
120
162
 
121
163
  function getAllowedMethods(): HttpMethod[] {
122
- const methods = parseEnvStringArray("JUST_BASH_ALLOWED_METHODS");
123
- return methods.length > 0 ? (methods as HttpMethod[]) : ["GET", "HEAD"];
164
+ const methods = parseEnvStringArray('JUST_BASH_ALLOWED_METHODS')
165
+ return methods.length > 0 ? (methods as HttpMethod[]) : ['GET', 'HEAD']
124
166
  }
125
167
 
126
168
  function getAllowedCommands(): CommandName[] | undefined {
127
- const commands = parseEnvStringArray("JUST_BASH_ALLOWED_COMMANDS");
128
- return commands.length > 0 ? (commands as CommandName[]) : undefined;
169
+ const commands = parseEnvStringArray('JUST_BASH_ALLOWED_COMMANDS')
170
+ return commands.length > 0 ? (commands as CommandName[]) : undefined
129
171
  }
130
172
 
131
173
  function parseEnvOptionalInt(key: string): number | undefined {
132
- const value = process.env[key];
133
- if (!value) return undefined;
134
- const parsed = Number.parseInt(value, 10);
135
- return Number.isNaN(parsed) ? undefined : parsed;
174
+ const value = process.env[key]
175
+ if (!value) return undefined
176
+ const parsed = Number.parseInt(value, 10)
177
+ return Number.isNaN(parsed) ? undefined : parsed
136
178
  }
137
179
 
138
180
  export const config: Config = {
139
- // Server info
140
- VERSION: WRAPPER_VERSION,
141
- SERVER_NAME: "just-bash-mcp",
142
-
143
- // Filesystem configuration
144
- OVERLAY_ROOT: process.env.JUST_BASH_OVERLAY_ROOT,
145
- READ_WRITE_ROOT: process.env.JUST_BASH_READ_WRITE_ROOT,
146
- MOUNTS_CONFIG: process.env.JUST_BASH_MOUNTS,
147
- INITIAL_CWD: parseEnvString("JUST_BASH_CWD", "/home/user"),
148
-
149
- // Network configuration
150
- ALLOW_NETWORK: parseEnvBoolean("JUST_BASH_ALLOW_NETWORK", false),
151
- ALLOWED_URL_PREFIXES: parseEnvStringArray("JUST_BASH_ALLOWED_URLS"),
152
- ALLOWED_METHODS: getAllowedMethods(),
153
- MAX_REDIRECTS: parseEnvInt("JUST_BASH_MAX_REDIRECTS", 20),
154
- NETWORK_TIMEOUT_MS: parseEnvInt("JUST_BASH_NETWORK_TIMEOUT_MS", 30000),
155
- MAX_RESPONSE_SIZE: parseEnvOptionalInt("JUST_BASH_MAX_RESPONSE_SIZE"),
156
-
157
- // Execution limits
158
- MAX_CALL_DEPTH: parseEnvInt("JUST_BASH_MAX_CALL_DEPTH", 100),
159
- MAX_COMMAND_COUNT: parseEnvInt("JUST_BASH_MAX_COMMAND_COUNT", 10000),
160
- MAX_LOOP_ITERATIONS: parseEnvInt("JUST_BASH_MAX_LOOP_ITERATIONS", 10000),
161
- MAX_AWK_ITERATIONS: parseEnvInt("JUST_BASH_MAX_AWK_ITERATIONS", 10000),
162
- MAX_SED_ITERATIONS: parseEnvInt("JUST_BASH_MAX_SED_ITERATIONS", 10000),
163
- MAX_JQ_ITERATIONS: parseEnvInt("JUST_BASH_MAX_JQ_ITERATIONS", 10000),
164
- MAX_GLOB_OPERATIONS: parseEnvInt("JUST_BASH_MAX_GLOB_OPERATIONS", 100000),
165
- MAX_STRING_LENGTH: parseEnvInt("JUST_BASH_MAX_STRING_LENGTH", 10485760),
166
- MAX_ARRAY_ELEMENTS: parseEnvInt("JUST_BASH_MAX_ARRAY_ELEMENTS", 100000),
167
- MAX_HEREDOC_SIZE: parseEnvInt("JUST_BASH_MAX_HEREDOC_SIZE", 10485760),
168
- MAX_SUBSTITUTION_DEPTH: parseEnvInt("JUST_BASH_MAX_SUBSTITUTION_DEPTH", 50),
169
- MAX_SQLITE_TIMEOUT_MS: parseEnvInt("JUST_BASH_MAX_SQLITE_TIMEOUT_MS", 5000),
170
- MAX_PYTHON_TIMEOUT_MS: parseEnvInt("JUST_BASH_MAX_PYTHON_TIMEOUT_MS", 30000),
171
-
172
- // Output limits
173
- MAX_OUTPUT_LENGTH: parseEnvInt("JUST_BASH_MAX_OUTPUT_LENGTH", 30000),
174
-
175
- // Filesystem limits
176
- MAX_FILE_READ_SIZE: parseEnvOptionalInt("JUST_BASH_MAX_FILE_READ_SIZE"),
177
-
178
- // Debugging
179
- ENABLE_LOGGING: parseEnvBoolean("JUST_BASH_ENABLE_LOGGING", false),
180
- ENABLE_TRACING: parseEnvBoolean("JUST_BASH_ENABLE_TRACING", false),
181
-
182
- // Feature flags
183
- ENABLE_PYTHON: parseEnvBoolean("JUST_BASH_ENABLE_PYTHON", false),
184
- ENABLE_DEFENSE_IN_DEPTH: parseEnvBoolean("JUST_BASH_DEFENSE_IN_DEPTH", false),
185
- DEFENSE_IN_DEPTH_AUDIT: parseEnvBoolean("JUST_BASH_DEFENSE_IN_DEPTH_AUDIT", false),
186
- DEFENSE_IN_DEPTH_LOG: parseEnvBoolean("JUST_BASH_DEFENSE_IN_DEPTH_LOG", false),
187
- OVERLAY_READ_ONLY: parseEnvBoolean("JUST_BASH_OVERLAY_READ_ONLY", false),
188
-
189
- // Command filtering
190
- ALLOWED_COMMANDS: getAllowedCommands(),
191
- };
181
+ // Server info
182
+ VERSION: WRAPPER_VERSION,
183
+ SERVER_NAME: 'just-bash-mcp',
184
+
185
+ // Filesystem configuration
186
+ OVERLAY_ROOT: process.env.JUST_BASH_OVERLAY_ROOT,
187
+ READ_WRITE_ROOT: process.env.JUST_BASH_READ_WRITE_ROOT,
188
+ MOUNTS_CONFIG: process.env.JUST_BASH_MOUNTS,
189
+ INITIAL_CWD: parseEnvString('JUST_BASH_CWD', '/home/user'),
190
+
191
+ // Network configuration
192
+ ALLOW_NETWORK: parseEnvBoolean('JUST_BASH_ALLOW_NETWORK', false),
193
+ ALLOWED_URL_PREFIXES: parseEnvStringArray('JUST_BASH_ALLOWED_URLS'),
194
+ ALLOWED_METHODS: getAllowedMethods(),
195
+ MAX_REDIRECTS: parseEnvInt('JUST_BASH_MAX_REDIRECTS', 20),
196
+ NETWORK_TIMEOUT_MS: parseEnvInt('JUST_BASH_NETWORK_TIMEOUT_MS', 30000),
197
+ MAX_RESPONSE_SIZE: parseEnvOptionalInt('JUST_BASH_MAX_RESPONSE_SIZE'),
198
+
199
+ // Execution limits
200
+ MAX_CALL_DEPTH: parseEnvInt('JUST_BASH_MAX_CALL_DEPTH', 100),
201
+ MAX_COMMAND_COUNT: parseEnvInt('JUST_BASH_MAX_COMMAND_COUNT', 10000),
202
+ MAX_LOOP_ITERATIONS: parseEnvInt('JUST_BASH_MAX_LOOP_ITERATIONS', 10000),
203
+ MAX_AWK_ITERATIONS: parseEnvInt('JUST_BASH_MAX_AWK_ITERATIONS', 10000),
204
+ MAX_SED_ITERATIONS: parseEnvInt('JUST_BASH_MAX_SED_ITERATIONS', 10000),
205
+ MAX_JQ_ITERATIONS: parseEnvInt('JUST_BASH_MAX_JQ_ITERATIONS', 10000),
206
+ MAX_GLOB_OPERATIONS: parseEnvInt('JUST_BASH_MAX_GLOB_OPERATIONS', 100000),
207
+ MAX_STRING_LENGTH: parseEnvInt('JUST_BASH_MAX_STRING_LENGTH', 10485760),
208
+ MAX_ARRAY_ELEMENTS: parseEnvInt('JUST_BASH_MAX_ARRAY_ELEMENTS', 100000),
209
+ MAX_HEREDOC_SIZE: parseEnvInt('JUST_BASH_MAX_HEREDOC_SIZE', 10485760),
210
+ MAX_SUBSTITUTION_DEPTH: parseEnvInt('JUST_BASH_MAX_SUBSTITUTION_DEPTH', 50),
211
+ MAX_SQLITE_TIMEOUT_MS: parseEnvInt('JUST_BASH_MAX_SQLITE_TIMEOUT_MS', 5000),
212
+ MAX_PYTHON_TIMEOUT_MS: parseEnvInt('JUST_BASH_MAX_PYTHON_TIMEOUT_MS', 30000),
213
+
214
+ // Output limits
215
+ MAX_OUTPUT_LENGTH: parseEnvInt('JUST_BASH_MAX_OUTPUT_LENGTH', 30000),
216
+
217
+ // Filesystem limits
218
+ MAX_FILE_READ_SIZE: parseEnvOptionalInt('JUST_BASH_MAX_FILE_READ_SIZE'),
219
+
220
+ // Debugging
221
+ ENABLE_LOGGING: parseEnvBoolean('JUST_BASH_ENABLE_LOGGING', false),
222
+ ENABLE_TRACING: parseEnvBoolean('JUST_BASH_ENABLE_TRACING', false),
223
+
224
+ // Feature flags
225
+ ENABLE_PYTHON: parseEnvBoolean('JUST_BASH_ENABLE_PYTHON', false),
226
+ ENABLE_JAVASCRIPT: parseEnvBoolean('JUST_BASH_ENABLE_JAVASCRIPT', false),
227
+ JAVASCRIPT_BOOTSTRAP: process.env.JUST_BASH_JAVASCRIPT_BOOTSTRAP,
228
+ ENABLE_DEFENSE_IN_DEPTH: parseEnvBoolean('JUST_BASH_DEFENSE_IN_DEPTH', false),
229
+ DEFENSE_IN_DEPTH_AUDIT: parseEnvBoolean('JUST_BASH_DEFENSE_IN_DEPTH_AUDIT', false),
230
+ DEFENSE_IN_DEPTH_LOG: parseEnvBoolean('JUST_BASH_DEFENSE_IN_DEPTH_LOG', false),
231
+ OVERLAY_READ_ONLY: parseEnvBoolean('JUST_BASH_OVERLAY_READ_ONLY', false),
232
+
233
+ // Command filtering
234
+ ALLOWED_COMMANDS: getAllowedCommands()
235
+ }
192
236
 
193
237
  // ============================================================================
194
238
  // Logger and Trace Callback
195
239
  // ============================================================================
196
240
 
197
241
  export const bashLogger: BashLogger | undefined = config.ENABLE_LOGGING
198
- ? {
199
- info(message: string, data?: Record<string, unknown>): void {
200
- if (data) {
201
- console.error(`[just-bash] INFO: ${message}`, data);
202
- } else {
203
- console.error(`[just-bash] INFO: ${message}`);
204
- }
205
- },
206
- debug(message: string, data?: Record<string, unknown>): void {
207
- if (data) {
208
- console.error(`[just-bash] DEBUG: ${message}`, data);
209
- } else {
210
- console.error(`[just-bash] DEBUG: ${message}`);
211
- }
212
- },
213
- }
214
- : undefined;
215
-
216
- export const traceCallback: BashOptions["trace"] = config.ENABLE_TRACING
217
- ? (event: TraceEvent) => {
218
- if (event.details) {
219
- console.error(
220
- `[just-bash] TRACE: ${event.category}/${event.name} ${event.durationMs}ms`,
221
- JSON.stringify(event.details),
222
- );
223
- } else {
224
- console.error(`[just-bash] TRACE: ${event.category}/${event.name} ${event.durationMs}ms`);
225
- }
226
- }
227
- : undefined;
242
+ ? {
243
+ info(message: string, data?: Record<string, unknown>): void {
244
+ if (data) {
245
+ console.error(`[just-bash] INFO: ${message}`, data)
246
+ } else {
247
+ console.error(`[just-bash] INFO: ${message}`)
248
+ }
249
+ },
250
+ debug(message: string, data?: Record<string, unknown>): void {
251
+ if (data) {
252
+ console.error(`[just-bash] DEBUG: ${message}`, data)
253
+ } else {
254
+ console.error(`[just-bash] DEBUG: ${message}`)
255
+ }
256
+ }
257
+ }
258
+ : undefined
259
+
260
+ export const traceCallback: BashOptions['trace'] = config.ENABLE_TRACING
261
+ ? (event: TraceEvent) => {
262
+ if (event.details) {
263
+ console.error(`[just-bash] TRACE: ${event.category}/${event.name} ${event.durationMs}ms`, JSON.stringify(event.details))
264
+ } else {
265
+ console.error(`[just-bash] TRACE: ${event.category}/${event.name} ${event.durationMs}ms`)
266
+ }
267
+ }
268
+ : undefined
269
+
270
+ let javascriptToolHandler: JavaScriptToolHandler | undefined
271
+
272
+ export function setJavaScriptToolHandler(handler: JavaScriptToolHandler | undefined): void {
273
+ javascriptToolHandler = handler
274
+ }
275
+
276
+ export function getJavaScriptToolHandler(): JavaScriptToolHandler | undefined {
277
+ return javascriptToolHandler
278
+ }
279
+
280
+ export function buildJavaScriptConfig(): BashOptions['javascript'] {
281
+ const handler = getJavaScriptToolHandler()
282
+ if (handler) {
283
+ return {...(config.JAVASCRIPT_BOOTSTRAP && {bootstrap: config.JAVASCRIPT_BOOTSTRAP}), invokeTool: (path: string, argsJson: string) => handler({path, argsJson})}
284
+ }
285
+
286
+ if (!config.ENABLE_JAVASCRIPT) {
287
+ return undefined
288
+ }
289
+
290
+ return config.JAVASCRIPT_BOOTSTRAP ? {bootstrap: config.JAVASCRIPT_BOOTSTRAP} : true
291
+ }
228
292
 
229
293
  // ============================================================================
230
294
  // Defense-in-Depth Configuration
@@ -234,29 +298,27 @@ export const traceCallback: BashOptions["trace"] = config.ENABLE_TRACING
234
298
  * Shared SecurityViolationLogger instance for tracking violations across
235
299
  * all Bash instances. Exposed so info-tools can report violation stats.
236
300
  */
237
- export const violationLogger = new SecurityViolationLogger();
301
+ export const violationLogger = new SecurityViolationLoggerCtor()
238
302
 
239
303
  /**
240
304
  * Build the defense-in-depth configuration from environment variables.
241
305
  * Returns `false` when disabled, or a full DefenseInDepthConfig object.
242
306
  */
243
307
  export function buildDefenseInDepthConfig(): DefenseInDepthConfig | false {
244
- if (!config.ENABLE_DEFENSE_IN_DEPTH) {
245
- return false;
246
- }
247
-
248
- const consoleCallback = config.DEFENSE_IN_DEPTH_LOG
249
- ? createConsoleViolationCallback()
250
- : undefined;
251
-
252
- return {
253
- enabled: true,
254
- auditMode: config.DEFENSE_IN_DEPTH_AUDIT,
255
- onViolation: (violation: { type: string; target: string; details: string }) => {
256
- violationLogger.record(violation);
257
- consoleCallback?.(violation);
258
- },
259
- };
308
+ if (!config.ENABLE_DEFENSE_IN_DEPTH) {
309
+ return false
310
+ }
311
+
312
+ const consoleCallback = config.DEFENSE_IN_DEPTH_LOG ? createConsoleViolationCallbackTyped() : undefined
313
+
314
+ return {
315
+ enabled: true,
316
+ auditMode: config.DEFENSE_IN_DEPTH_AUDIT,
317
+ onViolation: (violation: SecurityViolation) => {
318
+ violationLogger.record(violation)
319
+ consoleCallback?.(violation)
320
+ }
321
+ }
260
322
  }
261
323
 
262
324
  // ============================================================================
@@ -264,76 +326,49 @@ export function buildDefenseInDepthConfig(): DefenseInDepthConfig | false {
264
326
  // ============================================================================
265
327
 
266
328
  export function buildNetworkConfig(): NetworkConfig | undefined {
267
- if (!config.ALLOW_NETWORK) {
268
- return undefined;
269
- }
270
-
271
- if (config.ALLOWED_URL_PREFIXES.length > 0) {
272
- return {
273
- allowedUrlPrefixes: config.ALLOWED_URL_PREFIXES,
274
- allowedMethods: config.ALLOWED_METHODS,
275
- maxRedirects: config.MAX_REDIRECTS,
276
- timeoutMs: config.NETWORK_TIMEOUT_MS,
277
- ...(config.MAX_RESPONSE_SIZE !== undefined && {
278
- maxResponseSize: config.MAX_RESPONSE_SIZE,
279
- }),
280
- };
281
- }
282
-
283
- return {
284
- dangerouslyAllowFullInternetAccess: true,
285
- maxRedirects: config.MAX_REDIRECTS,
286
- timeoutMs: config.NETWORK_TIMEOUT_MS,
287
- ...(config.MAX_RESPONSE_SIZE !== undefined && {
288
- maxResponseSize: config.MAX_RESPONSE_SIZE,
289
- }),
290
- };
329
+ if (!config.ALLOW_NETWORK) {
330
+ return undefined
331
+ }
332
+
333
+ if (config.ALLOWED_URL_PREFIXES.length > 0) {
334
+ return {allowedUrlPrefixes: config.ALLOWED_URL_PREFIXES, allowedMethods: config.ALLOWED_METHODS, maxRedirects: config.MAX_REDIRECTS, timeoutMs: config.NETWORK_TIMEOUT_MS, ...(config.MAX_RESPONSE_SIZE !== undefined && {maxResponseSize: config.MAX_RESPONSE_SIZE})}
335
+ }
336
+
337
+ return {dangerouslyAllowFullInternetAccess: true, maxRedirects: config.MAX_REDIRECTS, timeoutMs: config.NETWORK_TIMEOUT_MS, ...(config.MAX_RESPONSE_SIZE !== undefined && {maxResponseSize: config.MAX_RESPONSE_SIZE})}
291
338
  }
292
339
 
293
- export function buildExecutionLimits(): NonNullable<BashOptions["executionLimits"]> {
294
- return {
295
- maxCallDepth: config.MAX_CALL_DEPTH,
296
- maxCommandCount: config.MAX_COMMAND_COUNT,
297
- maxLoopIterations: config.MAX_LOOP_ITERATIONS,
298
- maxAwkIterations: config.MAX_AWK_ITERATIONS,
299
- maxSedIterations: config.MAX_SED_ITERATIONS,
300
- maxJqIterations: config.MAX_JQ_ITERATIONS,
301
- maxGlobOperations: config.MAX_GLOB_OPERATIONS,
302
- maxStringLength: config.MAX_STRING_LENGTH,
303
- maxArrayElements: config.MAX_ARRAY_ELEMENTS,
304
- maxHeredocSize: config.MAX_HEREDOC_SIZE,
305
- maxSubstitutionDepth: config.MAX_SUBSTITUTION_DEPTH,
306
- maxSqliteTimeoutMs: config.MAX_SQLITE_TIMEOUT_MS,
307
- maxPythonTimeoutMs: config.MAX_PYTHON_TIMEOUT_MS,
308
- };
340
+ export function buildExecutionLimits(): NonNullable<BashOptions['executionLimits']> {
341
+ return {
342
+ maxCallDepth: config.MAX_CALL_DEPTH,
343
+ maxCommandCount: config.MAX_COMMAND_COUNT,
344
+ maxLoopIterations: config.MAX_LOOP_ITERATIONS,
345
+ maxAwkIterations: config.MAX_AWK_ITERATIONS,
346
+ maxSedIterations: config.MAX_SED_ITERATIONS,
347
+ maxJqIterations: config.MAX_JQ_ITERATIONS,
348
+ maxGlobOperations: config.MAX_GLOB_OPERATIONS,
349
+ maxStringLength: config.MAX_STRING_LENGTH,
350
+ maxArrayElements: config.MAX_ARRAY_ELEMENTS,
351
+ maxHeredocSize: config.MAX_HEREDOC_SIZE,
352
+ maxSubstitutionDepth: config.MAX_SUBSTITUTION_DEPTH,
353
+ maxSqliteTimeoutMs: config.MAX_SQLITE_TIMEOUT_MS,
354
+ maxPythonTimeoutMs: config.MAX_PYTHON_TIMEOUT_MS
355
+ }
309
356
  }
310
357
 
311
358
  export function parseMountsConfig(): MountConfig[] {
312
- if (!config.MOUNTS_CONFIG) return [];
313
- try {
314
- const parsed: unknown = JSON.parse(config.MOUNTS_CONFIG);
315
- if (!Array.isArray(parsed)) return [];
316
- return parsed.map(
317
- (mount: { mountPoint: string; root: string; type?: string; readOnly?: boolean }) => {
318
- const fsType = mount.type || "overlay";
319
- const maxFileReadSize = config.MAX_FILE_READ_SIZE;
320
- const filesystem =
321
- fsType === "readwrite"
322
- ? new ReadWriteFs({
323
- root: mount.root,
324
- ...(maxFileReadSize !== undefined && { maxFileReadSize }),
325
- })
326
- : new OverlayFs({
327
- root: mount.root,
328
- readOnly: mount.readOnly,
329
- ...(maxFileReadSize !== undefined && { maxFileReadSize }),
330
- });
331
- return { mountPoint: mount.mountPoint, filesystem };
332
- },
333
- );
334
- } catch {
335
- return [];
336
- }
359
+ if (!config.MOUNTS_CONFIG) return []
360
+ try {
361
+ const parsed: unknown = JSON.parse(config.MOUNTS_CONFIG)
362
+ if (!Array.isArray(parsed)) return []
363
+ return parsed.map((mount: {mountPoint: string; root: string; type?: string; readOnly?: boolean}) => {
364
+ const fsType = mount.type || 'overlay'
365
+ const maxFileReadSize = config.MAX_FILE_READ_SIZE
366
+ const filesystem = fsType === 'readwrite' ? new ReadWriteFs({root: mount.root, ...(maxFileReadSize !== undefined && {maxFileReadSize})}) : new OverlayFs({root: mount.root, readOnly: mount.readOnly, ...(maxFileReadSize !== undefined && {maxFileReadSize})})
367
+ return {mountPoint: mount.mountPoint, filesystem}
368
+ })
369
+ } catch {
370
+ return []
371
+ }
337
372
  }
338
373
 
339
374
  // ============================================================================
@@ -341,90 +376,75 @@ export function parseMountsConfig(): MountConfig[] {
341
376
  // ============================================================================
342
377
 
343
378
  export const ENVIRONMENT_VARIABLES = {
344
- JUST_BASH_OVERLAY_ROOT: "Real directory to mount as overlay (read from disk, write to memory)",
345
- JUST_BASH_OVERLAY_READ_ONLY:
346
- "If true, all writes to overlay filesystem throw errors (default: false)",
347
- JUST_BASH_READ_WRITE_ROOT: "Real directory with read-write access",
348
- JUST_BASH_MOUNTS: "JSON array of mount configurations (supports type, readOnly fields)",
349
- JUST_BASH_CWD: "Initial working directory (default: /home/user)",
350
- JUST_BASH_ALLOW_NETWORK: "Enable network access (default: false)",
351
- JUST_BASH_ALLOWED_URLS: "Comma-separated URL prefixes to allow",
352
- JUST_BASH_ALLOWED_METHODS: "Comma-separated HTTP methods (default: GET,HEAD)",
353
- JUST_BASH_ALLOWED_COMMANDS: "Comma-separated list of allowed commands",
354
- JUST_BASH_MAX_REDIRECTS: "Max HTTP redirects (default: 20)",
355
- JUST_BASH_NETWORK_TIMEOUT_MS: "Network timeout in ms (default: 30000)",
356
- JUST_BASH_MAX_RESPONSE_SIZE: "Max network response body size in bytes (default: 10MB)",
357
- JUST_BASH_MAX_CALL_DEPTH: "Max recursion depth (default: 100)",
358
- JUST_BASH_MAX_COMMAND_COUNT: "Max commands per execution (default: 10000)",
359
- JUST_BASH_MAX_LOOP_ITERATIONS: "Max bash loop iterations (default: 10000)",
360
- JUST_BASH_MAX_AWK_ITERATIONS: "Max AWK while/for loop iterations (default: 10000)",
361
- JUST_BASH_MAX_SED_ITERATIONS: "Max sed branch loop iterations (default: 10000)",
362
- JUST_BASH_MAX_JQ_ITERATIONS: "Max jq loop iterations (default: 10000)",
363
- JUST_BASH_MAX_GLOB_OPERATIONS: "Max glob filesystem operations (default: 100000)",
364
- JUST_BASH_MAX_STRING_LENGTH: "Max string length in bytes (default: 10485760 = 10MB)",
365
- JUST_BASH_MAX_ARRAY_ELEMENTS: "Max array elements (default: 100000)",
366
- JUST_BASH_MAX_HEREDOC_SIZE: "Max heredoc size in bytes (default: 10485760 = 10MB)",
367
- JUST_BASH_MAX_SUBSTITUTION_DEPTH: "Max command substitution nesting depth (default: 50)",
368
- JUST_BASH_MAX_SQLITE_TIMEOUT_MS: "SQLite timeout in ms (default: 5000)",
369
- JUST_BASH_MAX_PYTHON_TIMEOUT_MS: "Python timeout in ms (default: 30000)",
370
- JUST_BASH_MAX_OUTPUT_LENGTH: "Max output length (default: 30000)",
371
- JUST_BASH_MAX_FILE_READ_SIZE:
372
- "Max file read size in bytes for OverlayFs/ReadWriteFs (default: 10MB)",
373
- JUST_BASH_ENABLE_LOGGING: "Enable debug logging (default: false)",
374
- JUST_BASH_ENABLE_TRACING: "Enable performance tracing (default: false)",
375
- JUST_BASH_ENABLE_PYTHON:
376
- "Enable python3/python commands via the upstream emscripten CPython runtime (default: false)",
377
- JUST_BASH_DEFENSE_IN_DEPTH:
378
- "Enable defense-in-depth mode that patches dangerous JS globals (default: false)",
379
- JUST_BASH_DEFENSE_IN_DEPTH_AUDIT:
380
- "Audit mode: log violations but don't block them (default: false, requires DEFENSE_IN_DEPTH=true)",
381
- JUST_BASH_DEFENSE_IN_DEPTH_LOG:
382
- "Log violations to console via createConsoleViolationCallback (default: false)",
383
- } as const;
379
+ JUST_BASH_OVERLAY_ROOT: 'Real directory to mount as overlay (read from disk, write to memory)',
380
+ JUST_BASH_OVERLAY_READ_ONLY: 'If true, all writes to overlay filesystem throw errors (default: false)',
381
+ JUST_BASH_READ_WRITE_ROOT: 'Real directory with read-write access',
382
+ JUST_BASH_MOUNTS: 'JSON array of mount configurations (supports type, readOnly fields)',
383
+ JUST_BASH_CWD: 'Initial working directory (default: /home/user)',
384
+ JUST_BASH_ALLOW_NETWORK: 'Enable network access (default: false)',
385
+ JUST_BASH_ALLOWED_URLS: 'Comma-separated URL prefixes to allow',
386
+ JUST_BASH_ALLOWED_METHODS: 'Comma-separated HTTP methods (default: GET,HEAD)',
387
+ JUST_BASH_ALLOWED_COMMANDS: 'Comma-separated list of allowed commands',
388
+ JUST_BASH_MAX_REDIRECTS: 'Max HTTP redirects (default: 20)',
389
+ JUST_BASH_NETWORK_TIMEOUT_MS: 'Network timeout in ms (default: 30000)',
390
+ JUST_BASH_MAX_RESPONSE_SIZE: 'Max network response body size in bytes (default: 10MB)',
391
+ JUST_BASH_MAX_CALL_DEPTH: 'Max recursion depth (default: 100)',
392
+ JUST_BASH_MAX_COMMAND_COUNT: 'Max commands per execution (default: 10000)',
393
+ JUST_BASH_MAX_LOOP_ITERATIONS: 'Max bash loop iterations (default: 10000)',
394
+ JUST_BASH_MAX_AWK_ITERATIONS: 'Max AWK while/for loop iterations (default: 10000)',
395
+ JUST_BASH_MAX_SED_ITERATIONS: 'Max sed branch loop iterations (default: 10000)',
396
+ JUST_BASH_MAX_JQ_ITERATIONS: 'Max jq loop iterations (default: 10000)',
397
+ JUST_BASH_MAX_GLOB_OPERATIONS: 'Max glob filesystem operations (default: 100000)',
398
+ JUST_BASH_MAX_STRING_LENGTH: 'Max string length in bytes (default: 10485760 = 10MB)',
399
+ JUST_BASH_MAX_ARRAY_ELEMENTS: 'Max array elements (default: 100000)',
400
+ JUST_BASH_MAX_HEREDOC_SIZE: 'Max heredoc size in bytes (default: 10485760 = 10MB)',
401
+ JUST_BASH_MAX_SUBSTITUTION_DEPTH: 'Max command substitution nesting depth (default: 50)',
402
+ JUST_BASH_MAX_SQLITE_TIMEOUT_MS: 'SQLite timeout in ms (default: 5000)',
403
+ JUST_BASH_MAX_PYTHON_TIMEOUT_MS: 'Python timeout in ms (default: 30000)',
404
+ JUST_BASH_MAX_OUTPUT_LENGTH: 'Max output length (default: 30000)',
405
+ JUST_BASH_MAX_FILE_READ_SIZE: 'Max file read size in bytes for OverlayFs/ReadWriteFs (default: 10MB)',
406
+ JUST_BASH_ENABLE_LOGGING: 'Enable debug logging (default: false)',
407
+ JUST_BASH_ENABLE_TRACING: 'Enable performance tracing (default: false)',
408
+ JUST_BASH_ENABLE_PYTHON: 'Enable python3/python commands via the upstream emscripten CPython runtime (default: false)',
409
+ JUST_BASH_ENABLE_JAVASCRIPT: 'Enable js-exec command via the upstream QuickJS runtime (default: false)',
410
+ JUST_BASH_JAVASCRIPT_BOOTSTRAP: 'Bootstrap JavaScript code to run before each js-exec invocation',
411
+ JUST_BASH_DEFENSE_IN_DEPTH: 'Enable defense-in-depth mode that patches dangerous JS globals (default: false)',
412
+ JUST_BASH_DEFENSE_IN_DEPTH_AUDIT: "Audit mode: log violations but don't block them (default: false, requires DEFENSE_IN_DEPTH=true)",
413
+ JUST_BASH_DEFENSE_IN_DEPTH_LOG: 'Log violations to console via createConsoleViolationCallback (default: false)'
414
+ } as const
384
415
 
385
416
  // ============================================================================
386
417
  // Command Categories Documentation
387
418
  // ============================================================================
388
419
 
389
420
  export const COMMAND_CATEGORIES = {
390
- fileOperations: "cat, cp, file, ln, ls, mkdir, mv, readlink, rm, rmdir, split, stat, touch, tree",
391
- textProcessing:
392
- "awk, base64, column, comm, cut, diff, expand, fold, grep (egrep, fgrep), head, join, md5sum, nl, od, paste, printf, rev, rg (ripgrep), sed, sha1sum, sha256sum, sort, strings, tac, tail, tr, unexpand, uniq, wc, xargs",
393
- dataProcessing:
394
- "jq (JSON), python3/python (Python via emscripten CPython), sqlite3 (SQLite), xan (CSV), yq (YAML/XML/TOML/CSV)",
395
- compression: "gzip (gunzip, zcat), tar",
396
- navigation:
397
- "basename, cd, dirname, du, echo, env, export, find, hostname, printenv, pwd, tee, whoami",
398
- shellUtilities:
399
- "alias, bash, chmod, clear, date, expr, false, help, history, seq, sh, sleep, time, timeout, true, unalias, which",
400
- network: "curl, html-to-markdown (when network enabled)",
401
- } as const;
421
+ fileOperations: 'cat, cp, file, ln, ls, mkdir, mv, readlink, rm, rmdir, split, stat, touch, tree',
422
+ textProcessing: 'awk, base64, column, comm, cut, diff, expand, fold, grep (egrep, fgrep), head, join, md5sum, nl, od, paste, printf, rev, rg (ripgrep), sed, sha1sum, sha256sum, sort, strings, tac, tail, tr, unexpand, uniq, wc, xargs',
423
+ dataProcessing: 'jq (JSON), js-exec (JavaScript/TypeScript via QuickJS), python3/python (Python via emscripten CPython), sqlite3 (SQLite), xan (CSV), yq (YAML/XML/TOML/CSV)',
424
+ compression: 'gzip (gunzip, zcat), tar',
425
+ navigation: 'basename, cd, dirname, du, echo, env, export, find, hostname, printenv, pwd, tee, whoami',
426
+ shellUtilities: 'alias, bash, chmod, clear, date, expr, false, help, history, seq, sh, sleep, time, timeout, true, unalias, which',
427
+ network: 'curl, html-to-markdown (when network enabled)'
428
+ } as const
402
429
 
403
430
  // ============================================================================
404
431
  // Features Documentation
405
432
  // ============================================================================
406
433
 
407
434
  export const FEATURES = {
408
- customCommands:
409
- "Define custom TypeScript commands using defineCommand() from just-bash, supports lazy-loading via LazyCommand",
410
- rawScript: "Preserve leading whitespace in scripts (useful for here-docs)",
411
- logger: "Optional execution logging via BashLogger interface",
412
- trace: "Performance profiling via TraceCallback (upstream type)",
413
- commandFilter: "Restrict available commands via JUST_BASH_ALLOWED_COMMANDS env var",
414
- sandboxApi:
415
- "Additional persistent sandbox tools via bash_sandbox_* (run, write, read, mkdir, stop, reset)",
416
- python:
417
- "Python support via the upstream emscripten CPython runtime (opt-in via JUST_BASH_ENABLE_PYTHON=true)",
418
- defenseInDepth:
419
- "Defense-in-depth with SecurityViolationLogger, audit mode, and console logging (opt-in via JUST_BASH_DEFENSE_IN_DEPTH=true)",
420
- overlayReadOnly:
421
- "Read-only overlay filesystem mode (opt-in via JUST_BASH_OVERLAY_READ_ONLY=true)",
422
- networkResponseSize:
423
- "Configurable max network response body size via JUST_BASH_MAX_RESPONSE_SIZE",
424
- fileReadSizeLimit:
425
- "Configurable max file read size for OverlayFs/ReadWriteFs via JUST_BASH_MAX_FILE_READ_SIZE",
426
- networkErrorHandling:
427
- "Rich network error classification: NetworkAccessDeniedError, TooManyRedirectsError, RedirectNotAllowedError",
428
- securityViolationTracking:
429
- "SecurityViolationLogger tracks all defense-in-depth violations with stats via bash_info",
430
- } as const;
435
+ customCommands: 'Define custom TypeScript commands using defineCommand() from just-bash, supports lazy-loading via LazyCommand',
436
+ rawScript: 'Preserve leading whitespace in scripts (useful for here-docs)',
437
+ logger: 'Optional execution logging via BashLogger interface',
438
+ trace: 'Performance profiling via TraceCallback (upstream type)',
439
+ commandFilter: 'Restrict available commands via JUST_BASH_ALLOWED_COMMANDS env var',
440
+ sandboxApi: 'Additional persistent sandbox tools via bash_sandbox_* (run, write, read, mkdir, stop, reset)',
441
+ upstreamBashTool: 'MCP tool named bash mirrors the upstream bash-tool execute interface with a single command argument',
442
+ python: 'Python support via the upstream emscripten CPython runtime (opt-in via JUST_BASH_ENABLE_PYTHON=true)',
443
+ javascript: 'JavaScript/TypeScript support via upstream js-exec and optional invokeTool hook (opt-in via JUST_BASH_ENABLE_JAVASCRIPT=true or setJavaScriptToolHandler)',
444
+ defenseInDepth: 'Defense-in-depth with SecurityViolationLogger, audit mode, and console logging (opt-in via JUST_BASH_DEFENSE_IN_DEPTH=true)',
445
+ overlayReadOnly: 'Read-only overlay filesystem mode (opt-in via JUST_BASH_OVERLAY_READ_ONLY=true)',
446
+ networkResponseSize: 'Configurable max network response body size via JUST_BASH_MAX_RESPONSE_SIZE',
447
+ fileReadSizeLimit: 'Configurable max file read size for OverlayFs/ReadWriteFs via JUST_BASH_MAX_FILE_READ_SIZE',
448
+ networkErrorHandling: 'Rich network error classification: NetworkAccessDeniedError, TooManyRedirectsError, RedirectNotAllowedError',
449
+ securityViolationTracking: 'SecurityViolationLogger tracks all defense-in-depth violations with stats via bash_info'
450
+ } as const