microsandbox 0.1.0 → 0.3.6

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 CHANGED
@@ -1,6 +1,40 @@
1
- # Microsandbox JavaScript SDK
1
+ # microsandbox
2
2
 
3
- A minimal JavaScript SDK for the Microsandbox project.
3
+ Lightweight VM sandboxes for Node.js run AI agents and untrusted code with hardware-level isolation.
4
+
5
+ The `microsandbox` npm package provides native bindings to the [MicroSandbox](https://github.com/superradcompany/microsandbox) runtime. It spins up real microVMs (not containers) in under 100ms, runs standard OCI (Docker) images, and gives you full control over execution, filesystem, networking, and secrets — all from a simple async API.
6
+
7
+ ## Features
8
+
9
+ - **Hardware isolation** — Each sandbox is a real VM with its own Linux kernel
10
+ - **Sub-100ms boot** — No daemon, no server setup, embedded directly in your app
11
+ - **OCI image support** — Pull and run images from Docker Hub, GHCR, ECR, or any OCI registry
12
+ - **Command execution** — Run commands with collected or streaming output, interactive shells
13
+ - **Guest filesystem access** — Read, write, list, copy files inside a running sandbox
14
+ - **Named volumes** — Persistent storage across sandbox restarts, with quotas
15
+ - **Network policies** — Public-only (default), allow-all, or fully airgapped
16
+ - **DNS filtering** — Block specific domains or domain suffixes
17
+ - **TLS interception** — Transparent HTTPS inspection and secret substitution
18
+ - **Secrets** — Credentials that never enter the VM; placeholder substitution at the network layer
19
+ - **Port publishing** — Expose guest TCP/UDP services on host ports
20
+ - **Rootfs patches** — Modify the filesystem before the VM boots
21
+ - **Detached mode** — Sandboxes can outlive the Node.js process
22
+ - **Metrics** — CPU, memory, disk I/O, and network I/O per sandbox
23
+
24
+ ## Requirements
25
+
26
+ - **Node.js** >= 18
27
+ - **Linux** with KVM enabled, or **macOS** with Apple Silicon (M-series)
28
+
29
+ ## Supported Platforms
30
+
31
+ | Platform | Architecture | Package |
32
+ |----------|-------------|---------|
33
+ | macOS | ARM64 (Apple Silicon) | `@superradcompany/microsandbox-darwin-arm64` |
34
+ | Linux | x86_64 | `@superradcompany/microsandbox-linux-x64-gnu` |
35
+ | Linux | ARM64 | `@superradcompany/microsandbox-linux-arm64-gnu` |
36
+
37
+ Platform-specific binaries are installed automatically via optional dependencies.
4
38
 
5
39
  ## Installation
6
40
 
@@ -8,26 +42,346 @@ A minimal JavaScript SDK for the Microsandbox project.
8
42
  npm install microsandbox
9
43
  ```
10
44
 
11
- ## Usage
45
+ The postinstall script automatically downloads the `msb` binary and `libkrunfw` library to `~/.microsandbox/`.
46
+
47
+ ## Quick Start
12
48
 
13
- ```javascript
14
- const { greet } = require("microsandbox");
49
+ ```typescript
50
+ import { Sandbox } from "microsandbox";
15
51
 
16
- // Print a greeting
17
- greet("World");
52
+ // Create a sandbox from an OCI image.
53
+ const sandbox = await Sandbox.create({
54
+ name: "my-sandbox",
55
+ image: "alpine:latest",
56
+ cpus: 1,
57
+ memoryMib: 512,
58
+ });
18
59
 
19
- // Using ES modules
20
- // import { greet } from 'microsandbox';
21
- // greet('World');
60
+ // Run a command.
61
+ const output = await sandbox.shell("echo 'Hello from microsandbox!'");
62
+ console.log(output.stdout());
63
+
64
+ // Stop the sandbox.
65
+ await sandbox.stopAndWait();
22
66
  ```
23
67
 
24
68
  ## Examples
25
69
 
26
- Check out the [examples directory](./examples) for sample scripts that demonstrate how to:
70
+ ### Command Execution
71
+
72
+ ```typescript
73
+ import { Sandbox } from "microsandbox";
74
+
75
+ const sandbox = await Sandbox.create({
76
+ name: "exec-demo",
77
+ image: "python:3.12",
78
+ replace: true,
79
+ });
80
+
81
+ // Collected output.
82
+ const result = await sandbox.exec("python3", ["-c", "print(1 + 1)"]);
83
+ console.log(result.stdout()); // "2\n"
84
+ console.log(result.code); // 0
85
+
86
+ // Shell command (pipes, redirects, etc.).
87
+ const output = await sandbox.shell("echo hello && pwd");
88
+ console.log(output.stdout());
89
+
90
+ // Full configuration.
91
+ const configured = await sandbox.execWithConfig({
92
+ cmd: "python3",
93
+ args: ["script.py"],
94
+ cwd: "/app",
95
+ env: { PYTHONPATH: "/app/lib" },
96
+ timeoutMs: 30000,
97
+ });
98
+
99
+ // Streaming output.
100
+ const handle = await sandbox.execStream("tail", ["-f", "/var/log/app.log"]);
101
+ let event;
102
+ while ((event = await handle.recv()) !== null) {
103
+ if (event.eventType === "stdout") process.stdout.write(event.data);
104
+ }
105
+
106
+ await sandbox.stopAndWait();
107
+ ```
108
+
109
+ ### Filesystem Operations
110
+
111
+ ```typescript
112
+ const fs = sandbox.fs();
113
+
114
+ // Write and read files.
115
+ await fs.write("/tmp/config.json", Buffer.from('{"debug": true}'));
116
+ const content = await fs.readString("/tmp/config.json");
117
+
118
+ // List a directory.
119
+ const entries = await fs.list("/etc");
120
+ for (const entry of entries) {
121
+ console.log(`${entry.path} (${entry.kind})`);
122
+ }
123
+
124
+ // Copy between host and guest.
125
+ await fs.copyFromHost("./local-file.txt", "/tmp/file.txt");
126
+ await fs.copyToHost("/tmp/output.txt", "./output.txt");
127
+
128
+ // Check existence and metadata.
129
+ if (await fs.exists("/tmp/config.json")) {
130
+ const meta = await fs.stat("/tmp/config.json");
131
+ console.log(`size: ${meta.size}, kind: ${meta.kind}`);
132
+ }
133
+ ```
134
+
135
+ ### Named Volumes
136
+
137
+ ```typescript
138
+ import { Sandbox, Volume, Mount } from "microsandbox";
139
+
140
+ // Create a 100 MiB named volume.
141
+ const data = await Volume.create({ name: "my-data", quotaMib: 100 });
142
+
143
+ // Mount it in a sandbox.
144
+ const writer = await Sandbox.create({
145
+ name: "writer",
146
+ image: "alpine:latest",
147
+ volumes: { "/data": Mount.named(data.name) },
148
+ replace: true,
149
+ });
150
+
151
+ await writer.shell("echo 'hello' > /data/message.txt");
152
+ await writer.stopAndWait();
153
+
154
+ // Mount the same volume in another sandbox (read-only).
155
+ const reader = await Sandbox.create({
156
+ name: "reader",
157
+ image: "alpine:latest",
158
+ volumes: { "/data": Mount.named(data.name, { readonly: true }) },
159
+ replace: true,
160
+ });
161
+
162
+ const output = await reader.shell("cat /data/message.txt");
163
+ console.log(output.stdout()); // "hello\n"
164
+
165
+ await reader.stopAndWait();
166
+
167
+ // Cleanup.
168
+ await Sandbox.remove("writer");
169
+ await Sandbox.remove("reader");
170
+ await Volume.remove("my-data");
171
+ ```
172
+
173
+ ### Network Policies
174
+
175
+ ```typescript
176
+ import { Sandbox, NetworkPolicy } from "microsandbox";
177
+
178
+ // Default: public internet only (blocks private ranges).
179
+ const publicOnly = await Sandbox.create({
180
+ name: "public",
181
+ image: "alpine:latest",
182
+ });
183
+
184
+ // Fully airgapped.
185
+ const isolated = await Sandbox.create({
186
+ name: "isolated",
187
+ image: "alpine:latest",
188
+ network: NetworkPolicy.none(),
189
+ });
190
+
191
+ // Unrestricted.
192
+ const open = await Sandbox.create({
193
+ name: "open",
194
+ image: "alpine:latest",
195
+ network: NetworkPolicy.allowAll(),
196
+ });
197
+
198
+ // DNS filtering.
199
+ const filtered = await Sandbox.create({
200
+ name: "filtered",
201
+ image: "alpine:latest",
202
+ network: {
203
+ blockDomains: ["blocked.example.com"],
204
+ blockDomainSuffixes: [".evil.com"],
205
+ },
206
+ });
207
+ ```
208
+
209
+ ### Port Publishing
210
+
211
+ ```typescript
212
+ const sandbox = await Sandbox.create({
213
+ name: "web",
214
+ image: "python:3.12",
215
+ ports: { "8080": 80 }, // host:8080 -> guest:80
216
+ });
217
+ ```
218
+
219
+ ### Secrets
220
+
221
+ Secrets use placeholder substitution — the real value never enters the VM. It is only swapped in at the network layer for HTTPS requests to allowed hosts.
222
+
223
+ ```typescript
224
+ import { Sandbox, Secret } from "microsandbox";
225
+
226
+ const sandbox = await Sandbox.create({
227
+ name: "agent",
228
+ image: "python:3.12",
229
+ secrets: [
230
+ Secret.env("OPENAI_API_KEY", {
231
+ value: process.env.OPENAI_API_KEY!,
232
+ allowHosts: ["api.openai.com"],
233
+ }),
234
+ ],
235
+ });
236
+
237
+ // Guest sees: OPENAI_API_KEY=$MSB_OPENAI_API_KEY (a placeholder)
238
+ // HTTPS to api.openai.com: placeholder is transparently replaced with the real key
239
+ // HTTPS to any other host with the placeholder: request is blocked
240
+ ```
241
+
242
+ ### Rootfs Patches
243
+
244
+ Modify the filesystem before the VM boots:
245
+
246
+ ```typescript
247
+ import { Patch, Sandbox } from "microsandbox";
248
+
249
+ const sandbox = await Sandbox.create({
250
+ name: "patched",
251
+ image: "alpine:latest",
252
+ patches: [
253
+ Patch.text("/etc/greeting.txt", "Hello!\n"),
254
+ Patch.mkdir("/app", { mode: 0o755 }),
255
+ Patch.text("/app/config.json", '{"debug": true}', { mode: 0o644 }),
256
+ Patch.copyDir("./scripts", "/app/scripts"),
257
+ Patch.append("/etc/hosts", "127.0.0.1 myapp.local\n"),
258
+ ],
259
+ });
260
+ ```
261
+
262
+ ### Detached Mode
263
+
264
+ Sandboxes in detached mode survive the Node.js process:
265
+
266
+ ```typescript
267
+ // Create and detach.
268
+ const sandbox = await Sandbox.createDetached({
269
+ name: "background",
270
+ image: "python:3.12",
271
+ });
272
+ await sandbox.detach();
273
+
274
+ // Later, from another process:
275
+ const handle = await Sandbox.get("background");
276
+ const reconnected = await handle.connect();
277
+ const output = await reconnected.shell("echo reconnected");
278
+ ```
279
+
280
+ ### TLS Interception
281
+
282
+ ```typescript
283
+ const sandbox = await Sandbox.create({
284
+ name: "tls-inspect",
285
+ image: "python:3.12",
286
+ network: {
287
+ tls: {
288
+ bypass: ["*.googleapis.com"],
289
+ verifyUpstream: true,
290
+ interceptedPorts: [443],
291
+ },
292
+ },
293
+ });
294
+ ```
295
+
296
+ ### Metrics
297
+
298
+ ```typescript
299
+ import { Sandbox, allSandboxMetrics } from "microsandbox";
300
+
301
+ const sandbox = await Sandbox.create({
302
+ name: "metrics-demo",
303
+ image: "python:3.12",
304
+ });
305
+
306
+ // Per-sandbox metrics.
307
+ const m = await sandbox.metrics();
308
+ console.log(`CPU: ${m.cpuPercent.toFixed(1)}%`);
309
+ console.log(`Memory: ${(m.memoryBytes / 1024 / 1024).toFixed(1)} MiB`);
310
+ console.log(`Uptime: ${(m.uptimeMs / 1000).toFixed(1)}s`);
311
+
312
+ // All sandboxes at once.
313
+ const all = await allSandboxMetrics();
314
+ for (const [name, metrics] of Object.entries(all)) {
315
+ console.log(`${name}: ${metrics.cpuPercent.toFixed(1)}%`);
316
+ }
317
+ ```
318
+
319
+ ### Runtime Setup
320
+
321
+ ```typescript
322
+ import { isInstalled, install } from "microsandbox";
323
+
324
+ if (!isInstalled()) {
325
+ await install(); // Downloads msb + libkrunfw to ~/.microsandbox/
326
+ }
327
+ ```
328
+
329
+ ## API Reference
330
+
331
+ ### Classes
332
+
333
+ | Class | Description |
334
+ |-------|-------------|
335
+ | `Sandbox` | Live handle to a running sandbox — lifecycle, execution, filesystem |
336
+ | `SandboxHandle` | Lightweight database handle — use `connect()` or `start()` to get a live `Sandbox` |
337
+ | `ExecOutput` | Captured stdout/stderr with exit status |
338
+ | `ExecHandle` | Streaming execution handle — call `recv()` for events |
339
+ | `ExecSink` | Writable stdin channel for streaming exec |
340
+ | `SandboxFs` | Guest filesystem operations (read, write, list, copy, stat) |
341
+ | `Volume` | Persistent named volume |
342
+ | `VolumeHandle` | Lightweight volume handle from the database |
343
+
344
+ ### Factories
345
+
346
+ | Class | Description |
347
+ |-------|-------------|
348
+ | `Mount` | Volume mount configuration — `Mount.bind()`, `Mount.named()`, `Mount.tmpfs()` |
349
+ | `NetworkPolicy` | Network presets — `NetworkPolicy.none()`, `NetworkPolicy.publicOnly()`, `NetworkPolicy.allowAll()` |
350
+ | `Secret` | Secret entry — `Secret.env(name, options)` |
351
+
352
+ ### Interfaces
353
+
354
+ | Interface | Description |
355
+ |-----------|-------------|
356
+ | `SandboxConfig` | Sandbox creation configuration |
357
+ | `ExecConfig` | Full command execution options (cmd, args, cwd, env, timeout, tty) |
358
+ | `NetworkConfig` | Network policy with rules, DNS blocking, TLS interception |
359
+ | `MountConfig` | Volume mount (bind, named, or tmpfs) |
360
+ | `PatchConfig` | Pre-boot filesystem modification |
361
+ | `VolumeConfig` | Volume creation options (name, quota, labels) |
362
+ | `SecretEntry` / `SecretEnvOptions` | Secret binding to env var with host allowlist |
363
+ | `ExecEvent` | Stream event: `"started"`, `"stdout"`, `"stderr"`, `"exited"` |
364
+ | `ExitStatus` | Exit code and success flag |
365
+ | `FsEntry` / `FsMetadata` | Filesystem entry info and metadata |
366
+ | `SandboxInfo` | Sandbox listing info (name, status, timestamps) |
367
+ | `SandboxMetrics` | Resource metrics (CPU, memory, disk I/O, network I/O, uptime) |
368
+ | `TlsConfig` | TLS interception options (bypass domains, upstream verification) |
369
+
370
+ ### Functions
371
+
372
+ | Function | Description |
373
+ |----------|-------------|
374
+ | `isInstalled()` | Check if `msb` and `libkrunfw` are available |
375
+ | `install()` | Download and install runtime dependencies |
376
+ | `allSandboxMetrics()` | Get metrics for all running sandboxes |
377
+
378
+ ### Enums
27
379
 
28
- - Use the SDK in both JavaScript and TypeScript
29
- - See simple usage patterns
380
+ | Enum | Values |
381
+ |------|--------|
382
+ | `LogLevel` | `"trace"`, `"debug"`, `"info"`, `"warn"`, `"error"` |
383
+ | `PullPolicy` | `"always"`, `"if-missing"`, `"never"` |
30
384
 
31
385
  ## License
32
386
 
33
- [MIT](LICENSE)
387
+ Apache-2.0