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.
- package/README.md +47 -5
- package/package.json +3 -2
- package/src/config/index.ts +346 -326
- package/src/index.ts +8 -12
- package/src/tools/bash-instance.ts +66 -131
- package/src/tools/exec-tools.ts +92 -139
- package/src/tools/file-tools.ts +89 -182
- package/src/tools/index.ts +19 -27
- package/src/tools/info-tools.ts +103 -135
- package/src/tools/sandbox-tools.ts +114 -209
- package/src/types.ts +1 -1
- package/src/utils/index.ts +20 -55
package/src/config/index.ts
CHANGED
|
@@ -3,228 +3,292 @@
|
|
|
3
3
|
* Handles environment variable parsing and configuration building
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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 =
|
|
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
|
-
|
|
78
|
+
return process.env[key] || defaultValue
|
|
46
79
|
}
|
|
47
80
|
|
|
48
81
|
function parseEnvBoolean(key: string, defaultValue: boolean): boolean {
|
|
49
|
-
|
|
82
|
+
return process.env[key] === 'true' ? true : defaultValue
|
|
50
83
|
}
|
|
51
84
|
|
|
52
85
|
function parseEnvInt(key: string, defaultValue: number): number {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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(
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
export const traceCallback: BashOptions[
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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[
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|