@cloudflare/sandbox 0.5.6 → 0.6.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.
Files changed (56) hide show
  1. package/Dockerfile +54 -56
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +3 -1
  5. package/dist/index.js.map +1 -1
  6. package/package.json +11 -6
  7. package/.turbo/turbo-build.log +0 -23
  8. package/CHANGELOG.md +0 -463
  9. package/src/clients/base-client.ts +0 -356
  10. package/src/clients/command-client.ts +0 -133
  11. package/src/clients/file-client.ts +0 -300
  12. package/src/clients/git-client.ts +0 -98
  13. package/src/clients/index.ts +0 -64
  14. package/src/clients/interpreter-client.ts +0 -339
  15. package/src/clients/port-client.ts +0 -105
  16. package/src/clients/process-client.ts +0 -198
  17. package/src/clients/sandbox-client.ts +0 -39
  18. package/src/clients/types.ts +0 -88
  19. package/src/clients/utility-client.ts +0 -156
  20. package/src/errors/adapter.ts +0 -238
  21. package/src/errors/classes.ts +0 -594
  22. package/src/errors/index.ts +0 -109
  23. package/src/file-stream.ts +0 -175
  24. package/src/index.ts +0 -121
  25. package/src/interpreter.ts +0 -168
  26. package/src/openai/index.ts +0 -465
  27. package/src/request-handler.ts +0 -184
  28. package/src/sandbox.ts +0 -1937
  29. package/src/security.ts +0 -119
  30. package/src/sse-parser.ts +0 -147
  31. package/src/storage-mount/credential-detection.ts +0 -41
  32. package/src/storage-mount/errors.ts +0 -51
  33. package/src/storage-mount/index.ts +0 -17
  34. package/src/storage-mount/provider-detection.ts +0 -93
  35. package/src/storage-mount/types.ts +0 -17
  36. package/src/version.ts +0 -6
  37. package/tests/base-client.test.ts +0 -582
  38. package/tests/command-client.test.ts +0 -444
  39. package/tests/file-client.test.ts +0 -831
  40. package/tests/file-stream.test.ts +0 -310
  41. package/tests/get-sandbox.test.ts +0 -172
  42. package/tests/git-client.test.ts +0 -455
  43. package/tests/openai-shell-editor.test.ts +0 -434
  44. package/tests/port-client.test.ts +0 -283
  45. package/tests/process-client.test.ts +0 -649
  46. package/tests/request-handler.test.ts +0 -292
  47. package/tests/sandbox.test.ts +0 -890
  48. package/tests/sse-parser.test.ts +0 -291
  49. package/tests/storage-mount/credential-detection.test.ts +0 -119
  50. package/tests/storage-mount/provider-detection.test.ts +0 -77
  51. package/tests/utility-client.test.ts +0 -339
  52. package/tests/version.test.ts +0 -16
  53. package/tests/wrangler.jsonc +0 -35
  54. package/tsconfig.json +0 -11
  55. package/tsdown.config.ts +0 -13
  56. package/vitest.config.ts +0 -31
package/src/security.ts DELETED
@@ -1,119 +0,0 @@
1
- /**
2
- * Security utilities for URL construction and input validation
3
- *
4
- * This module contains critical security functions to prevent:
5
- * - URL injection attacks
6
- * - SSRF (Server-Side Request Forgery) attacks
7
- * - DNS rebinding attacks
8
- * - Host header injection
9
- * - Open redirect vulnerabilities
10
- */
11
-
12
- export class SecurityError extends Error {
13
- constructor(
14
- message: string,
15
- public readonly code?: string
16
- ) {
17
- super(message);
18
- this.name = 'SecurityError';
19
- }
20
- }
21
-
22
- /**
23
- * Validates port numbers for sandbox services
24
- * Only allows non-system ports to prevent conflicts and security issues
25
- */
26
- export function validatePort(port: number): boolean {
27
- // Must be a valid integer
28
- if (!Number.isInteger(port)) {
29
- return false;
30
- }
31
-
32
- // Only allow non-system ports (1024-65535)
33
- if (port < 1024 || port > 65535) {
34
- return false;
35
- }
36
-
37
- // Exclude ports reserved by our system
38
- const reservedPorts = [
39
- 3000, // Control plane port
40
- 8787 // Common wrangler dev port
41
- ];
42
-
43
- if (reservedPorts.includes(port)) {
44
- return false;
45
- }
46
-
47
- return true;
48
- }
49
-
50
- /**
51
- * Sanitizes and validates sandbox IDs for DNS compliance and security
52
- * Only enforces critical requirements - allows maximum developer flexibility
53
- */
54
- export function sanitizeSandboxId(id: string): string {
55
- // Basic validation: not empty, reasonable length limit (DNS subdomain limit is 63 chars)
56
- if (!id || id.length > 63) {
57
- throw new SecurityError(
58
- 'Sandbox ID must be 1-63 characters long.',
59
- 'INVALID_SANDBOX_ID_LENGTH'
60
- );
61
- }
62
-
63
- // DNS compliance: cannot start or end with hyphens (RFC requirement)
64
- if (id.startsWith('-') || id.endsWith('-')) {
65
- throw new SecurityError(
66
- 'Sandbox ID cannot start or end with hyphens (DNS requirement).',
67
- 'INVALID_SANDBOX_ID_HYPHENS'
68
- );
69
- }
70
-
71
- // Prevent reserved names that cause technical conflicts
72
- const reservedNames = [
73
- 'www',
74
- 'api',
75
- 'admin',
76
- 'root',
77
- 'system',
78
- 'cloudflare',
79
- 'workers'
80
- ];
81
-
82
- const lowerCaseId = id.toLowerCase();
83
- if (reservedNames.includes(lowerCaseId)) {
84
- throw new SecurityError(
85
- `Reserved sandbox ID '${id}' is not allowed.`,
86
- 'RESERVED_SANDBOX_ID'
87
- );
88
- }
89
-
90
- return id;
91
- }
92
-
93
- /**
94
- * Validates language for code interpreter
95
- * Only allows supported languages
96
- */
97
- export function validateLanguage(language: string | undefined): void {
98
- if (!language) {
99
- return; // undefined is valid, will default to python
100
- }
101
-
102
- const supportedLanguages = [
103
- 'python',
104
- 'python3',
105
- 'javascript',
106
- 'js',
107
- 'node',
108
- 'typescript',
109
- 'ts'
110
- ];
111
- const normalized = language.toLowerCase();
112
-
113
- if (!supportedLanguages.includes(normalized)) {
114
- throw new SecurityError(
115
- `Unsupported language '${language}'. Supported languages: python, javascript, typescript`,
116
- 'INVALID_LANGUAGE'
117
- );
118
- }
119
- }
package/src/sse-parser.ts DELETED
@@ -1,147 +0,0 @@
1
- /**
2
- * Server-Sent Events (SSE) parser for streaming responses
3
- * Converts ReadableStream<Uint8Array> to typed AsyncIterable<T>
4
- */
5
-
6
- /**
7
- * Parse a ReadableStream of SSE events into typed AsyncIterable
8
- * @param stream - The ReadableStream from fetch response
9
- * @param signal - Optional AbortSignal for cancellation
10
- */
11
- export async function* parseSSEStream<T>(
12
- stream: ReadableStream<Uint8Array>,
13
- signal?: AbortSignal
14
- ): AsyncIterable<T> {
15
- const reader = stream.getReader();
16
- const decoder = new TextDecoder();
17
- let buffer = '';
18
-
19
- try {
20
- while (true) {
21
- // Check for cancellation
22
- if (signal?.aborted) {
23
- throw new Error('Operation was aborted');
24
- }
25
-
26
- const { done, value } = await reader.read();
27
- if (done) break;
28
-
29
- // Decode chunk and add to buffer
30
- buffer += decoder.decode(value, { stream: true });
31
-
32
- // Process complete SSE events in buffer
33
- const lines = buffer.split('\n');
34
-
35
- // Keep the last incomplete line in buffer
36
- buffer = lines.pop() || '';
37
-
38
- for (const line of lines) {
39
- // Skip empty lines
40
- if (line.trim() === '') continue;
41
-
42
- // Process SSE data lines
43
- if (line.startsWith('data: ')) {
44
- const data = line.substring(6);
45
-
46
- // Skip [DONE] markers or empty data
47
- if (data === '[DONE]' || data.trim() === '') continue;
48
-
49
- try {
50
- const event = JSON.parse(data) as T;
51
- yield event;
52
- } catch {
53
- // Skip invalid JSON events and continue processing
54
- }
55
- }
56
- // Handle other SSE fields if needed (event:, id:, retry:)
57
- // For now, we only care about data: lines
58
- }
59
- }
60
-
61
- // Process any remaining data in buffer
62
- if (buffer.trim() && buffer.startsWith('data: ')) {
63
- const data = buffer.substring(6);
64
- if (data !== '[DONE]' && data.trim()) {
65
- try {
66
- const event = JSON.parse(data) as T;
67
- yield event;
68
- } catch {
69
- // Skip invalid JSON in final event
70
- }
71
- }
72
- }
73
- } finally {
74
- // Clean up resources
75
- try {
76
- await reader.cancel();
77
- } catch {}
78
- reader.releaseLock();
79
- }
80
- }
81
-
82
- /**
83
- * Helper to convert a Response with SSE stream directly to AsyncIterable
84
- * @param response - Response object with SSE stream
85
- * @param signal - Optional AbortSignal for cancellation
86
- */
87
- export async function* responseToAsyncIterable<T>(
88
- response: Response,
89
- signal?: AbortSignal
90
- ): AsyncIterable<T> {
91
- if (!response.ok) {
92
- throw new Error(
93
- `Response not ok: ${response.status} ${response.statusText}`
94
- );
95
- }
96
-
97
- if (!response.body) {
98
- throw new Error('No response body');
99
- }
100
-
101
- yield* parseSSEStream<T>(response.body, signal);
102
- }
103
-
104
- /**
105
- * Create an SSE-formatted ReadableStream from an AsyncIterable
106
- * (Useful for Worker endpoints that need to forward AsyncIterable as SSE)
107
- * @param events - AsyncIterable of events
108
- * @param options - Stream options
109
- */
110
- export function asyncIterableToSSEStream<T>(
111
- events: AsyncIterable<T>,
112
- options?: {
113
- signal?: AbortSignal;
114
- serialize?: (event: T) => string;
115
- }
116
- ): ReadableStream<Uint8Array> {
117
- const encoder = new TextEncoder();
118
- const serialize = options?.serialize || JSON.stringify;
119
-
120
- return new ReadableStream({
121
- async start(controller) {
122
- try {
123
- for await (const event of events) {
124
- if (options?.signal?.aborted) {
125
- controller.error(new Error('Operation was aborted'));
126
- break;
127
- }
128
-
129
- const data = serialize(event);
130
- const sseEvent = `data: ${data}\n\n`;
131
- controller.enqueue(encoder.encode(sseEvent));
132
- }
133
-
134
- // Send completion marker
135
- controller.enqueue(encoder.encode('data: [DONE]\n\n'));
136
- } catch (error) {
137
- controller.error(error);
138
- } finally {
139
- controller.close();
140
- }
141
- },
142
-
143
- cancel() {
144
- // Handle stream cancellation
145
- }
146
- });
147
- }
@@ -1,41 +0,0 @@
1
- import type { BucketCredentials, MountBucketOptions } from '@repo/shared';
2
- import { MissingCredentialsError } from './errors';
3
-
4
- /**
5
- * Detect credentials for bucket mounting from environment variables
6
- * Priority order:
7
- * 1. Explicit options.credentials
8
- * 2. Standard AWS env vars: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
9
- * 3. Error: no credentials found
10
- *
11
- * @param options - Mount options
12
- * @param envVars - Environment variables
13
- * @returns Detected credentials
14
- * @throws MissingCredentialsError if no credentials found
15
- */
16
- export function detectCredentials(
17
- options: MountBucketOptions,
18
- envVars: Record<string, string | undefined>
19
- ): BucketCredentials {
20
- // Priority 1: Explicit credentials in options
21
- if (options.credentials) {
22
- return options.credentials;
23
- }
24
-
25
- // Priority 2: Standard AWS env vars
26
- const awsAccessKeyId = envVars.AWS_ACCESS_KEY_ID;
27
- const awsSecretAccessKey = envVars.AWS_SECRET_ACCESS_KEY;
28
-
29
- if (awsAccessKeyId && awsSecretAccessKey) {
30
- return {
31
- accessKeyId: awsAccessKeyId,
32
- secretAccessKey: awsSecretAccessKey
33
- };
34
- }
35
-
36
- // No credentials found - throw error with helpful message
37
- throw new MissingCredentialsError(
38
- `No credentials found. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY ` +
39
- `environment variables, or pass explicit credentials in options.`
40
- );
41
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * Bucket mounting error classes
3
- *
4
- * These are SDK-side validation errors that follow the same pattern as SecurityError.
5
- * They are thrown before any container interaction occurs.
6
- */
7
-
8
- import { ErrorCode } from '@repo/shared/errors';
9
-
10
- /**
11
- * Base error for bucket mounting operations
12
- */
13
- export class BucketMountError extends Error {
14
- public readonly code: ErrorCode;
15
-
16
- constructor(message: string, code: ErrorCode = ErrorCode.BUCKET_MOUNT_ERROR) {
17
- super(message);
18
- this.name = 'BucketMountError';
19
- this.code = code;
20
- }
21
- }
22
-
23
- /**
24
- * Thrown when S3FS mount command fails
25
- */
26
- export class S3FSMountError extends BucketMountError {
27
- constructor(message: string) {
28
- super(message, ErrorCode.S3FS_MOUNT_ERROR);
29
- this.name = 'S3FSMountError';
30
- }
31
- }
32
-
33
- /**
34
- * Thrown when no credentials found in environment
35
- */
36
- export class MissingCredentialsError extends BucketMountError {
37
- constructor(message: string) {
38
- super(message, ErrorCode.MISSING_CREDENTIALS);
39
- this.name = 'MissingCredentialsError';
40
- }
41
- }
42
-
43
- /**
44
- * Thrown when bucket name, mount path, or options are invalid
45
- */
46
- export class InvalidMountConfigError extends BucketMountError {
47
- constructor(message: string) {
48
- super(message, ErrorCode.INVALID_MOUNT_CONFIG);
49
- this.name = 'InvalidMountConfigError';
50
- }
51
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Bucket mounting functionality
3
- */
4
-
5
- export { detectCredentials } from './credential-detection';
6
- export {
7
- BucketMountError,
8
- InvalidMountConfigError,
9
- MissingCredentialsError,
10
- S3FSMountError
11
- } from './errors';
12
- export {
13
- detectProviderFromUrl,
14
- getProviderFlags,
15
- resolveS3fsOptions
16
- } from './provider-detection';
17
- export type { MountInfo } from './types';
@@ -1,93 +0,0 @@
1
- /**
2
- * Provider detection and s3fs flag configuration
3
- *
4
- * Based on s3fs-fuse documentation:
5
- * https://github.com/s3fs-fuse/s3fs-fuse/wiki/Non-Amazon-S3
6
- */
7
-
8
- import type { BucketProvider } from '@repo/shared';
9
-
10
- /**
11
- * Detect provider from endpoint URL using pattern matching
12
- */
13
- export function detectProviderFromUrl(endpoint: string): BucketProvider | null {
14
- try {
15
- const url = new URL(endpoint);
16
- const hostname = url.hostname.toLowerCase();
17
-
18
- if (hostname.endsWith('.r2.cloudflarestorage.com')) {
19
- return 'r2';
20
- }
21
-
22
- // Match AWS S3: *.amazonaws.com or s3.amazonaws.com
23
- if (
24
- hostname.endsWith('.amazonaws.com') ||
25
- hostname === 's3.amazonaws.com'
26
- ) {
27
- return 's3';
28
- }
29
-
30
- if (hostname === 'storage.googleapis.com') {
31
- return 'gcs';
32
- }
33
-
34
- return null;
35
- } catch {
36
- return null;
37
- }
38
- }
39
-
40
- /**
41
- * Get s3fs flags for a given provider
42
- *
43
- * Based on s3fs-fuse wiki recommendations:
44
- * https://github.com/s3fs-fuse/s3fs-fuse/wiki/Non-Amazon-S3
45
- */
46
- export function getProviderFlags(provider: BucketProvider | null): string[] {
47
- if (!provider) {
48
- return ['use_path_request_style'];
49
- }
50
-
51
- switch (provider) {
52
- case 'r2':
53
- return ['nomixupload'];
54
-
55
- case 's3':
56
- return [];
57
-
58
- case 'gcs':
59
- return [];
60
-
61
- default:
62
- return ['use_path_request_style'];
63
- }
64
- }
65
-
66
- /**
67
- * Resolve s3fs options by combining provider defaults with user overrides
68
- */
69
- export function resolveS3fsOptions(
70
- provider: BucketProvider | null,
71
- userOptions?: string[]
72
- ): string[] {
73
- const providerFlags = getProviderFlags(provider);
74
-
75
- if (!userOptions || userOptions.length === 0) {
76
- return providerFlags;
77
- }
78
-
79
- // Merge provider flags with user options
80
- // User options take precedence (come last in the array)
81
- const allFlags = [...providerFlags, ...userOptions];
82
-
83
- // Deduplicate flags (keep last occurrence)
84
- const flagMap = new Map<string, string>();
85
-
86
- for (const flag of allFlags) {
87
- // Split on '=' to get the flag name
88
- const [flagName] = flag.split('=');
89
- flagMap.set(flagName, flag);
90
- }
91
-
92
- return Array.from(flagMap.values());
93
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Internal bucket mounting types
3
- */
4
-
5
- import type { BucketProvider } from '@repo/shared';
6
-
7
- /**
8
- * Internal tracking information for active mounts
9
- */
10
- export interface MountInfo {
11
- bucket: string;
12
- mountPath: string;
13
- endpoint: string;
14
- provider: BucketProvider | null;
15
- passwordFilePath: string;
16
- mounted: boolean;
17
- }
package/src/version.ts DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * SDK version - automatically synchronized with package.json by Changesets
3
- * This file is auto-updated by .github/changeset-version.ts during releases
4
- * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
5
- */
6
- export const SDK_VERSION = '0.5.6';