@cloudflare/sandbox 0.0.0-a04f6b6 → 0.0.0-aa00a75
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/CHANGELOG.md +161 -0
- package/Dockerfile +120 -27
- package/README.md +164 -0
- package/dist/chunk-BCJ7SF3Q.js +117 -0
- package/dist/chunk-BCJ7SF3Q.js.map +1 -0
- package/dist/chunk-BFVUNTP4.js +104 -0
- package/dist/chunk-BFVUNTP4.js.map +1 -0
- package/dist/chunk-EKSWCBCA.js +86 -0
- package/dist/chunk-EKSWCBCA.js.map +1 -0
- package/dist/chunk-HGF554LH.js +2236 -0
- package/dist/chunk-HGF554LH.js.map +1 -0
- package/dist/chunk-Z532A7QC.js +78 -0
- package/dist/chunk-Z532A7QC.js.map +1 -0
- package/dist/file-stream.d.ts +43 -0
- package/dist/file-stream.js +9 -0
- package/dist/file-stream.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/interpreter.d.ts +33 -0
- package/dist/interpreter.js +8 -0
- package/dist/interpreter.js.map +1 -0
- package/dist/request-handler.d.ts +18 -0
- package/dist/request-handler.js +12 -0
- package/dist/request-handler.js.map +1 -0
- package/dist/sandbox-D9K2ypln.d.ts +583 -0
- package/dist/sandbox.d.ts +4 -0
- package/dist/sandbox.js +12 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/security.d.ts +31 -0
- package/dist/security.js +13 -0
- package/dist/security.js.map +1 -0
- package/dist/sse-parser.d.ts +28 -0
- package/dist/sse-parser.js +11 -0
- package/dist/sse-parser.js.map +1 -0
- package/package.json +11 -4
- package/src/clients/base-client.ts +280 -0
- package/src/clients/command-client.ts +115 -0
- package/src/clients/file-client.ts +269 -0
- package/src/clients/git-client.ts +92 -0
- package/src/clients/index.ts +63 -0
- package/src/clients/interpreter-client.ts +329 -0
- package/src/clients/port-client.ts +105 -0
- package/src/clients/process-client.ts +177 -0
- package/src/clients/sandbox-client.ts +41 -0
- package/src/clients/types.ts +84 -0
- package/src/clients/utility-client.ts +94 -0
- package/src/errors/adapter.ts +180 -0
- package/src/errors/classes.ts +469 -0
- package/src/errors/index.ts +105 -0
- package/src/file-stream.ts +164 -0
- package/src/index.ts +85 -12
- package/src/interpreter.ts +159 -0
- package/src/request-handler.ts +69 -43
- package/src/sandbox.ts +560 -288
- package/src/security.ts +14 -23
- package/src/sse-parser.ts +4 -8
- package/startup.sh +3 -0
- package/tests/base-client.test.ts +328 -0
- package/tests/command-client.test.ts +407 -0
- package/tests/file-client.test.ts +643 -0
- package/tests/file-stream.test.ts +306 -0
- package/tests/git-client.test.ts +328 -0
- package/tests/port-client.test.ts +301 -0
- package/tests/process-client.test.ts +658 -0
- package/tests/sandbox.test.ts +465 -0
- package/tests/sse-parser.test.ts +290 -0
- package/tests/utility-client.test.ts +266 -0
- package/tests/wrangler.jsonc +35 -0
- package/tsconfig.json +9 -1
- package/vitest.config.ts +31 -0
- package/container_src/handler/exec.ts +0 -337
- package/container_src/handler/file.ts +0 -844
- package/container_src/handler/git.ts +0 -182
- package/container_src/handler/ports.ts +0 -314
- package/container_src/handler/process.ts +0 -640
- package/container_src/index.ts +0 -361
- package/container_src/package.json +0 -9
- package/container_src/types.ts +0 -103
- package/src/client.ts +0 -1038
- package/src/types.ts +0 -386
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,166 @@
|
|
|
1
1
|
# @cloudflare/sandbox
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#95](https://github.com/cloudflare/sandbox-sdk/pull/95) [`7aee736`](https://github.com/cloudflare/sandbox-sdk/commit/7aee736bf07a4bf9020e2109bdaaa70214d52a01) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Rewrite SDK with cleaner design patterns and tests. Remove the unnecessary isolation cruft and fix foundational issues with streaming, sessions, validations and error handling. Cover the SDK with unit & e2e tests.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#106](https://github.com/cloudflare/sandbox-sdk/pull/106) [`da947cd`](https://github.com/cloudflare/sandbox-sdk/commit/da947cd9543fc99831eefb1e8741fc905cb8fa42) Thanks [@jahands](https://github.com/jahands)! - fix examples failing to deploy and prevent committing node_modules
|
|
12
|
+
|
|
13
|
+
## 0.3.3
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- [#83](https://github.com/cloudflare/sandbox-sdk/pull/83) [`eec5bb6`](https://github.com/cloudflare/sandbox-sdk/commit/eec5bb6203dd5d775b4b54e91c26de25eeb767ce) Thanks [@mikenomitch](https://github.com/mikenomitch)! - Bump containers package version
|
|
18
|
+
|
|
19
|
+
## 0.3.2
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [#76](https://github.com/cloudflare/sandbox-sdk/pull/76) [`ef9e320`](https://github.com/cloudflare/sandbox-sdk/commit/ef9e320dcef30e57797fef6ebd9a9383fa9720d9) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Replace Jupyter with lightweight interpreters for >90% faster cold starts for `.runCode` calls, while maintaining full code execution capabilities and rich output support.
|
|
24
|
+
|
|
25
|
+
## 0.3.1
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- [#71](https://github.com/cloudflare/sandbox-sdk/pull/71) [`fb3c9c2`](https://github.com/cloudflare/sandbox-sdk/commit/fb3c9c22242d9d4f157c26f547f1e697ef7875f9) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Bump containers package version
|
|
30
|
+
|
|
31
|
+
- [#70](https://github.com/cloudflare/sandbox-sdk/pull/70) [`e1fa354`](https://github.com/cloudflare/sandbox-sdk/commit/e1fa354ab1bc7b0e89db4901b67028ebf1a93d0a) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Fix escaped quotes in file write operations
|
|
32
|
+
|
|
33
|
+
- [#68](https://github.com/cloudflare/sandbox-sdk/pull/68) [`69b91d1`](https://github.com/cloudflare/sandbox-sdk/commit/69b91d1a8f6afb63262cc381ea93e94a033ed5e8) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Configurable timeouts via environment variables in isolation.ts
|
|
34
|
+
|
|
35
|
+
- [#66](https://github.com/cloudflare/sandbox-sdk/pull/66) [`eca93b9`](https://github.com/cloudflare/sandbox-sdk/commit/eca93b97e40fa0d3bd9dc27af2cc214ec355b696) Thanks [@peterp](https://github.com/peterp)! - Determine if the port is specified in the URL.
|
|
36
|
+
|
|
37
|
+
## 0.3.0
|
|
38
|
+
|
|
39
|
+
### Minor Changes
|
|
40
|
+
|
|
41
|
+
- [#59](https://github.com/cloudflare/sandbox-sdk/pull/59) [`b6757f7`](https://github.com/cloudflare/sandbox-sdk/commit/b6757f730c34381d5a70d513944bbf9840f598ab) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Add process isolation for sandbox commands
|
|
42
|
+
|
|
43
|
+
Implements PID namespace isolation to protect control plane processes (Jupyter, Bun) from sandboxed code. Commands executed via `exec()` now run in isolated namespaces that cannot see or interact with system processes.
|
|
44
|
+
|
|
45
|
+
**Key security improvements:**
|
|
46
|
+
|
|
47
|
+
- Control plane processes are hidden from sandboxed commands
|
|
48
|
+
- Platform secrets in `/proc/1/environ` are inaccessible
|
|
49
|
+
- Ports 8888 (Jupyter) and 3000 (Bun) are protected from hijacking
|
|
50
|
+
|
|
51
|
+
**Breaking changes:**
|
|
52
|
+
|
|
53
|
+
1. **Removed `sessionId` parameter**: The `sessionId` parameter has been removed from all methods (`exec()`, `execStream()`, `startProcess()`, etc.). Each sandbox now maintains its own persistent session automatically.
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
// Before: manual session management
|
|
57
|
+
await sandbox.exec("cd /app", { sessionId: "my-session" });
|
|
58
|
+
|
|
59
|
+
// After: automatic session per sandbox
|
|
60
|
+
await sandbox.exec("cd /app");
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
2. **Commands now maintain state**: Commands within the same sandbox now share state (working directory, environment variables, background processes). Previously each command was stateless.
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
// Before: each exec was independent
|
|
67
|
+
await sandbox.exec("cd /app");
|
|
68
|
+
await sandbox.exec("pwd"); // Output: /workspace
|
|
69
|
+
|
|
70
|
+
// After: state persists in session
|
|
71
|
+
await sandbox.exec("cd /app");
|
|
72
|
+
await sandbox.exec("pwd"); // Output: /app
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Migration guide:**
|
|
76
|
+
|
|
77
|
+
- Remove `sessionId` from all method calls - each sandbox maintains its own session
|
|
78
|
+
- If you need isolated execution contexts within the same sandbox, use `sandbox.createSession()`:
|
|
79
|
+
```javascript
|
|
80
|
+
// Create independent sessions with different environments
|
|
81
|
+
const buildSession = await sandbox.createSession({
|
|
82
|
+
name: "build",
|
|
83
|
+
env: { NODE_ENV: "production" },
|
|
84
|
+
cwd: "/build",
|
|
85
|
+
});
|
|
86
|
+
const testSession = await sandbox.createSession({
|
|
87
|
+
name: "test",
|
|
88
|
+
env: { NODE_ENV: "test" },
|
|
89
|
+
cwd: "/test",
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
- Environment variables set in one command persist to the next
|
|
93
|
+
- Background processes remain active until explicitly killed
|
|
94
|
+
- Requires CAP_SYS_ADMIN (available in production, falls back gracefully in dev)
|
|
95
|
+
|
|
96
|
+
### Patch Changes
|
|
97
|
+
|
|
98
|
+
- [#62](https://github.com/cloudflare/sandbox-sdk/pull/62) [`4bedc3a`](https://github.com/cloudflare/sandbox-sdk/commit/4bedc3aba347f3d4090a6efe2c9778bac00ce74a) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Fix broken build due to bun lockfile not being used
|
|
99
|
+
|
|
100
|
+
## 0.2.4
|
|
101
|
+
|
|
102
|
+
### Patch Changes
|
|
103
|
+
|
|
104
|
+
- [#57](https://github.com/cloudflare/sandbox-sdk/pull/57) [`12bbd12`](https://github.com/cloudflare/sandbox-sdk/commit/12bbd1229c07ef8c1c0bf58a4235a27938155b08) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Add listFiles method
|
|
105
|
+
|
|
106
|
+
## 0.2.3
|
|
107
|
+
|
|
108
|
+
### Patch Changes
|
|
109
|
+
|
|
110
|
+
- [#53](https://github.com/cloudflare/sandbox-sdk/pull/53) [`c87db11`](https://github.com/cloudflare/sandbox-sdk/commit/c87db117693a86cfb667bf09fb7720d6a6e0524d) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Improve jupyterlab config to speed up startup
|
|
111
|
+
|
|
112
|
+
## 0.2.2
|
|
113
|
+
|
|
114
|
+
### Patch Changes
|
|
115
|
+
|
|
116
|
+
- [#51](https://github.com/cloudflare/sandbox-sdk/pull/51) [`4aceb32`](https://github.com/cloudflare/sandbox-sdk/commit/4aceb3215c836f59afcb88b2b325016b3f623f46) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Handle intermittent interpreter failures and decouple jupyter startup
|
|
117
|
+
|
|
118
|
+
## 0.2.1
|
|
119
|
+
|
|
120
|
+
### Patch Changes
|
|
121
|
+
|
|
122
|
+
- [#49](https://github.com/cloudflare/sandbox-sdk/pull/49) [`d81d2a5`](https://github.com/cloudflare/sandbox-sdk/commit/d81d2a563c9af8947d5444019ed4d6156db563e3) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Implement code interpreter API
|
|
123
|
+
|
|
124
|
+
## 0.2.0
|
|
125
|
+
|
|
126
|
+
### Minor Changes
|
|
127
|
+
|
|
128
|
+
- [#47](https://github.com/cloudflare/sandbox-sdk/pull/47) [`8a93d0c`](https://github.com/cloudflare/sandbox-sdk/commit/8a93d0cae18a25bda6506b8b0a08d9e9eb3bb290) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Change default directory to a clean /workspace
|
|
129
|
+
|
|
130
|
+
## 0.1.4
|
|
131
|
+
|
|
132
|
+
### Patch Changes
|
|
133
|
+
|
|
134
|
+
- [#46](https://github.com/cloudflare/sandbox-sdk/pull/46) [`7de28be`](https://github.com/cloudflare/sandbox-sdk/commit/7de28be482d9634551572d548c7c4b5842df812d) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Update README
|
|
135
|
+
|
|
136
|
+
- [#44](https://github.com/cloudflare/sandbox-sdk/pull/44) [`215ab49`](https://github.com/cloudflare/sandbox-sdk/commit/215ab494427d7e2a92bb9a25384cb493a221c200) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Update example to use env & cwd
|
|
137
|
+
|
|
138
|
+
- [#42](https://github.com/cloudflare/sandbox-sdk/pull/42) [`bb72193`](https://github.com/cloudflare/sandbox-sdk/commit/bb72193ad75695979bd1132206f481e91fe37325) Thanks [@jonasnobile](https://github.com/jonasnobile)! - Propagate `cwd` and `env` options in `executeCommand`
|
|
139
|
+
|
|
140
|
+
- [#27](https://github.com/cloudflare/sandbox-sdk/pull/27) [`fd5ec7f`](https://github.com/cloudflare/sandbox-sdk/commit/fd5ec7f34bc12b06320a89356c4af07801f52d64) Thanks [@threepointone](https://github.com/threepointone)! - remove yarn and pnpm from the image
|
|
141
|
+
|
|
142
|
+
## 0.1.3
|
|
143
|
+
|
|
144
|
+
### Patch Changes
|
|
145
|
+
|
|
146
|
+
- [#32](https://github.com/cloudflare/sandbox-sdk/pull/32) [`1a42464`](https://github.com/cloudflare/sandbox-sdk/commit/1a4246479369c5d0160705caf192aa1816540d52) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Bring back package README
|
|
147
|
+
|
|
148
|
+
## 0.1.2
|
|
149
|
+
|
|
150
|
+
### Patch Changes
|
|
151
|
+
|
|
152
|
+
- [#30](https://github.com/cloudflare/sandbox-sdk/pull/30) [`30e5c25`](https://github.com/cloudflare/sandbox-sdk/commit/30e5c25cf7d4b07f9049724206c531e2d5d29d5c) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Remove actions timeout
|
|
153
|
+
|
|
154
|
+
- [#29](https://github.com/cloudflare/sandbox-sdk/pull/29) [`d78508f`](https://github.com/cloudflare/sandbox-sdk/commit/d78508f7287a59e0423edd2999c2c83e9e34ccfd) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Create multi-platform Docker image and switch to Cloudflare official repo
|
|
155
|
+
|
|
156
|
+
## 0.1.1
|
|
157
|
+
|
|
158
|
+
### Patch Changes
|
|
159
|
+
|
|
160
|
+
- [`157dde9`](https://github.com/cloudflare/sandbox-sdk/commit/157dde9b1f23e9bb6f3e9c3f0514b639a8813897) Thanks [@threepointone](https://github.com/threepointone)! - update deps
|
|
161
|
+
|
|
162
|
+
- [`a04f6b6`](https://github.com/cloudflare/sandbox-sdk/commit/a04f6b6c0b2ef9e3ce0851b53769f1c10d8c6de6) Thanks [@threepointone](https://github.com/threepointone)! - trigger a build with updated deps
|
|
163
|
+
|
|
3
164
|
## 0.1.0
|
|
4
165
|
|
|
5
166
|
### Minor Changes
|
package/Dockerfile
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
|
-
# Sandbox
|
|
2
|
-
|
|
1
|
+
# Sandbox container image with full development environment
|
|
2
|
+
# Multi-stage build optimized for Turborepo monorepo
|
|
3
|
+
|
|
4
|
+
# ============================================================================
|
|
5
|
+
# Stage 1: Prune monorepo to only include necessary packages
|
|
6
|
+
# ============================================================================
|
|
7
|
+
FROM node:20-alpine AS pruner
|
|
8
|
+
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
|
|
11
|
+
# Install Turborepo globally
|
|
12
|
+
RUN npm install -g turbo
|
|
13
|
+
|
|
14
|
+
# Copy entire monorepo
|
|
15
|
+
COPY . .
|
|
16
|
+
|
|
17
|
+
# Prune to only @repo/sandbox-container and its dependencies (@repo/shared)
|
|
18
|
+
# The --docker flag generates out/json and out/full directories
|
|
19
|
+
RUN turbo prune @repo/sandbox-container --docker
|
|
20
|
+
|
|
21
|
+
# ============================================================================
|
|
22
|
+
# Stage 2: Install dependencies and build packages
|
|
23
|
+
# ============================================================================
|
|
24
|
+
FROM node:20-alpine AS builder
|
|
25
|
+
|
|
26
|
+
WORKDIR /app
|
|
27
|
+
|
|
28
|
+
# Copy pruned lockfile and package.json files (for Docker layer caching)
|
|
29
|
+
COPY --from=pruner /app/out/json/ .
|
|
30
|
+
COPY --from=pruner /app/out/package-lock.json ./package-lock.json
|
|
31
|
+
|
|
32
|
+
# Install ALL dependencies (including devDependencies for build)
|
|
33
|
+
RUN npm ci
|
|
34
|
+
|
|
35
|
+
# Copy pruned source code
|
|
36
|
+
COPY --from=pruner /app/out/full/ .
|
|
37
|
+
|
|
38
|
+
# Build all packages (Turborepo handles dependency order automatically)
|
|
39
|
+
# This builds @repo/shared first, then @repo/sandbox-container
|
|
40
|
+
RUN npx turbo run build
|
|
41
|
+
|
|
42
|
+
# ============================================================================
|
|
43
|
+
# Stage 3: Install production-only dependencies
|
|
44
|
+
# ============================================================================
|
|
45
|
+
FROM node:20-alpine AS prod-deps
|
|
46
|
+
|
|
47
|
+
WORKDIR /app
|
|
48
|
+
|
|
49
|
+
# Copy package files from builder
|
|
50
|
+
COPY --from=builder /app/package.json ./package.json
|
|
51
|
+
COPY --from=builder /app/package-lock.json ./package-lock.json
|
|
52
|
+
COPY --from=builder /app/packages ./packages
|
|
53
|
+
COPY --from=builder /app/tooling ./tooling
|
|
54
|
+
|
|
55
|
+
# Install ONLY production dependencies (excludes typescript, @types/*, etc.)
|
|
56
|
+
RUN npm ci --production
|
|
57
|
+
|
|
58
|
+
# ============================================================================
|
|
59
|
+
# Stage 4: Runtime - Ubuntu 22.04 with full development environment
|
|
60
|
+
# ============================================================================
|
|
61
|
+
FROM ubuntu:22.04 AS runtime
|
|
3
62
|
|
|
4
63
|
# Prevent interactive prompts during package installation
|
|
5
64
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
@@ -12,6 +71,7 @@ RUN apt-get update && apt-get install -y \
|
|
|
12
71
|
git \
|
|
13
72
|
unzip \
|
|
14
73
|
zip \
|
|
74
|
+
file \
|
|
15
75
|
# Process management
|
|
16
76
|
procps \
|
|
17
77
|
htop \
|
|
@@ -30,51 +90,84 @@ RUN apt-get update && apt-get install -y \
|
|
|
30
90
|
python3.11 \
|
|
31
91
|
python3.11-dev \
|
|
32
92
|
python3-pip \
|
|
93
|
+
python3.11-venv \
|
|
33
94
|
# Other useful tools
|
|
34
|
-
sudo \
|
|
35
95
|
ca-certificates \
|
|
36
96
|
gnupg \
|
|
37
97
|
lsb-release \
|
|
98
|
+
strace \
|
|
38
99
|
&& rm -rf /var/lib/apt/lists/*
|
|
39
100
|
|
|
40
101
|
# Set Python 3.11 as default python3
|
|
41
102
|
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1
|
|
42
103
|
|
|
43
|
-
# Install Node.js
|
|
44
|
-
|
|
45
|
-
RUN apt-get update && apt-get install -y ca-certificates curl gnupg \
|
|
46
|
-
&& mkdir -p /etc/apt/keyrings \
|
|
47
|
-
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
|
|
48
|
-
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
|
|
49
|
-
&& apt-get update \
|
|
104
|
+
# Install Node.js 20 LTS using official NodeSource setup script
|
|
105
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
|
50
106
|
&& apt-get install -y nodejs \
|
|
51
107
|
&& rm -rf /var/lib/apt/lists/*
|
|
52
108
|
|
|
53
|
-
# Install Bun
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
&& mv /root/.bun/bin/bunx /usr/local/bin/bunx \
|
|
57
|
-
&& rm -rf /root/.bun
|
|
109
|
+
# Install Bun runtime from official image
|
|
110
|
+
COPY --from=oven/bun:1 /usr/local/bin/bun /usr/local/bin/bun
|
|
111
|
+
COPY --from=oven/bun:1 /usr/local/bin/bunx /usr/local/bin/bunx
|
|
58
112
|
|
|
59
|
-
# Install
|
|
60
|
-
RUN npm install -g
|
|
113
|
+
# Install development tools globally
|
|
114
|
+
RUN npm install -g \
|
|
115
|
+
wrangler \
|
|
116
|
+
vite \
|
|
117
|
+
opencode-ai
|
|
61
118
|
|
|
62
|
-
#
|
|
63
|
-
|
|
119
|
+
# Install essential Python packages for code execution
|
|
120
|
+
RUN pip3 install --no-cache-dir \
|
|
121
|
+
matplotlib \
|
|
122
|
+
numpy \
|
|
123
|
+
pandas \
|
|
124
|
+
ipython
|
|
64
125
|
|
|
65
126
|
# Verify installations
|
|
66
127
|
RUN python3 --version && \
|
|
67
128
|
node --version && \
|
|
68
129
|
npm --version && \
|
|
69
|
-
bun --version
|
|
70
|
-
|
|
71
|
-
|
|
130
|
+
bun --version
|
|
131
|
+
|
|
132
|
+
# Set up runtime container server directory
|
|
133
|
+
WORKDIR /container-server
|
|
72
134
|
|
|
73
|
-
# Copy container
|
|
74
|
-
COPY
|
|
135
|
+
# Copy built sandbox-container package
|
|
136
|
+
COPY --from=builder /app/packages/sandbox-container/dist ./dist
|
|
137
|
+
COPY --from=builder /app/packages/sandbox-container/package.json ./package.json
|
|
75
138
|
|
|
76
|
-
#
|
|
139
|
+
# Copy Python executor to runtime location
|
|
140
|
+
COPY --from=builder /app/packages/sandbox-container/src/runtime/executors/python/ipython_executor.py ./dist/runtime/executors/python/
|
|
141
|
+
|
|
142
|
+
# Copy production-only node_modules (excludes typescript, @types/*, etc.)
|
|
143
|
+
# Includes: @repo/shared, zod, esbuild (runtime dependencies)
|
|
144
|
+
COPY --from=prod-deps /app/node_modules ./node_modules
|
|
145
|
+
|
|
146
|
+
# Copy workspace packages so node_modules symlinks work
|
|
147
|
+
# @repo/shared and @repo/typescript-config are symlinked in node_modules
|
|
148
|
+
COPY --from=prod-deps /app/packages/shared ./packages/shared
|
|
149
|
+
COPY --from=prod-deps /app/tooling/typescript-config ./tooling/typescript-config
|
|
150
|
+
|
|
151
|
+
# Configure process pool sizes (can be overridden at runtime)
|
|
152
|
+
ENV PYTHON_POOL_MIN_SIZE=3
|
|
153
|
+
ENV PYTHON_POOL_MAX_SIZE=15
|
|
154
|
+
ENV JAVASCRIPT_POOL_MIN_SIZE=3
|
|
155
|
+
ENV JAVASCRIPT_POOL_MAX_SIZE=10
|
|
156
|
+
ENV TYPESCRIPT_POOL_MIN_SIZE=3
|
|
157
|
+
ENV TYPESCRIPT_POOL_MAX_SIZE=10
|
|
158
|
+
|
|
159
|
+
# Create clean workspace directory for user code
|
|
160
|
+
# Architecture:
|
|
161
|
+
# /container-server/ - SDK infrastructure (server, executors, dependencies)
|
|
162
|
+
# /workspace/ - User's clean workspace for their code
|
|
163
|
+
RUN mkdir -p /workspace
|
|
164
|
+
|
|
165
|
+
# Expose the application port (3000 for control)
|
|
77
166
|
EXPOSE 3000
|
|
78
167
|
|
|
79
|
-
#
|
|
80
|
-
|
|
168
|
+
# Copy and make startup script executable
|
|
169
|
+
COPY packages/sandbox/startup.sh ./
|
|
170
|
+
RUN chmod +x startup.sh
|
|
171
|
+
|
|
172
|
+
# Use startup script
|
|
173
|
+
CMD ["./startup.sh"]
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<img width="1362" height="450" alt="Image" src="https://github.com/user-attachments/assets/6f770ae3-0a14-4d2b-9aed-a304ee5446c5" />
|
|
2
|
+
|
|
3
|
+
# Cloudflare Sandbox SDK
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@cloudflare/sandbox)
|
|
6
|
+
|
|
7
|
+
**Build secure, isolated code execution environments on Cloudflare.**
|
|
8
|
+
|
|
9
|
+
The Sandbox SDK lets you run untrusted code safely in isolated containers. Execute commands, manage files, run background processes, and expose services — all from your Workers applications.
|
|
10
|
+
|
|
11
|
+
Perfect for AI code execution, interactive development environments, data analysis platforms, CI/CD systems, and any application that needs secure code execution at the edge.
|
|
12
|
+
|
|
13
|
+
> **Note:** The latest published version of the SDK is on the [v03 branch](https://github.com/cloudflare/sandbox-sdk/tree/v03). This main branch is currently the development version and is not yet published.
|
|
14
|
+
|
|
15
|
+
## Getting Started
|
|
16
|
+
|
|
17
|
+
### Prerequisites
|
|
18
|
+
|
|
19
|
+
1. Install [Node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) (version 16.17.0 or later)
|
|
20
|
+
2. Ensure Docker is running locally
|
|
21
|
+
3. For deploying to production, sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages)
|
|
22
|
+
|
|
23
|
+
### 1. Create a new project
|
|
24
|
+
|
|
25
|
+
Create a new Sandbox SDK project using the minimal template:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm create cloudflare@latest -- my-sandbox --template=cloudflare/sandbox-sdk/examples/minimal
|
|
29
|
+
cd my-sandbox
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. Test locally
|
|
33
|
+
|
|
34
|
+
Start the development server:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run dev
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> **Note:** First run builds the Docker container (2-3 minutes). Subsequent runs are much faster.
|
|
41
|
+
|
|
42
|
+
Test the endpoints:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Execute Python code
|
|
46
|
+
curl http://localhost:8787/run
|
|
47
|
+
|
|
48
|
+
# File operations
|
|
49
|
+
curl http://localhost:8787/file
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Deploy to production
|
|
53
|
+
|
|
54
|
+
Deploy your Worker and container:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx wrangler deploy
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> **Wait for provisioning:** After first deployment, wait 2-3 minutes before making requests.
|
|
61
|
+
|
|
62
|
+
**📖 [View the complete getting started guide](https://developers.cloudflare.com/sandbox/get-started/)** for detailed instructions and explanations.
|
|
63
|
+
|
|
64
|
+
## Quick API Example
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { getSandbox, proxyToSandbox, type Sandbox } from '@cloudflare/sandbox';
|
|
68
|
+
|
|
69
|
+
export { Sandbox } from '@cloudflare/sandbox';
|
|
70
|
+
|
|
71
|
+
type Env = {
|
|
72
|
+
Sandbox: DurableObjectNamespace<Sandbox>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default {
|
|
76
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
77
|
+
// Required for preview URLs
|
|
78
|
+
const proxyResponse = await proxyToSandbox(request, env);
|
|
79
|
+
if (proxyResponse) return proxyResponse;
|
|
80
|
+
|
|
81
|
+
const url = new URL(request.url);
|
|
82
|
+
const sandbox = getSandbox(env.Sandbox, 'my-sandbox');
|
|
83
|
+
|
|
84
|
+
// Execute Python code
|
|
85
|
+
if (url.pathname === '/run') {
|
|
86
|
+
const result = await sandbox.exec('python3 -c "print(2 + 2)"');
|
|
87
|
+
return Response.json({ output: result.stdout, success: result.success });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Work with files
|
|
91
|
+
if (url.pathname === '/file') {
|
|
92
|
+
await sandbox.writeFile('/workspace/hello.txt', 'Hello, Sandbox!');
|
|
93
|
+
const file = await sandbox.readFile('/workspace/hello.txt');
|
|
94
|
+
return Response.json({ content: file.content });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return new Response('Try /run or /file');
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Documentation
|
|
103
|
+
|
|
104
|
+
**📖 [Full Documentation](https://developers.cloudflare.com/sandbox/)**
|
|
105
|
+
|
|
106
|
+
- [Get Started Guide](https://developers.cloudflare.com/sandbox/get-started/) - Step-by-step tutorial
|
|
107
|
+
- [API Reference](https://developers.cloudflare.com/sandbox/api/) - Complete API docs
|
|
108
|
+
- [Guides](https://developers.cloudflare.com/sandbox/guides/) - Execute commands, manage files, expose services
|
|
109
|
+
- [Examples](https://developers.cloudflare.com/sandbox/tutorials/) - AI agents, data analysis, CI/CD pipelines
|
|
110
|
+
|
|
111
|
+
## Key Features
|
|
112
|
+
|
|
113
|
+
- **Secure Isolation** - Each sandbox runs in its own container
|
|
114
|
+
- **Edge-Native** - Runs on Cloudflare's global network
|
|
115
|
+
- **Code Interpreter** - Execute Python and JavaScript with rich outputs
|
|
116
|
+
- **File System Access** - Read, write, and manage files
|
|
117
|
+
- **Command Execution** - Run any command with streaming support
|
|
118
|
+
- **Preview URLs** - Expose services with public URLs
|
|
119
|
+
- **Git Integration** - Clone repositories directly
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
This repository contains the SDK source code. To contribute:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Clone the repo
|
|
127
|
+
git clone https://github.com/cloudflare/sandbox-sdk
|
|
128
|
+
cd sandbox-sdk
|
|
129
|
+
|
|
130
|
+
# Install dependencies
|
|
131
|
+
npm install
|
|
132
|
+
|
|
133
|
+
# Run tests
|
|
134
|
+
npm test
|
|
135
|
+
|
|
136
|
+
# Build the project
|
|
137
|
+
npm run build
|
|
138
|
+
|
|
139
|
+
# Type checking and linting
|
|
140
|
+
npm run check
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Examples
|
|
144
|
+
|
|
145
|
+
See the [examples directory](./examples) for complete working examples:
|
|
146
|
+
|
|
147
|
+
- [Minimal](./examples/minimal) - Basic sandbox setup
|
|
148
|
+
- [Code Interpreter](./examples/code-interpreter) - Use sandbox as an interpreter tool with gpt-oss
|
|
149
|
+
- [Complete](./examples/basic) - Huge example integrated with every sandbox feature
|
|
150
|
+
|
|
151
|
+
## Status
|
|
152
|
+
|
|
153
|
+
**Beta** - The SDK is in active development. APIs may change before v1.0.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
[MIT License](LICENSE)
|
|
158
|
+
|
|
159
|
+
## Links
|
|
160
|
+
|
|
161
|
+
- [Documentation](https://developers.cloudflare.com/sandbox/)
|
|
162
|
+
- [GitHub Issues](https://github.com/cloudflare/sandbox-sdk/issues)
|
|
163
|
+
- [Developer Discord](https://discord.cloudflare.com)
|
|
164
|
+
- [Cloudflare Developers](https://twitter.com/CloudflareDev)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateLanguage
|
|
3
|
+
} from "./chunk-Z532A7QC.js";
|
|
4
|
+
|
|
5
|
+
// src/interpreter.ts
|
|
6
|
+
import {
|
|
7
|
+
Execution,
|
|
8
|
+
ResultImpl
|
|
9
|
+
} from "@repo/shared";
|
|
10
|
+
var CodeInterpreter = class {
|
|
11
|
+
interpreterClient;
|
|
12
|
+
contexts = /* @__PURE__ */ new Map();
|
|
13
|
+
constructor(sandbox) {
|
|
14
|
+
this.interpreterClient = sandbox.client.interpreter;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create a new code execution context
|
|
18
|
+
*/
|
|
19
|
+
async createCodeContext(options = {}) {
|
|
20
|
+
validateLanguage(options.language);
|
|
21
|
+
const context = await this.interpreterClient.createCodeContext(options);
|
|
22
|
+
this.contexts.set(context.id, context);
|
|
23
|
+
return context;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Run code with optional context
|
|
27
|
+
*/
|
|
28
|
+
async runCode(code, options = {}) {
|
|
29
|
+
let context = options.context;
|
|
30
|
+
if (!context) {
|
|
31
|
+
const language = options.language || "python";
|
|
32
|
+
context = await this.getOrCreateDefaultContext(language);
|
|
33
|
+
}
|
|
34
|
+
const execution = new Execution(code, context);
|
|
35
|
+
await this.interpreterClient.runCodeStream(context.id, code, options.language, {
|
|
36
|
+
onStdout: (output) => {
|
|
37
|
+
execution.logs.stdout.push(output.text);
|
|
38
|
+
if (options.onStdout) return options.onStdout(output);
|
|
39
|
+
},
|
|
40
|
+
onStderr: (output) => {
|
|
41
|
+
execution.logs.stderr.push(output.text);
|
|
42
|
+
if (options.onStderr) return options.onStderr(output);
|
|
43
|
+
},
|
|
44
|
+
onResult: async (result) => {
|
|
45
|
+
execution.results.push(new ResultImpl(result));
|
|
46
|
+
if (options.onResult) return options.onResult(result);
|
|
47
|
+
},
|
|
48
|
+
onError: (error) => {
|
|
49
|
+
execution.error = error;
|
|
50
|
+
if (options.onError) return options.onError(error);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return execution;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Run code and return a streaming response
|
|
57
|
+
*/
|
|
58
|
+
async runCodeStream(code, options = {}) {
|
|
59
|
+
let context = options.context;
|
|
60
|
+
if (!context) {
|
|
61
|
+
const language = options.language || "python";
|
|
62
|
+
context = await this.getOrCreateDefaultContext(language);
|
|
63
|
+
}
|
|
64
|
+
const response = await this.interpreterClient.doFetch("/api/execute/code", {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
Accept: "text/event-stream"
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
context_id: context.id,
|
|
72
|
+
code,
|
|
73
|
+
language: options.language
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const errorData = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
78
|
+
throw new Error(
|
|
79
|
+
errorData.error || `Failed to execute code: ${response.status}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (!response.body) {
|
|
83
|
+
throw new Error("No response body for streaming execution");
|
|
84
|
+
}
|
|
85
|
+
return response.body;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* List all code contexts
|
|
89
|
+
*/
|
|
90
|
+
async listCodeContexts() {
|
|
91
|
+
const contexts = await this.interpreterClient.listCodeContexts();
|
|
92
|
+
for (const context of contexts) {
|
|
93
|
+
this.contexts.set(context.id, context);
|
|
94
|
+
}
|
|
95
|
+
return contexts;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Delete a code context
|
|
99
|
+
*/
|
|
100
|
+
async deleteCodeContext(contextId) {
|
|
101
|
+
await this.interpreterClient.deleteCodeContext(contextId);
|
|
102
|
+
this.contexts.delete(contextId);
|
|
103
|
+
}
|
|
104
|
+
async getOrCreateDefaultContext(language) {
|
|
105
|
+
for (const context of this.contexts.values()) {
|
|
106
|
+
if (context.language === language) {
|
|
107
|
+
return context;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return this.createCodeContext({ language });
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
CodeInterpreter
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=chunk-BCJ7SF3Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/interpreter.ts"],"sourcesContent":["import {\n type CodeContext,\n type CreateContextOptions,\n Execution,\n type ExecutionError,\n type OutputMessage,\n type Result,\n ResultImpl,\n type RunCodeOptions,\n} from \"@repo/shared\";\nimport type { InterpreterClient } from \"./clients/interpreter-client.js\";\nimport type { Sandbox } from \"./sandbox.js\";\nimport { validateLanguage } from \"./security.js\";\n\nexport class CodeInterpreter {\n private interpreterClient: InterpreterClient;\n private contexts = new Map<string, CodeContext>();\n\n constructor(sandbox: Sandbox) {\n // In init-testing architecture, client is a SandboxClient with an interpreter property\n this.interpreterClient = (sandbox.client as any).interpreter as InterpreterClient;\n }\n\n /**\n * Create a new code execution context\n */\n async createCodeContext(\n options: CreateContextOptions = {}\n ): Promise<CodeContext> {\n // Validate language before sending to container\n validateLanguage(options.language);\n\n const context = await this.interpreterClient.createCodeContext(options);\n this.contexts.set(context.id, context);\n return context;\n }\n\n /**\n * Run code with optional context\n */\n async runCode(\n code: string,\n options: RunCodeOptions = {}\n ): Promise<Execution> {\n // Get or create context\n let context = options.context;\n if (!context) {\n // Try to find or create a default context for the language\n const language = options.language || \"python\";\n context = await this.getOrCreateDefaultContext(language);\n }\n\n // Create execution object to collect results\n const execution = new Execution(code, context);\n\n // Stream execution\n await this.interpreterClient.runCodeStream(context.id, code, options.language, {\n onStdout: (output: OutputMessage) => {\n execution.logs.stdout.push(output.text);\n if (options.onStdout) return options.onStdout(output);\n },\n onStderr: (output: OutputMessage) => {\n execution.logs.stderr.push(output.text);\n if (options.onStderr) return options.onStderr(output);\n },\n onResult: async (result: Result) => {\n execution.results.push(new ResultImpl(result) as any);\n if (options.onResult) return options.onResult(result);\n },\n onError: (error: ExecutionError) => {\n execution.error = error;\n if (options.onError) return options.onError(error);\n },\n });\n\n return execution;\n }\n\n /**\n * Run code and return a streaming response\n */\n async runCodeStream(\n code: string,\n options: RunCodeOptions = {}\n ): Promise<ReadableStream> {\n // Get or create context\n let context = options.context;\n if (!context) {\n const language = options.language || \"python\";\n context = await this.getOrCreateDefaultContext(language);\n }\n\n // Create streaming response\n // Note: doFetch is protected but we need direct access for raw stream response\n const response = await (this.interpreterClient as any).doFetch(\"/api/execute/code\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n },\n body: JSON.stringify({\n context_id: context.id,\n code,\n language: options.language,\n }),\n });\n\n if (!response.ok) {\n const errorData = (await response\n .json()\n .catch(() => ({ error: \"Unknown error\" }))) as { error?: string };\n throw new Error(\n errorData.error || `Failed to execute code: ${response.status}`\n );\n }\n\n if (!response.body) {\n throw new Error(\"No response body for streaming execution\");\n }\n\n return response.body;\n }\n\n /**\n * List all code contexts\n */\n async listCodeContexts(): Promise<CodeContext[]> {\n const contexts = await this.interpreterClient.listCodeContexts();\n\n // Update local cache\n for (const context of contexts) {\n this.contexts.set(context.id, context);\n }\n\n return contexts;\n }\n\n /**\n * Delete a code context\n */\n async deleteCodeContext(contextId: string): Promise<void> {\n await this.interpreterClient.deleteCodeContext(contextId);\n this.contexts.delete(contextId);\n }\n\n private async getOrCreateDefaultContext(\n language: \"python\" | \"javascript\" | \"typescript\"\n ): Promise<CodeContext> {\n // Check if we have a cached context for this language\n for (const context of this.contexts.values()) {\n if (context.language === language) {\n return context;\n }\n }\n\n // Create new default context\n return this.createCodeContext({ language });\n }\n}\n"],"mappings":"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","names":[]}
|