@claxedo/sandbox-manager 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -89,14 +89,14 @@ function createMemoryLeaseStore(seed = []) {
89
89
  },
90
90
  async update(workspaceId, expectedEpoch, patch) {
91
91
  const current = leases.get(workspaceId);
92
- if (!current || current.epoch !== expectedEpoch) return;
92
+ if (!current || current.epoch !== expectedEpoch) return void 0;
93
93
  const next = applySandboxLeasePatch(current, patch, Date.now());
94
94
  leases.set(workspaceId, next);
95
95
  return next;
96
96
  },
97
97
  async recordFailure(workspaceId, expectedEpoch, error, nextRetryAt) {
98
98
  const current = leases.get(workspaceId);
99
- if (!current || current.epoch !== expectedEpoch) return;
99
+ if (!current || current.epoch !== expectedEpoch) return void 0;
100
100
  const next = {
101
101
  ...current,
102
102
  status: "unavailable",
package/package.json CHANGED
@@ -1,6 +1,4 @@
1
1
  {
2
- "name": "@claxedo/sandbox-manager",
3
- "version": "0.7.0",
4
2
  "type": "module",
5
3
  "description": "Sandbox lifecycle manager with epoch-based leases and pluggable drivers for Daytona, Modal, Vercel Sandbox, Cloudflare, Box, and Docker",
6
4
  "license": "MIT",
@@ -24,151 +22,151 @@
24
22
  "types": "./dist/index.d.ts",
25
23
  "exports": {
26
24
  ".": {
25
+ "development": "./src/index.ts",
26
+ "bun": "./src/index.ts",
27
27
  "types": "./dist/index.d.ts",
28
- "bun": "./dist/index.mjs",
29
- "development": "./dist/index.mjs",
30
28
  "import": "./dist/index.mjs",
31
29
  "default": "./dist/index.mjs"
32
30
  },
33
31
  "./command": {
32
+ "development": "./src/command.ts",
33
+ "bun": "./src/command.ts",
34
34
  "types": "./dist/command.d.ts",
35
- "bun": "./dist/command.mjs",
36
- "development": "./dist/command.mjs",
37
35
  "import": "./dist/command.mjs",
38
36
  "default": "./dist/command.mjs"
39
37
  },
40
38
  "./constants": {
39
+ "development": "./src/constants.ts",
40
+ "bun": "./src/constants.ts",
41
41
  "types": "./dist/constants.d.ts",
42
- "bun": "./dist/constants.mjs",
43
- "development": "./dist/constants.mjs",
44
42
  "import": "./dist/constants.mjs",
45
43
  "default": "./dist/constants.mjs"
46
44
  },
47
45
  "./checkpoint-manager": {
46
+ "development": "./src/checkpoint-manager.ts",
47
+ "bun": "./src/checkpoint-manager.ts",
48
48
  "types": "./dist/checkpoint-manager.d.ts",
49
- "bun": "./dist/checkpoint-manager.mjs",
50
- "development": "./dist/checkpoint-manager.mjs",
51
49
  "import": "./dist/checkpoint-manager.mjs",
52
50
  "default": "./dist/checkpoint-manager.mjs"
53
51
  },
54
52
  "./defaults": {
53
+ "development": "./src/defaults.ts",
54
+ "bun": "./src/defaults.ts",
55
55
  "types": "./dist/defaults.d.ts",
56
- "bun": "./dist/defaults.mjs",
57
- "development": "./dist/defaults.mjs",
58
56
  "import": "./dist/defaults.mjs",
59
57
  "default": "./dist/defaults.mjs"
60
58
  },
61
59
  "./driver-catalog": {
60
+ "development": "./src/driver-catalog.ts",
61
+ "bun": "./src/driver-catalog.ts",
62
62
  "types": "./dist/driver-catalog.d.ts",
63
- "bun": "./dist/driver-catalog.mjs",
64
- "development": "./dist/driver-catalog.mjs",
65
63
  "import": "./dist/driver-catalog.mjs",
66
64
  "default": "./dist/driver-catalog.mjs"
67
65
  },
68
66
  "./drivers/box": {
67
+ "development": "./src/drivers/box.ts",
68
+ "bun": "./src/drivers/box.ts",
69
69
  "types": "./dist/drivers/box.d.ts",
70
- "bun": "./dist/drivers/box.mjs",
71
- "development": "./dist/drivers/box.mjs",
72
70
  "import": "./dist/drivers/box.mjs",
73
71
  "default": "./dist/drivers/box.mjs"
74
72
  },
75
73
  "./drivers/exe": {
74
+ "development": "./src/drivers/exe.ts",
75
+ "bun": "./src/drivers/exe.ts",
76
76
  "types": "./dist/drivers/exe.d.ts",
77
- "bun": "./dist/drivers/exe.mjs",
78
- "development": "./dist/drivers/exe.mjs",
79
77
  "import": "./dist/drivers/exe.mjs",
80
78
  "default": "./dist/drivers/exe.mjs"
81
79
  },
82
80
  "./drivers/cloudflare": {
81
+ "development": "./src/drivers/cloudflare.ts",
82
+ "bun": "./src/drivers/cloudflare.ts",
83
83
  "types": "./dist/drivers/cloudflare.d.ts",
84
- "bun": "./dist/drivers/cloudflare.mjs",
85
- "development": "./dist/drivers/cloudflare.mjs",
86
84
  "import": "./dist/drivers/cloudflare.mjs",
87
85
  "default": "./dist/drivers/cloudflare.mjs"
88
86
  },
89
87
  "./drivers/daytona": {
88
+ "development": "./src/drivers/daytona.ts",
89
+ "bun": "./src/drivers/daytona.ts",
90
90
  "types": "./dist/drivers/daytona.d.ts",
91
- "bun": "./dist/drivers/daytona.mjs",
92
- "development": "./dist/drivers/daytona.mjs",
93
91
  "import": "./dist/drivers/daytona.mjs",
94
92
  "default": "./dist/drivers/daytona.mjs"
95
93
  },
96
94
  "./drivers/docker": {
95
+ "development": "./src/drivers/docker.ts",
96
+ "bun": "./src/drivers/docker.ts",
97
97
  "types": "./dist/drivers/docker.d.ts",
98
- "bun": "./dist/drivers/docker.mjs",
99
- "development": "./dist/drivers/docker.mjs",
100
98
  "import": "./dist/drivers/docker.mjs",
101
99
  "default": "./dist/drivers/docker.mjs"
102
100
  },
103
101
  "./drivers/fetch-bridge": {
102
+ "development": "./src/drivers/fetch-bridge.ts",
103
+ "bun": "./src/drivers/fetch-bridge.ts",
104
104
  "types": "./dist/drivers/fetch-bridge.d.ts",
105
- "bun": "./dist/drivers/fetch-bridge.mjs",
106
- "development": "./dist/drivers/fetch-bridge.mjs",
107
105
  "import": "./dist/drivers/fetch-bridge.mjs",
108
106
  "default": "./dist/drivers/fetch-bridge.mjs"
109
107
  },
110
108
  "./drivers/modal": {
109
+ "development": "./src/drivers/modal.ts",
110
+ "bun": "./src/drivers/modal.ts",
111
111
  "types": "./dist/drivers/modal.d.ts",
112
- "bun": "./dist/drivers/modal.mjs",
113
- "development": "./dist/drivers/modal.mjs",
114
112
  "import": "./dist/drivers/modal.mjs",
115
113
  "default": "./dist/drivers/modal.mjs"
116
114
  },
117
115
  "./drivers/vercel": {
116
+ "development": "./src/drivers/vercel.ts",
117
+ "bun": "./src/drivers/vercel.ts",
118
118
  "types": "./dist/drivers/vercel.d.ts",
119
- "bun": "./dist/drivers/vercel.mjs",
120
- "development": "./dist/drivers/vercel.mjs",
121
119
  "import": "./dist/drivers/vercel.mjs",
122
120
  "default": "./dist/drivers/vercel.mjs"
123
121
  },
124
122
  "./image": {
123
+ "development": "./src/image.ts",
124
+ "bun": "./src/image.ts",
125
125
  "types": "./dist/image.d.ts",
126
- "bun": "./dist/image.mjs",
127
- "development": "./dist/image.mjs",
128
126
  "import": "./dist/image.mjs",
129
127
  "default": "./dist/image.mjs"
130
128
  },
129
+ "./image-name": {
130
+ "development": "./src/image-name.ts",
131
+ "bun": "./src/image-name.ts",
132
+ "types": "./dist/image-name.d.ts",
133
+ "import": "./dist/image-name.mjs",
134
+ "default": "./dist/image-name.mjs"
135
+ },
131
136
  "./lease-policy": {
137
+ "development": "./src/lease-policy.ts",
138
+ "bun": "./src/lease-policy.ts",
132
139
  "types": "./dist/lease-policy.d.ts",
133
- "bun": "./dist/lease-policy.mjs",
134
- "development": "./dist/lease-policy.mjs",
135
140
  "import": "./dist/lease-policy.mjs",
136
141
  "default": "./dist/lease-policy.mjs"
137
142
  },
138
143
  "./lease-types": {
144
+ "development": "./src/lease-types.ts",
145
+ "bun": "./src/lease-types.ts",
139
146
  "types": "./dist/lease-types.d.ts",
140
- "bun": "./dist/lease-types.mjs",
141
- "development": "./dist/lease-types.mjs",
142
147
  "import": "./dist/lease-types.mjs",
143
148
  "default": "./dist/lease-types.mjs"
144
149
  },
145
150
  "./runtime-env": {
151
+ "development": "./src/runtime-env.ts",
152
+ "bun": "./src/runtime-env.ts",
146
153
  "types": "./dist/runtime-env.d.ts",
147
- "bun": "./dist/runtime-env.mjs",
148
- "development": "./dist/runtime-env.mjs",
149
154
  "import": "./dist/runtime-env.mjs",
150
155
  "default": "./dist/runtime-env.mjs"
151
156
  },
152
157
  "./service-url-exposure": {
158
+ "development": "./src/service-url-exposure.ts",
159
+ "bun": "./src/service-url-exposure.ts",
153
160
  "types": "./dist/service-url-exposure.d.ts",
154
- "bun": "./dist/service-url-exposure.mjs",
155
- "development": "./dist/service-url-exposure.mjs",
156
161
  "import": "./dist/service-url-exposure.mjs",
157
162
  "default": "./dist/service-url-exposure.mjs"
158
163
  },
159
164
  "./stores/memory": {
165
+ "development": "./src/stores/memory.ts",
166
+ "bun": "./src/stores/memory.ts",
160
167
  "types": "./dist/stores/memory.d.ts",
161
- "bun": "./dist/stores/memory.mjs",
162
- "development": "./dist/stores/memory.mjs",
163
168
  "import": "./dist/stores/memory.mjs",
164
169
  "default": "./dist/stores/memory.mjs"
165
- },
166
- "./drivers/cloudflare-egress": {
167
- "types": "./dist/drivers/cloudflare-egress.d.ts",
168
- "bun": "./dist/drivers/cloudflare-egress.mjs",
169
- "development": "./dist/drivers/cloudflare-egress.mjs",
170
- "import": "./dist/drivers/cloudflare-egress.mjs",
171
- "default": "./dist/drivers/cloudflare-egress.mjs"
172
170
  }
173
171
  },
174
172
  "files": [
@@ -179,16 +177,20 @@
179
177
  "publishConfig": {
180
178
  "access": "public"
181
179
  },
182
- "dependencies": {
183
- "@daytona/sdk": "0.192.0",
184
- "@vercel/sandbox": "1.10.2",
185
- "modal": "0.7.5"
186
- },
180
+ "name": "@claxedo/sandbox-manager",
181
+ "version": "0.9.0",
187
182
  "scripts": {
188
183
  "build": "tsx scripts/build.ts",
189
184
  "prepublishOnly": "npm run build",
190
185
  "typecheck": "tsc --noEmit -p tsconfig.json",
191
- "test": "mkdir -p .artifacts/unit && bun test src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml"
186
+ "test": "mkdir -p .artifacts/unit && node ../../scripts/test-deadline.mjs bun test src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml"
187
+ },
188
+ "dependencies": {
189
+ "@claxedo/helpers": "0.1.0",
190
+ "@claxedo/sandbox-contract": "0.8.0",
191
+ "@daytona/sdk": "0.211.2",
192
+ "@vercel/sandbox": "3.3.0",
193
+ "modal": "0.10.1"
192
194
  },
193
195
  "devDependencies": {
194
196
  "@tsconfig/node-lts": "22.0.4",
@@ -1,40 +0,0 @@
1
- export type EgressTokenClaims = {
2
- /** Sandbox the token is bound to. */
3
- sub: string;
4
- /** Hosts the sandbox may reach through the broker. */
5
- hosts: string[];
6
- /** Expiry (epoch seconds). */
7
- exp: number;
8
- };
9
- /** Mint a short-lived HS256 token binding a sandbox to its brokered hosts. Carries NO secret values. */
10
- export declare function mintEgressToken(input: {
11
- sandboxId: string;
12
- hosts: string[];
13
- signingSecret: string;
14
- ttlSeconds?: number;
15
- now?: () => number;
16
- }): Promise<string>;
17
- /** Verify a token and return its claims, or null if invalid/expired. */
18
- export declare function verifyEgressToken(token: string, signingSecret: string, now?: () => number): Promise<EgressTokenClaims | null>;
19
- /** How the sandbox names its egress target: header carries the absolute upstream URL. */
20
- export declare const EGRESS_TARGET_HEADER = "x-claxedo-egress-target";
21
- /** Resolves the brokered credential for a (sandbox, host) pair — Worker-side only, never the sandbox. */
22
- export type EgressSecretResolver = (sandboxId: string, host: string) => Promise<{
23
- header: string;
24
- value: string;
25
- } | undefined> | {
26
- header: string;
27
- value: string;
28
- } | undefined;
29
- /**
30
- * Handle one egress-proxy request from a sandbox: validate the JWT, confirm
31
- * the target host is allowed, inject the brokered credential as its header,
32
- * and forward. The credential is read from `resolveSecret` (Worker store) and
33
- * never round-trips through the sandbox.
34
- */
35
- export declare function handleEgressRequest(request: Request, options: {
36
- signingSecret: string;
37
- resolveSecret: EgressSecretResolver;
38
- fetchImpl?: typeof fetch;
39
- now?: () => number;
40
- }): Promise<Response>;
@@ -1,113 +0,0 @@
1
- // src/drivers/cloudflare-egress.ts
2
- function b64urlEncode(bytes) {
3
- let binary = "";
4
- for (const byte of bytes) binary += String.fromCharCode(byte);
5
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6
- }
7
- function b64urlDecode(input) {
8
- const padded = input.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - input.length % 4) % 4);
9
- const binary = atob(padded);
10
- const bytes = new Uint8Array(binary.length);
11
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
12
- return bytes;
13
- }
14
- function encodeJson(value) {
15
- return b64urlEncode(new TextEncoder().encode(JSON.stringify(value)));
16
- }
17
- async function hmacKey(secret) {
18
- return crypto.subtle.importKey(
19
- "raw",
20
- new TextEncoder().encode(secret),
21
- { name: "HMAC", hash: "SHA-256" },
22
- false,
23
- ["sign", "verify"]
24
- );
25
- }
26
- function timingSafeEqual(a, b) {
27
- if (a.length !== b.length) return false;
28
- let diff = 0;
29
- for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
30
- return diff === 0;
31
- }
32
- async function mintEgressToken(input) {
33
- const now = input.now ?? Date.now;
34
- const header = encodeJson({ alg: "HS256", typ: "JWT" });
35
- const payload = encodeJson({
36
- sub: input.sandboxId,
37
- hosts: input.hosts,
38
- exp: Math.floor(now() / 1e3) + (input.ttlSeconds ?? 15 * 60)
39
- });
40
- const key = await hmacKey(input.signingSecret);
41
- const sig = new Uint8Array(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${header}.${payload}`)));
42
- return `${header}.${payload}.${b64urlEncode(sig)}`;
43
- }
44
- async function verifyEgressToken(token, signingSecret, now = Date.now) {
45
- const parts = token.split(".");
46
- if (parts.length !== 3) return null;
47
- const [header, payload, sig] = parts;
48
- const key = await hmacKey(signingSecret);
49
- const expected = b64urlEncode(
50
- new Uint8Array(await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${header}.${payload}`)))
51
- );
52
- if (!timingSafeEqual(sig, expected)) return null;
53
- let claims;
54
- try {
55
- claims = JSON.parse(new TextDecoder().decode(b64urlDecode(payload)));
56
- } catch {
57
- return null;
58
- }
59
- if (typeof claims.sub !== "string" || !Array.isArray(claims.hosts) || typeof claims.exp !== "number") return null;
60
- if (claims.exp <= Math.floor(now() / 1e3)) return null;
61
- return claims;
62
- }
63
- var EGRESS_TARGET_HEADER = "x-claxedo-egress-target";
64
- async function handleEgressRequest(request, options) {
65
- const fetchImpl = options.fetchImpl ?? fetch;
66
- const auth = request.headers.get("authorization");
67
- const token = auth?.toLowerCase().startsWith("bearer ") ? auth.slice(7) : void 0;
68
- if (!token) return egressError(401, "missing egress token");
69
- const claims = await verifyEgressToken(token, options.signingSecret, options.now);
70
- if (!claims) return egressError(401, "invalid or expired egress token");
71
- const targetRaw = request.headers.get(EGRESS_TARGET_HEADER);
72
- if (!targetRaw) return egressError(400, "missing egress target");
73
- let target;
74
- try {
75
- target = new URL(targetRaw);
76
- } catch {
77
- return egressError(400, "malformed egress target");
78
- }
79
- if (target.protocol !== "https:") return egressError(400, "egress target must be https");
80
- if (!claims.hosts.includes(target.host)) return egressError(403, "host not permitted for this sandbox");
81
- const secret = await options.resolveSecret(claims.sub, target.host);
82
- if (!secret) return egressError(403, "no brokered credential for host");
83
- const headers = new Headers(request.headers);
84
- headers.delete("authorization");
85
- headers.delete(EGRESS_TARGET_HEADER);
86
- headers.set(secret.header, secret.value);
87
- headers.set("host", target.host);
88
- try {
89
- const upstream = await fetchImpl(target.toString(), {
90
- method: request.method,
91
- headers,
92
- body: request.method === "GET" || request.method === "HEAD" ? void 0 : request.body,
93
- redirect: "manual"
94
- });
95
- const outHeaders = new Headers(upstream.headers);
96
- outHeaders.delete(secret.header);
97
- return new Response(upstream.body, { status: upstream.status, statusText: upstream.statusText, headers: outHeaders });
98
- } catch {
99
- return egressError(502, "upstream request failed");
100
- }
101
- }
102
- function egressError(status, message) {
103
- return new Response(JSON.stringify({ error: message }), {
104
- status,
105
- headers: { "content-type": "application/json" }
106
- });
107
- }
108
- export {
109
- EGRESS_TARGET_HEADER,
110
- handleEgressRequest,
111
- mintEgressToken,
112
- verifyEgressToken
113
- };