just-bash-mcp 2.8.0 → 2.9.1
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 +58 -4
- package/package.json +5 -5
- package/src/config/index.ts +83 -13
- package/src/tools/bash-instance.ts +56 -4
- package/src/tools/exec-tools.ts +34 -2
- package/src/tools/index.ts +3 -0
- package/src/tools/info-tools.ts +31 -23
- package/src/tools/sandbox-tools.ts +64 -8
- package/src/types.ts +7 -0
package/README.md
CHANGED
|
@@ -7,15 +7,20 @@ An MCP (Model Context Protocol) server that provides a sandboxed bash environmen
|
|
|
7
7
|
|
|
8
8
|
Execute bash commands in a secure, isolated environment with an in-memory virtual filesystem.
|
|
9
9
|
|
|
10
|
-
Built on top of [`just-bash`](https://github.com/vercel-labs/just-bash) v2.
|
|
10
|
+
Built on top of [`just-bash`](https://github.com/vercel-labs/just-bash) v2.10.2.
|
|
11
11
|
|
|
12
|
-
## What's New in v2.
|
|
12
|
+
## What's New in v2.9.0
|
|
13
13
|
|
|
14
|
+
- **Synced with upstream `just-bash` v2.10.2** - Latest upstream commands, APIs, and type exports
|
|
15
|
+
- **Defense-in-depth mode** - Opt-in monkey-patching of dangerous JS globals (`JUST_BASH_DEFENSE_IN_DEPTH=true`)
|
|
16
|
+
- **Python support** - Python3 via Pyodide (`JUST_BASH_ENABLE_PYTHON=true`)
|
|
17
|
+
- **Vercel Sandbox API** - Compatible `bash_sandbox_*` tools for isolated execution
|
|
18
|
+
- **oxlint/oxfmt toolchain** - Replaced tsc/biome with faster oxlint and oxfmt
|
|
19
|
+
- **Configurable limits** - Fine-grained control over glob ops, string length, array size, heredoc size, and more
|
|
14
20
|
- **`rg` (ripgrep)** - Fast regex search with `--files`, `-d`, `--stats`, `-t markdown`
|
|
15
21
|
- **`tar`** - Archive support with compression
|
|
16
22
|
- **MountableFS** - Mount multiple filesystems at different paths
|
|
17
23
|
- **ReadWriteFS** - Direct read-write access to real directories
|
|
18
|
-
- **Multi-level glob patterns** - Improved `**/*.ts` style matching
|
|
19
24
|
|
|
20
25
|
## Features
|
|
21
26
|
|
|
@@ -120,6 +125,16 @@ Add to your MCP settings:
|
|
|
120
125
|
| `JUST_BASH_MAX_CALL_DEPTH` | Maximum function recursion depth | `100` |
|
|
121
126
|
| `JUST_BASH_MAX_COMMAND_COUNT` | Maximum total commands per execution | `10000` |
|
|
122
127
|
| `JUST_BASH_MAX_LOOP_ITERATIONS` | Maximum iterations per loop | `10000` |
|
|
128
|
+
| `JUST_BASH_ENABLE_PYTHON` | Enable Python3 via Pyodide (`true`/`false`) | `false` |
|
|
129
|
+
| `JUST_BASH_DEFENSE_IN_DEPTH` | Enable defense-in-depth mode (`true`/`false`) | `false` |
|
|
130
|
+
| `JUST_BASH_DEFENSE_IN_DEPTH_AUDIT` | Audit mode: log violations but don't block | `false` |
|
|
131
|
+
| `JUST_BASH_DEFENSE_IN_DEPTH_LOG` | Log violations to console | `false` |
|
|
132
|
+
| `JUST_BASH_OVERLAY_READ_ONLY` | OverlayFS read-only mode | `false` |
|
|
133
|
+
| `JUST_BASH_MAX_RESPONSE_SIZE` | Max network response body size (bytes) | `10485760` |
|
|
134
|
+
| `JUST_BASH_MAX_FILE_READ_SIZE` | Max file read size for OverlayFs/ReadWriteFs | `10485760` |
|
|
135
|
+
| `JUST_BASH_ALLOWED_COMMANDS` | Comma-separated command allow-list | all |
|
|
136
|
+
| `JUST_BASH_ENABLE_LOGGING` | Enable execution logging | `false` |
|
|
137
|
+
| `JUST_BASH_ENABLE_TRACING` | Enable performance tracing | `false` |
|
|
123
138
|
|
|
124
139
|
## Tools
|
|
125
140
|
|
|
@@ -149,9 +164,28 @@ Reset the persistent bash environment, clearing all files and state.
|
|
|
149
164
|
|
|
150
165
|
File operations in the persistent environment.
|
|
151
166
|
|
|
167
|
+
### `bash_direct_read` / `bash_direct_write`
|
|
168
|
+
|
|
169
|
+
Direct filesystem read/write operations (bypass shell execution).
|
|
170
|
+
|
|
152
171
|
### `bash_info`
|
|
153
172
|
|
|
154
|
-
Get information about the bash environment configuration.
|
|
173
|
+
Get information about the bash environment configuration, including defense-in-depth violation stats.
|
|
174
|
+
|
|
175
|
+
### `bash_get_cwd` / `bash_get_env`
|
|
176
|
+
|
|
177
|
+
Get current working directory or environment variables.
|
|
178
|
+
|
|
179
|
+
### Vercel Sandbox API
|
|
180
|
+
|
|
181
|
+
Compatible with the Vercel Sandbox API:
|
|
182
|
+
|
|
183
|
+
- `bash_sandbox_run` - Run a command in the sandbox
|
|
184
|
+
- `bash_sandbox_write_files` - Write multiple files at once
|
|
185
|
+
- `bash_sandbox_read_file` - Read a file (supports base64 encoding)
|
|
186
|
+
- `bash_sandbox_mkdir` - Create a directory
|
|
187
|
+
- `bash_sandbox_stop` - Stop and clean up the sandbox
|
|
188
|
+
- `bash_sandbox_reset` - Reset the sandbox state
|
|
155
189
|
|
|
156
190
|
## Supported Commands
|
|
157
191
|
|
|
@@ -235,6 +269,26 @@ Get information about the bash environment configuration.
|
|
|
235
269
|
- Execution limits protect against infinite loops and recursion
|
|
236
270
|
- No binary/WASM execution
|
|
237
271
|
- Network disabled by default; when enabled, URL and method allow-lists enforced
|
|
272
|
+
- **Defense-in-depth mode** (opt-in): Monkey-patches dangerous JS globals (`Function`, `eval`, `setTimeout`, `process`, etc.) during script execution to block escape vectors
|
|
273
|
+
- **SecurityViolationLogger**: Tracks all defense-in-depth violations with full stats accessible via `bash_info`
|
|
274
|
+
- **Rich network error classification**: `NetworkAccessDeniedError`, `TooManyRedirectsError`, `RedirectNotAllowedError` for precise error messages
|
|
275
|
+
|
|
276
|
+
## Upstream API Coverage
|
|
277
|
+
|
|
278
|
+
This wrapper integrates the full public API surface of `just-bash` v2.10.2:
|
|
279
|
+
|
|
280
|
+
| Category | Exports Used |
|
|
281
|
+
|----------|-------------|
|
|
282
|
+
| Core | `Bash`, `BashOptions`, `ExecOptions`, `BashExecResult` |
|
|
283
|
+
| Commands | `CommandName`, `AllCommandName`, `getCommandNames`, `getNetworkCommandNames`, `getPythonCommandNames` |
|
|
284
|
+
| Custom Commands | `defineCommand`, `CustomCommand`, `LazyCommand` |
|
|
285
|
+
| Filesystem | `InMemoryFs`, `OverlayFs`, `ReadWriteFs`, `MountableFs`, `IFileSystem` |
|
|
286
|
+
| Network | `NetworkConfig`, `NetworkAccessDeniedError`, `TooManyRedirectsError`, `RedirectNotAllowedError` |
|
|
287
|
+
| Sandbox | `Sandbox`, `SandboxCommand`, `SandboxOptions`, `OutputMessage` |
|
|
288
|
+
| Security | `DefenseInDepthBox`, `SecurityViolationLogger`, `SecurityViolationError`, `createConsoleViolationCallback` |
|
|
289
|
+
| Trace | `TraceCallback`, `TraceEvent` |
|
|
290
|
+
|
|
291
|
+
All types are re-exported from `src/types.ts` for downstream consumers.
|
|
238
292
|
|
|
239
293
|
## License
|
|
240
294
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "just-bash-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"description": "MCP server providing a sandboxed bash environment using just-bash",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
"packageManager": "bun@1.3.8",
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
64
|
-
"just-bash": "^2.
|
|
64
|
+
"just-bash": "^2.10.2",
|
|
65
65
|
"zod": "^4.3.6"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@types/node": "^25.2.
|
|
68
|
+
"@types/node": "^25.2.3",
|
|
69
69
|
"oxfmt": "^0.28.0",
|
|
70
|
-
"oxlint": "^1.
|
|
71
|
-
"oxlint-tsgolint": "^0.11.
|
|
70
|
+
"oxlint": "^1.48.0",
|
|
71
|
+
"oxlint-tsgolint": "^0.11.5"
|
|
72
72
|
}
|
|
73
73
|
}
|
package/src/config/index.ts
CHANGED
|
@@ -3,23 +3,24 @@
|
|
|
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";
|
|
6
9
|
import {
|
|
7
10
|
type BashLogger,
|
|
8
11
|
type BashOptions,
|
|
9
12
|
type CommandName,
|
|
10
|
-
|
|
11
|
-
MountableFs,
|
|
13
|
+
type DefenseInDepthConfig,
|
|
12
14
|
type MountConfig,
|
|
13
15
|
type NetworkConfig,
|
|
14
16
|
OverlayFs,
|
|
15
17
|
ReadWriteFs,
|
|
18
|
+
SecurityViolationLogger,
|
|
19
|
+
createConsoleViolationCallback,
|
|
16
20
|
} from "just-bash";
|
|
17
21
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
// ============================================================================
|
|
21
|
-
|
|
22
|
-
export type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
|
|
22
|
+
// Re-export upstream types used by other modules
|
|
23
|
+
export type { DefenseInDepthConfig };
|
|
23
24
|
|
|
24
25
|
export interface TraceEvent {
|
|
25
26
|
category: string;
|
|
@@ -30,6 +31,12 @@ export interface TraceEvent {
|
|
|
30
31
|
|
|
31
32
|
export type TraceCallback = (event: TraceEvent) => void;
|
|
32
33
|
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Types
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
export type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
|
|
39
|
+
|
|
33
40
|
// ============================================================================
|
|
34
41
|
// Environment Variable Parsing
|
|
35
42
|
// ============================================================================
|
|
@@ -89,10 +96,28 @@ export interface Config {
|
|
|
89
96
|
readonly ENABLE_TRACING: boolean;
|
|
90
97
|
readonly ENABLE_PYTHON: boolean;
|
|
91
98
|
readonly ENABLE_DEFENSE_IN_DEPTH: boolean;
|
|
99
|
+
readonly DEFENSE_IN_DEPTH_AUDIT: boolean;
|
|
100
|
+
readonly DEFENSE_IN_DEPTH_LOG: boolean;
|
|
92
101
|
readonly OVERLAY_READ_ONLY: boolean;
|
|
93
102
|
readonly ALLOWED_COMMANDS: CommandName[] | undefined;
|
|
94
103
|
}
|
|
95
104
|
|
|
105
|
+
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
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const WRAPPER_VERSION = readPackageVersion("../../package.json");
|
|
117
|
+
export const UPSTREAM_JUST_BASH_VERSION = readPackageVersion(
|
|
118
|
+
"../../node_modules/just-bash/package.json",
|
|
119
|
+
);
|
|
120
|
+
|
|
96
121
|
function getAllowedMethods(): HttpMethod[] {
|
|
97
122
|
const methods = parseEnvStringArray("JUST_BASH_ALLOWED_METHODS");
|
|
98
123
|
return methods.length > 0 ? (methods as HttpMethod[]) : ["GET", "HEAD"];
|
|
@@ -112,7 +137,7 @@ function parseEnvOptionalInt(key: string): number | undefined {
|
|
|
112
137
|
|
|
113
138
|
export const config: Config = {
|
|
114
139
|
// Server info
|
|
115
|
-
VERSION:
|
|
140
|
+
VERSION: WRAPPER_VERSION,
|
|
116
141
|
SERVER_NAME: "just-bash-mcp",
|
|
117
142
|
|
|
118
143
|
// Filesystem configuration
|
|
@@ -157,6 +182,8 @@ export const config: Config = {
|
|
|
157
182
|
// Feature flags
|
|
158
183
|
ENABLE_PYTHON: parseEnvBoolean("JUST_BASH_ENABLE_PYTHON", false),
|
|
159
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),
|
|
160
187
|
OVERLAY_READ_ONLY: parseEnvBoolean("JUST_BASH_OVERLAY_READ_ONLY", false),
|
|
161
188
|
|
|
162
189
|
// Command filtering
|
|
@@ -186,7 +213,7 @@ export const bashLogger: BashLogger | undefined = config.ENABLE_LOGGING
|
|
|
186
213
|
}
|
|
187
214
|
: undefined;
|
|
188
215
|
|
|
189
|
-
export const traceCallback:
|
|
216
|
+
export const traceCallback: BashOptions["trace"] = config.ENABLE_TRACING
|
|
190
217
|
? (event: TraceEvent) => {
|
|
191
218
|
if (event.details) {
|
|
192
219
|
console.error(
|
|
@@ -199,6 +226,39 @@ export const traceCallback: TraceCallback | undefined = config.ENABLE_TRACING
|
|
|
199
226
|
}
|
|
200
227
|
: undefined;
|
|
201
228
|
|
|
229
|
+
// ============================================================================
|
|
230
|
+
// Defense-in-Depth Configuration
|
|
231
|
+
// ============================================================================
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Shared SecurityViolationLogger instance for tracking violations across
|
|
235
|
+
* all Bash instances. Exposed so info-tools can report violation stats.
|
|
236
|
+
*/
|
|
237
|
+
export const violationLogger = new SecurityViolationLogger();
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Build the defense-in-depth configuration from environment variables.
|
|
241
|
+
* Returns `false` when disabled, or a full DefenseInDepthConfig object.
|
|
242
|
+
*/
|
|
243
|
+
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
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
202
262
|
// ============================================================================
|
|
203
263
|
// Configuration Builders
|
|
204
264
|
// ============================================================================
|
|
@@ -315,6 +375,10 @@ export const ENVIRONMENT_VARIABLES = {
|
|
|
315
375
|
JUST_BASH_ENABLE_PYTHON: "Enable python3/python commands via Pyodide (default: false)",
|
|
316
376
|
JUST_BASH_DEFENSE_IN_DEPTH:
|
|
317
377
|
"Enable defense-in-depth mode that patches dangerous JS globals (default: false)",
|
|
378
|
+
JUST_BASH_DEFENSE_IN_DEPTH_AUDIT:
|
|
379
|
+
"Audit mode: log violations but don't block them (default: false, requires DEFENSE_IN_DEPTH=true)",
|
|
380
|
+
JUST_BASH_DEFENSE_IN_DEPTH_LOG:
|
|
381
|
+
"Log violations to console via createConsoleViolationCallback (default: false)",
|
|
318
382
|
} as const;
|
|
319
383
|
|
|
320
384
|
// ============================================================================
|
|
@@ -340,19 +404,25 @@ export const COMMAND_CATEGORIES = {
|
|
|
340
404
|
// ============================================================================
|
|
341
405
|
|
|
342
406
|
export const FEATURES = {
|
|
343
|
-
customCommands:
|
|
407
|
+
customCommands:
|
|
408
|
+
"Define custom TypeScript commands using defineCommand() from just-bash, supports lazy-loading via LazyCommand",
|
|
344
409
|
rawScript: "Preserve leading whitespace in scripts (useful for here-docs)",
|
|
345
410
|
logger: "Optional execution logging via BashLogger interface",
|
|
346
|
-
trace: "Performance profiling via TraceCallback",
|
|
411
|
+
trace: "Performance profiling via TraceCallback (upstream type)",
|
|
347
412
|
commandFilter: "Restrict available commands via JUST_BASH_ALLOWED_COMMANDS env var",
|
|
348
|
-
sandboxApi:
|
|
413
|
+
sandboxApi:
|
|
414
|
+
"Vercel Sandbox compatible API via bash_sandbox_* tools (run, write, read, mkdir, stop, reset)",
|
|
349
415
|
python: "Python support via Pyodide (opt-in via JUST_BASH_ENABLE_PYTHON=true)",
|
|
350
416
|
defenseInDepth:
|
|
351
|
-
"Defense-in-depth
|
|
417
|
+
"Defense-in-depth with SecurityViolationLogger, audit mode, and console logging (opt-in via JUST_BASH_DEFENSE_IN_DEPTH=true)",
|
|
352
418
|
overlayReadOnly:
|
|
353
419
|
"Read-only overlay filesystem mode (opt-in via JUST_BASH_OVERLAY_READ_ONLY=true)",
|
|
354
420
|
networkResponseSize:
|
|
355
421
|
"Configurable max network response body size via JUST_BASH_MAX_RESPONSE_SIZE",
|
|
356
422
|
fileReadSizeLimit:
|
|
357
423
|
"Configurable max file read size for OverlayFs/ReadWriteFs via JUST_BASH_MAX_FILE_READ_SIZE",
|
|
424
|
+
networkErrorHandling:
|
|
425
|
+
"Rich network error classification: NetworkAccessDeniedError, TooManyRedirectsError, RedirectNotAllowedError",
|
|
426
|
+
securityViolationTracking:
|
|
427
|
+
"SecurityViolationLogger tracks all defense-in-depth violations with stats via bash_info",
|
|
358
428
|
} as const;
|
|
@@ -1,33 +1,79 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bash instance management
|
|
3
3
|
* Handles creation and lifecycle of Bash instances
|
|
4
|
+
*
|
|
5
|
+
* Uses all upstream just-bash APIs:
|
|
6
|
+
* - Bash, Sandbox, SandboxCommand for execution
|
|
7
|
+
* - DefenseInDepthBox with SecurityViolationLogger for security monitoring
|
|
8
|
+
* - defineCommand for custom command registration
|
|
9
|
+
* - All filesystem variants (InMemoryFs, MountableFs, OverlayFs, ReadWriteFs)
|
|
10
|
+
* - Network error classes for rich error reporting (handled in exec-tools/sandbox-tools)
|
|
4
11
|
*/
|
|
5
12
|
|
|
6
13
|
import {
|
|
7
14
|
Bash,
|
|
8
15
|
type BashOptions,
|
|
9
16
|
type CustomCommand,
|
|
17
|
+
DefenseInDepthBox,
|
|
10
18
|
InMemoryFs,
|
|
11
19
|
MountableFs,
|
|
12
20
|
OverlayFs,
|
|
13
21
|
ReadWriteFs,
|
|
14
22
|
Sandbox,
|
|
15
23
|
type SandboxOptions,
|
|
24
|
+
defineCommand,
|
|
16
25
|
} from "just-bash";
|
|
17
26
|
|
|
18
27
|
import {
|
|
19
28
|
bashLogger,
|
|
29
|
+
buildDefenseInDepthConfig,
|
|
20
30
|
buildExecutionLimits,
|
|
21
31
|
buildNetworkConfig,
|
|
22
32
|
config,
|
|
23
33
|
parseMountsConfig,
|
|
24
34
|
traceCallback,
|
|
35
|
+
violationLogger,
|
|
25
36
|
} from "../config/index.ts";
|
|
26
37
|
|
|
27
38
|
// ============================================================================
|
|
28
39
|
// Bash Instance Factory
|
|
29
40
|
// ============================================================================
|
|
30
41
|
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// Defense-in-Depth Box (singleton)
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
46
|
+
let defenseInDepthBox: DefenseInDepthBox | null = null;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get or create the shared DefenseInDepthBox instance.
|
|
50
|
+
* Returns null if defense-in-depth is disabled.
|
|
51
|
+
*/
|
|
52
|
+
export function getDefenseInDepthBox(): DefenseInDepthBox | null {
|
|
53
|
+
const didConfig = buildDefenseInDepthConfig();
|
|
54
|
+
if (!didConfig) return null;
|
|
55
|
+
|
|
56
|
+
if (!defenseInDepthBox) {
|
|
57
|
+
defenseInDepthBox = new DefenseInDepthBox(didConfig);
|
|
58
|
+
}
|
|
59
|
+
return defenseInDepthBox;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the shared SecurityViolationLogger for querying violation stats.
|
|
64
|
+
*/
|
|
65
|
+
export { violationLogger } from "../config/index.ts";
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Re-export defineCommand so downstream consumers can create custom commands
|
|
69
|
+
* using the upstream API without importing just-bash directly.
|
|
70
|
+
*/
|
|
71
|
+
export { defineCommand };
|
|
72
|
+
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// Bash Instance Factory
|
|
75
|
+
// ============================================================================
|
|
76
|
+
|
|
31
77
|
/**
|
|
32
78
|
* Create a new Bash instance with the given configuration
|
|
33
79
|
*/
|
|
@@ -38,6 +84,7 @@ export function createBashInstance(
|
|
|
38
84
|
): Bash {
|
|
39
85
|
const networkConfig = buildNetworkConfig();
|
|
40
86
|
const executionLimits = buildExecutionLimits();
|
|
87
|
+
const defenseInDepthConfig = buildDefenseInDepthConfig();
|
|
41
88
|
|
|
42
89
|
const baseOptions: BashOptions = {
|
|
43
90
|
network: networkConfig,
|
|
@@ -49,7 +96,7 @@ export function createBashInstance(
|
|
|
49
96
|
customCommands,
|
|
50
97
|
commands: config.ALLOWED_COMMANDS,
|
|
51
98
|
python: config.ENABLE_PYTHON,
|
|
52
|
-
defenseInDepth:
|
|
99
|
+
defenseInDepth: defenseInDepthConfig,
|
|
53
100
|
};
|
|
54
101
|
|
|
55
102
|
// Check for mountable filesystem configuration
|
|
@@ -134,7 +181,8 @@ export function resetPersistentBash(): void {
|
|
|
134
181
|
let persistentSandbox: Sandbox | null = null;
|
|
135
182
|
|
|
136
183
|
/**
|
|
137
|
-
* Get or create the persistent Sandbox instance
|
|
184
|
+
* Get or create the persistent Sandbox instance.
|
|
185
|
+
* Passes all available configuration including network and filesystem options.
|
|
138
186
|
*/
|
|
139
187
|
export async function getPersistentSandbox(): Promise<Sandbox> {
|
|
140
188
|
if (!persistentSandbox) {
|
|
@@ -153,8 +201,12 @@ export async function getPersistentSandbox(): Promise<Sandbox> {
|
|
|
153
201
|
}
|
|
154
202
|
|
|
155
203
|
/**
|
|
156
|
-
* Reset the persistent Sandbox instance
|
|
204
|
+
* Reset the persistent Sandbox instance.
|
|
205
|
+
* Calls Sandbox.stop() to clean up resources before releasing.
|
|
157
206
|
*/
|
|
158
|
-
export function resetPersistentSandbox(): void {
|
|
207
|
+
export async function resetPersistentSandbox(): Promise<void> {
|
|
208
|
+
if (persistentSandbox) {
|
|
209
|
+
await persistentSandbox.stop();
|
|
210
|
+
}
|
|
159
211
|
persistentSandbox = null;
|
|
160
212
|
}
|
package/src/tools/exec-tools.ts
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bash execution tools
|
|
3
3
|
* Core tools for executing bash commands
|
|
4
|
+
*
|
|
5
|
+
* Uses upstream network error classes for rich error classification:
|
|
6
|
+
* - NetworkAccessDeniedError: URL not in allowlist
|
|
7
|
+
* - TooManyRedirectsError: Redirect limit exceeded
|
|
8
|
+
* - RedirectNotAllowedError: Redirect target not in allowlist
|
|
9
|
+
* - SecurityViolationError: Defense-in-depth violation detected
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
12
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
|
+
import {
|
|
14
|
+
NetworkAccessDeniedError,
|
|
15
|
+
RedirectNotAllowedError,
|
|
16
|
+
SecurityViolationError,
|
|
17
|
+
TooManyRedirectsError,
|
|
18
|
+
} from "just-bash";
|
|
7
19
|
import { z } from "zod/v4";
|
|
8
20
|
import { createErrorResponse, formatExecResult } from "../utils/index.ts";
|
|
9
21
|
import { createBashInstance, getPersistentBash, resetPersistentBash } from "./bash-instance.ts";
|
|
10
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Classify errors from just-bash into user-friendly messages.
|
|
25
|
+
* Uses upstream error classes for precise classification.
|
|
26
|
+
*/
|
|
27
|
+
function classifyError(error: unknown, prefix: string) {
|
|
28
|
+
if (error instanceof NetworkAccessDeniedError) {
|
|
29
|
+
return createErrorResponse(error, `${prefix} [Network Access Denied]`);
|
|
30
|
+
}
|
|
31
|
+
if (error instanceof TooManyRedirectsError) {
|
|
32
|
+
return createErrorResponse(error, `${prefix} [Too Many Redirects]`);
|
|
33
|
+
}
|
|
34
|
+
if (error instanceof RedirectNotAllowedError) {
|
|
35
|
+
return createErrorResponse(error, `${prefix} [Redirect Not Allowed]`);
|
|
36
|
+
}
|
|
37
|
+
if (error instanceof SecurityViolationError) {
|
|
38
|
+
return createErrorResponse(error, `${prefix} [Security Violation]`);
|
|
39
|
+
}
|
|
40
|
+
return createErrorResponse(error, prefix);
|
|
41
|
+
}
|
|
42
|
+
|
|
11
43
|
/**
|
|
12
44
|
* Register bash execution tools with the MCP server
|
|
13
45
|
*/
|
|
@@ -63,7 +95,7 @@ export function registerExecTools(server: McpServer): void {
|
|
|
63
95
|
const result = await bash.exec(command, { cwd, env, rawScript });
|
|
64
96
|
return formatExecResult(result);
|
|
65
97
|
} catch (error) {
|
|
66
|
-
return
|
|
98
|
+
return classifyError(error, "Execution error");
|
|
67
99
|
}
|
|
68
100
|
},
|
|
69
101
|
);
|
|
@@ -104,7 +136,7 @@ export function registerExecTools(server: McpServer): void {
|
|
|
104
136
|
const result = await bash.exec(command, { cwd, env, rawScript });
|
|
105
137
|
return formatExecResult(result);
|
|
106
138
|
} catch (error) {
|
|
107
|
-
return
|
|
139
|
+
return classifyError(error, "Execution error");
|
|
108
140
|
}
|
|
109
141
|
},
|
|
110
142
|
);
|
package/src/tools/index.ts
CHANGED
|
@@ -13,10 +13,13 @@ import { registerSandboxTools } from "./sandbox-tools.ts";
|
|
|
13
13
|
// Re-export bash instance utilities
|
|
14
14
|
export {
|
|
15
15
|
createBashInstance,
|
|
16
|
+
defineCommand,
|
|
17
|
+
getDefenseInDepthBox,
|
|
16
18
|
getPersistentBash,
|
|
17
19
|
getPersistentSandbox,
|
|
18
20
|
resetPersistentBash,
|
|
19
21
|
resetPersistentSandbox,
|
|
22
|
+
violationLogger,
|
|
20
23
|
} from "./bash-instance.ts";
|
|
21
24
|
// Re-export individual registrations for fine-grained control
|
|
22
25
|
export { registerExecTools } from "./exec-tools.ts";
|
package/src/tools/info-tools.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Information and state tools
|
|
3
3
|
* Tools for getting information about the bash environment
|
|
4
|
+
*
|
|
5
|
+
* Uses upstream command registry types (AllCommandName, CommandName) and
|
|
6
|
+
* SecurityViolationLogger for defense-in-depth violation reporting.
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
|
-
import { readFileSync } from "node:fs";
|
|
7
|
-
import { dirname, join } from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
9
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
10
|
import {
|
|
11
|
+
type AllCommandName,
|
|
11
12
|
type CommandName,
|
|
12
13
|
getCommandNames,
|
|
13
14
|
getNetworkCommandNames,
|
|
@@ -20,25 +21,11 @@ import {
|
|
|
20
21
|
ENVIRONMENT_VARIABLES,
|
|
21
22
|
FEATURES,
|
|
22
23
|
parseMountsConfig,
|
|
24
|
+
UPSTREAM_JUST_BASH_VERSION,
|
|
25
|
+
violationLogger,
|
|
23
26
|
} from "../config/index.ts";
|
|
24
27
|
import { createErrorResponse, createJsonResponse } from "../utils/index.ts";
|
|
25
|
-
import { getPersistentBash } from "./bash-instance.ts";
|
|
26
|
-
|
|
27
|
-
function getUpstreamVersion(): string {
|
|
28
|
-
try {
|
|
29
|
-
// Resolve the just-bash package directory relative to this module
|
|
30
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31
|
-
const pkgPath = join(__dirname, "..", "..", "node_modules", "just-bash", "package.json");
|
|
32
|
-
const pkg: { version: string } = JSON.parse(readFileSync(pkgPath, "utf-8")) as {
|
|
33
|
-
version: string;
|
|
34
|
-
};
|
|
35
|
-
return pkg.version;
|
|
36
|
-
} catch {
|
|
37
|
-
return "unknown";
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const UPSTREAM_VERSION = getUpstreamVersion();
|
|
28
|
+
import { getDefenseInDepthBox, getPersistentBash } from "./bash-instance.ts";
|
|
42
29
|
|
|
43
30
|
/**
|
|
44
31
|
* Register information tools with the MCP server
|
|
@@ -67,12 +54,33 @@ export function registerInfoTools(server: McpServer): void {
|
|
|
67
54
|
// Get actual available commands (respects ALLOWED_COMMANDS filter)
|
|
68
55
|
const allBuiltinCommands = getCommandNames();
|
|
69
56
|
const availableCommands = config.ALLOWED_COMMANDS
|
|
70
|
-
? allBuiltinCommands.filter((cmd) =>
|
|
57
|
+
? allBuiltinCommands.filter((cmd) =>
|
|
58
|
+
config.ALLOWED_COMMANDS?.includes(cmd as AllCommandName as CommandName),
|
|
59
|
+
)
|
|
71
60
|
: allBuiltinCommands;
|
|
72
61
|
|
|
62
|
+
// Build defense-in-depth status with violation stats
|
|
63
|
+
const didBox = getDefenseInDepthBox();
|
|
64
|
+
const defenseInDepthStatus = config.ENABLE_DEFENSE_IN_DEPTH
|
|
65
|
+
? {
|
|
66
|
+
enabled: true,
|
|
67
|
+
auditMode: config.DEFENSE_IN_DEPTH_AUDIT,
|
|
68
|
+
consoleLogging: config.DEFENSE_IN_DEPTH_LOG,
|
|
69
|
+
boxActive: didBox?.isActive() ?? false,
|
|
70
|
+
...(didBox && {
|
|
71
|
+
stats: didBox.getStats(),
|
|
72
|
+
}),
|
|
73
|
+
violations: {
|
|
74
|
+
total: violationLogger.getTotalCount(),
|
|
75
|
+
hasViolations: violationLogger.hasViolations(),
|
|
76
|
+
summary: violationLogger.getSummary(),
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
: { enabled: false };
|
|
80
|
+
|
|
73
81
|
const info = {
|
|
74
82
|
version: config.VERSION,
|
|
75
|
-
upstreamVersion:
|
|
83
|
+
upstreamVersion: UPSTREAM_JUST_BASH_VERSION,
|
|
76
84
|
fsMode,
|
|
77
85
|
fsRoot: config.READ_WRITE_ROOT || config.OVERLAY_ROOT || null,
|
|
78
86
|
overlayReadOnly: config.OVERLAY_ROOT ? config.OVERLAY_READ_ONLY : null,
|
|
@@ -88,7 +96,7 @@ export function registerInfoTools(server: McpServer): void {
|
|
|
88
96
|
loggingEnabled: config.ENABLE_LOGGING,
|
|
89
97
|
tracingEnabled: config.ENABLE_TRACING,
|
|
90
98
|
pythonEnabled: config.ENABLE_PYTHON,
|
|
91
|
-
|
|
99
|
+
defenseInDepth: defenseInDepthStatus,
|
|
92
100
|
commandFilter: config.ALLOWED_COMMANDS || null,
|
|
93
101
|
executionLimits: buildExecutionLimits(),
|
|
94
102
|
availableCommands,
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sandbox API tools
|
|
3
3
|
* Vercel Sandbox compatible tools for execution in an isolated environment
|
|
4
|
+
*
|
|
5
|
+
* Uses upstream Sandbox/SandboxCommand APIs:
|
|
6
|
+
* - Sandbox.create(), runCommand(), writeFiles(), readFile(), mkDir(), stop()
|
|
7
|
+
* - SandboxCommand: wait(), stdout(), stderr(), output(), logs(), kill()
|
|
8
|
+
* - Sandbox.domain getter for domain info
|
|
9
|
+
* - OutputMessage type for streaming output
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
12
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
|
+
import {
|
|
14
|
+
NetworkAccessDeniedError,
|
|
15
|
+
RedirectNotAllowedError,
|
|
16
|
+
SecurityViolationError,
|
|
17
|
+
TooManyRedirectsError,
|
|
18
|
+
} from "just-bash";
|
|
7
19
|
import { z } from "zod/v4";
|
|
8
20
|
import { config } from "../config/index.ts";
|
|
9
21
|
import {
|
|
@@ -14,6 +26,25 @@ import {
|
|
|
14
26
|
} from "../utils/index.ts";
|
|
15
27
|
import { getPersistentSandbox, resetPersistentSandbox } from "./bash-instance.ts";
|
|
16
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Classify errors from just-bash into user-friendly messages.
|
|
31
|
+
*/
|
|
32
|
+
function classifyError(error: unknown, prefix: string) {
|
|
33
|
+
if (error instanceof NetworkAccessDeniedError) {
|
|
34
|
+
return createErrorResponse(error, `${prefix} [Network Access Denied]`);
|
|
35
|
+
}
|
|
36
|
+
if (error instanceof TooManyRedirectsError) {
|
|
37
|
+
return createErrorResponse(error, `${prefix} [Too Many Redirects]`);
|
|
38
|
+
}
|
|
39
|
+
if (error instanceof RedirectNotAllowedError) {
|
|
40
|
+
return createErrorResponse(error, `${prefix} [Redirect Not Allowed]`);
|
|
41
|
+
}
|
|
42
|
+
if (error instanceof SecurityViolationError) {
|
|
43
|
+
return createErrorResponse(error, `${prefix} [Security Violation]`);
|
|
44
|
+
}
|
|
45
|
+
return createErrorResponse(error, prefix);
|
|
46
|
+
}
|
|
47
|
+
|
|
17
48
|
/**
|
|
18
49
|
* Register Vercel Sandbox compatible tools with the MCP server
|
|
19
50
|
*/
|
|
@@ -57,7 +88,7 @@ export function registerSandboxTools(server: McpServer): void {
|
|
|
57
88
|
result.exitCode !== 0,
|
|
58
89
|
);
|
|
59
90
|
} catch (error) {
|
|
60
|
-
return
|
|
91
|
+
return classifyError(error, "Sandbox error");
|
|
61
92
|
}
|
|
62
93
|
},
|
|
63
94
|
);
|
|
@@ -82,7 +113,7 @@ export function registerSandboxTools(server: McpServer): void {
|
|
|
82
113
|
`Successfully wrote ${Object.keys(files).length} file(s): ${Object.keys(files).join(", ")}`,
|
|
83
114
|
);
|
|
84
115
|
} catch (error) {
|
|
85
|
-
return
|
|
116
|
+
return classifyError(error, "Write error");
|
|
86
117
|
}
|
|
87
118
|
},
|
|
88
119
|
);
|
|
@@ -96,12 +127,13 @@ export function registerSandboxTools(server: McpServer): void {
|
|
|
96
127
|
description: "Read a file from the sandbox environment.",
|
|
97
128
|
inputSchema: {
|
|
98
129
|
path: z.string().describe("The file path to read"),
|
|
130
|
+
encoding: z.enum(["utf-8", "base64"]).optional().describe("File encoding (default: utf-8)"),
|
|
99
131
|
},
|
|
100
132
|
},
|
|
101
|
-
async ({ path }: { path: string }) => {
|
|
133
|
+
async ({ path, encoding = "utf-8" }: { path: string; encoding?: "utf-8" | "base64" }) => {
|
|
102
134
|
try {
|
|
103
135
|
const sandbox = await getPersistentSandbox();
|
|
104
|
-
const content = await sandbox.readFile(path);
|
|
136
|
+
const content = await sandbox.readFile(path, encoding);
|
|
105
137
|
|
|
106
138
|
return {
|
|
107
139
|
content: [
|
|
@@ -112,7 +144,7 @@ export function registerSandboxTools(server: McpServer): void {
|
|
|
112
144
|
],
|
|
113
145
|
};
|
|
114
146
|
} catch (error) {
|
|
115
|
-
return
|
|
147
|
+
return classifyError(error, "Read error");
|
|
116
148
|
}
|
|
117
149
|
},
|
|
118
150
|
);
|
|
@@ -139,7 +171,27 @@ export function registerSandboxTools(server: McpServer): void {
|
|
|
139
171
|
|
|
140
172
|
return createSuccessResponse(`Successfully created directory: ${path}`);
|
|
141
173
|
} catch (error) {
|
|
142
|
-
return
|
|
174
|
+
return classifyError(error, "Mkdir error");
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// ========================================================================
|
|
180
|
+
// bash_sandbox_stop - Stop and clean up sandbox
|
|
181
|
+
// ========================================================================
|
|
182
|
+
server.registerTool(
|
|
183
|
+
"bash_sandbox_stop",
|
|
184
|
+
{
|
|
185
|
+
description:
|
|
186
|
+
"Stop and clean up the sandbox environment, releasing all resources. Use bash_sandbox_reset to just clear state.",
|
|
187
|
+
inputSchema: {},
|
|
188
|
+
},
|
|
189
|
+
async () => {
|
|
190
|
+
try {
|
|
191
|
+
await resetPersistentSandbox();
|
|
192
|
+
return createSuccessResponse("Sandbox environment has been stopped and cleaned up.");
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return classifyError(error, "Stop error");
|
|
143
195
|
}
|
|
144
196
|
},
|
|
145
197
|
);
|
|
@@ -154,8 +206,12 @@ export function registerSandboxTools(server: McpServer): void {
|
|
|
154
206
|
inputSchema: {},
|
|
155
207
|
},
|
|
156
208
|
async () => {
|
|
157
|
-
|
|
158
|
-
|
|
209
|
+
try {
|
|
210
|
+
await resetPersistentSandbox();
|
|
211
|
+
return createSuccessResponse("Sandbox environment has been reset.");
|
|
212
|
+
} catch (error) {
|
|
213
|
+
return classifyError(error, "Reset error");
|
|
214
|
+
}
|
|
159
215
|
},
|
|
160
216
|
);
|
|
161
217
|
}
|