@prodisco/sandbox-server
v0.1.6
Published
gRPC sandbox server for code execution with Kubernetes context
Readme
@prodisco/sandbox-server
A gRPC-based sandbox server for secure TypeScript/JavaScript code execution with Kubernetes and Prometheus context. Designed to decouple code execution from MCP servers, enabling flexible deployment options and improved isolation.
Table of Contents
- Features
- Installation
- Quick Start
- Execution Modes
- Sandbox Environment
- Configuration
- API Reference
- Docker & Kubernetes Deployment
- Development
- Protocol
- Architecture
- License
Features
- Secure Sandboxed Execution - Run untrusted code in Node.js VM with restricted module access
- TypeScript Support - Execute TypeScript directly with esbuild transformation
- Multiple Execution Modes - Synchronous, streaming, async, and test execution
- Unit Testing - Built-in test runner with structured results using uvu assertions
- Kubernetes Integration - Pre-configured access to
@kubernetes/client-node - Prometheus/Loki Support - Query metrics and logs from within scripts
- Script Caching - Automatic caching with content-based deduplication
- Flexible Transport - Unix socket (default) or TCP with optional TLS/mTLS
- Container Ready - Dockerfile and Kubernetes manifests included
Installation
npm install @prodisco/sandbox-serverQuick Start
Starting the Server
# Using the CLI
npx sandbox-server
# Or programmatically
import { startServer } from '@prodisco/sandbox-server/server';
const server = await startServer({
socketPath: '/tmp/prodisco-sandbox.sock',
cacheDir: '/tmp/prodisco-scripts',
});Using the Client
import { SandboxClient } from '@prodisco/sandbox-server/client';
const client = new SandboxClient();
// Simple execution
const result = await client.execute({
code: `
const k8s = require('@kubernetes/client-node');
console.log('Kubernetes client loaded:', typeof k8s.CoreV1Api);
`,
timeoutMs: 30000,
});
console.log(result.success); // true
console.log(result.output); // "Kubernetes client loaded: function"Execution Modes
| Mode | Method | Use Case |
|------|--------|----------|
| Synchronous | execute() | Simple scripts, short execution time |
| Streaming | executeStream() | Real-time output, long-running scripts |
| Async | executeAsync() | Background execution with polling |
| Test | executeTest() | Unit testing with structured results |
Synchronous Execution
const result = await client.execute({
code: 'console.log("Hello, World!");',
timeoutMs: 30000,
});
// Result:
// {
// success: true,
// output: "Hello, World!",
// executionTimeMs: 45
// }Streaming Execution
for await (const chunk of client.executeStream({ code: longRunningScript })) {
if (chunk.type === 'output') {
process.stdout.write(chunk.data);
} else if (chunk.type === 'error') {
process.stderr.write(chunk.data);
} else if (chunk.type === 'result') {
console.log('Finished:', chunk.data.success);
}
}Async Execution with Polling
// Start execution
const { executionId } = await client.executeAsync({
code: `
for (let i = 0; i < 10; i++) {
console.log("Processing:", i);
await new Promise(r => setTimeout(r, 100));
}
`,
});
// Poll for status
const status = await client.getExecution(executionId, { wait: true });
console.log(status.state); // 'completed'
console.log(status.output); // Processing output
// Or cancel if needed
await client.cancelExecution(executionId);Test Execution
Run unit tests with structured results using the built-in uvu test framework:
const result = await client.executeTest({
code: `
function fibonacci(n: number): number[] {
if (n <= 0) return [];
if (n === 1) return [0];
const seq = [0, 1];
for (let i = 2; i < n; i++) {
seq.push(seq[i-1] + seq[i-2]);
}
return seq;
}
`,
tests: `
test("fibonacci(0) returns empty array", () => {
assert.equal(fibonacci(0), []);
});
test("fibonacci(5) returns correct sequence", () => {
assert.equal(fibonacci(5), [0, 1, 1, 2, 3]);
});
test("each number is sum of previous two", () => {
const seq = fibonacci(10);
for (let i = 2; i < seq.length; i++) {
assert.is(seq[i], seq[i-1] + seq[i-2]);
}
});
`,
});
// Result:
// {
// success: true,
// summary: { total: 3, passed: 3, failed: 0, skipped: 0 },
// tests: [
// { name: 'fibonacci(0) returns empty array', passed: true, durationMs: 0 },
// { name: 'fibonacci(5) returns correct sequence', passed: true, durationMs: 0 },
// { name: 'each number is sum of previous two', passed: true, durationMs: 1 }
// ],
// executionTimeMs: 45
// }Important: In test mode, test() and assert are pre-injected globals. Do NOT import them.
Available Assertions:
| Assertion | Description |
|-----------|-------------|
| assert.is(a, b) | Strict equality (===) |
| assert.equal(a, b) | Deep equality for objects/arrays |
| assert.ok(val) | Truthy check |
| assert.not(val) | Falsy check |
| assert.throws(fn) | Expects function to throw |
Sandbox Environment
Allowed Modules
By default, the sandbox allows these modules:
@kubernetes/client-node- Kubernetes API client@prodisco/prometheus-client- Prometheus metrics queries@prodisco/loki-client- Loki log queriessimple-statistics- Statistical analysis functionsuvu- Test framework (always available for test mode)
Available Globals
// Console methods
console.log(), console.error(), console.warn(), console.info()
// Timers
setTimeout, setInterval, clearTimeout, clearInterval
// Built-ins
Promise, JSON, Buffer, Date, Math, Array, Object, String, Number, Boolean, Error
// Environment
process.env // Read-only access to environment variablesCustom Module Configuration
Configure allowed modules via environment variable or config file:
# Environment variable (JSON array or comma-separated)
export SANDBOX_ALLOWED_MODULES='["@kubernetes/client-node","lodash"]'
# Or via config file
export PRODISCO_CONFIG_PATH=/path/to/config.yamlConfig file format:
libraries:
- name: "@kubernetes/client-node"
description: "Kubernetes API client"
- name: "lodash"
description: "Utility library"Configuration
Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| SANDBOX_SOCKET_PATH | /tmp/prodisco-sandbox.sock | Unix socket path |
| SANDBOX_USE_TCP | false | Use TCP instead of Unix socket |
| SANDBOX_TCP_HOST | 0.0.0.0 / localhost | TCP host (server/client) |
| SANDBOX_TCP_PORT | 50051 | TCP port |
| SCRIPTS_CACHE_DIR | /tmp/prodisco-scripts | Cache directory |
| SANDBOX_ALLOWED_MODULES | (see above) | Allowed require() modules |
| SANDBOX_MODULES_BASE_PATH | process.cwd() | node_modules resolution base |
Transport Security
| Variable | Description |
|----------|-------------|
| SANDBOX_TRANSPORT_MODE | insecure, tls, or mtls |
| SANDBOX_TLS_CERT_PATH | Server certificate path |
| SANDBOX_TLS_KEY_PATH | Server private key path |
| SANDBOX_TLS_CA_PATH | CA certificate path |
| SANDBOX_TLS_CLIENT_CERT_PATH | Client cert (mTLS) |
| SANDBOX_TLS_CLIENT_KEY_PATH | Client key (mTLS) |
API Reference
SandboxClient
class SandboxClient {
constructor(options?: SandboxClientOptions);
// Synchronous execution
execute(options: ExecuteOptions): Promise<ExecuteResult>;
// Streaming execution
executeStream(options: ExecuteOptions): AsyncGenerator<StreamChunk>;
executeStreamWithAbort(options: ExecuteOptions, signal?: AbortSignal): AsyncGenerator<StreamChunk>;
// Async execution
executeAsync(options: ExecuteOptions): Promise<{ executionId: string; state: ExecutionState }>;
getExecution(id: string, options?: GetExecutionOptions): Promise<ExecutionStatus>;
waitForExecution(id: string): Promise<ExecutionStatus>;
cancelExecution(id: string): Promise<CancelResult>;
listExecutions(options?: ListOptions): Promise<ExecutionSummary[]>;
// Test execution
executeTest(options: ExecuteTestOptions): Promise<TestExecutionResult>;
// Cache management
listCache(filter?: string): Promise<CacheEntry[]>;
clearCache(): Promise<number>;
// Health check
healthCheck(): Promise<{ healthy: boolean; kubernetesContext: string }>;
waitForHealthy(timeoutMs: number): Promise<boolean>;
close(): void;
}Singleton Pattern
import { getSandboxClient, closeSandboxClient } from '@prodisco/sandbox-server/client';
// Get or create shared client instance
const client = getSandboxClient({ socketPath: '/tmp/sandbox.sock' });
// Close when done
closeSandboxClient();Docker & Kubernetes Deployment
Building the Container
docker build -f packages/sandbox-server/Dockerfile -t prodisco/sandbox-server:latest .Kubernetes Deployment
# Deploy to cluster
kubectl apply -f packages/sandbox-server/k8s/deployment.yaml
# Port forward for local access
kubectl -n prodisco port-forward service/sandbox-server 50051:50051Connecting to Containerized Server
// Via port-forward
const client = new SandboxClient({
useTcp: true,
tcpHost: 'localhost',
tcpPort: 50051,
});
// Via in-cluster DNS
const client = new SandboxClient({
useTcp: true,
tcpHost: 'sandbox-server.prodisco.svc.cluster.local',
tcpPort: 50051,
});Development
Building
npm run buildRunning Tests
# All tests
npm test
# Watch mode
npm run test:watch
# Security tests
npm run test:security
# E2E tests (requires kind cluster)
npm run test:e2e:setup
npm run test:e2e
npm run test:e2e:teardownRegenerating Protobuf Types
npm run proto:generateDevelopment Server
npm run devProtocol
The gRPC service is defined in proto/sandbox.proto. Key RPCs:
service SandboxService {
rpc Execute(ExecuteRequest) returns (ExecuteResponse);
rpc ExecuteStream(ExecuteRequest) returns (stream ExecuteChunk);
rpc ExecuteAsync(ExecuteRequest) returns (ExecuteAsyncResponse);
rpc GetExecution(GetExecutionRequest) returns (GetExecutionResponse);
rpc CancelExecution(CancelExecutionRequest) returns (CancelExecutionResponse);
rpc ListExecutions(ListExecutionsRequest) returns (ListExecutionsResponse);
rpc ExecuteTest(ExecuteTestRequest) returns (ExecuteTestResponse);
rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
rpc ListCache(ListCacheRequest) returns (ListCacheResponse);
rpc ClearCache(ClearCacheRequest) returns (ClearCacheResponse);
}Architecture
See docs/grpc-sandbox-architecture.md for detailed architecture documentation.
+---------------------------------------------------------------------+
| MCP Server |
| +-------------------+ +--------------------------------------+ |
| | searchTools | | runSandbox Tool | |
| | (API discovery) | | (thin gRPC client wrapper) | |
| +-------------------+ +------------------+-------------------+ |
| | |
+----------------------------------------------+-----------------------+
| gRPC over Unix Socket
v
+---------------------------------------------------------------------+
| Sandbox gRPC Server |
| +---------------------------------------------------------------+ |
| | SandboxService | |
| | (Execute, ExecuteStream, ExecuteAsync, ExecuteTest, ...) | |
| +---------------------------------------------------------------+ |
| | |
| +----------------+ +-------+--------+ +----------------------+ |
| | Executor | | Execution | | CacheManager | |
| | (VM + esbuild) | | Registry | | (dedup, persist) | |
| +----------------+ +----------------+ +----------------------+ |
+---------------------------------------------------------------------+License
MIT
