@cloudflare/sandbox 0.0.0-4aceb32 → 0.0.0-4bedc3a

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.
@@ -133,16 +133,29 @@ export class JupyterService {
133
133
  * Pre-warm context pools for better performance
134
134
  */
135
135
  private async warmContextPools() {
136
+ // Delay pre-warming to avoid startup rush that triggers Bun WebSocket bug
137
+ await new Promise((resolve) => setTimeout(resolve, 2000));
138
+
136
139
  try {
137
140
  console.log(
138
141
  "[JupyterService] Pre-warming context pools for better performance"
139
142
  );
140
143
 
141
- // Enable pool warming with min sizes
144
+ // Only pre-warm Python contexts to avoid Bun WebSocket validation errors
145
+ // JavaScript contexts will be created on-demand
142
146
  await this.jupyterServer.enablePoolWarming("python", 1);
143
- await this.jupyterServer.enablePoolWarming("javascript", 1);
147
+
148
+ // Commenting out JavaScript pre-warming due to kernel message validation errors
149
+ // The errors appear to be related to Bun's handling of WebSocket messages
150
+ // JavaScript contexts still work fine when created on-demand
151
+ //
152
+ // await new Promise((resolve) => setTimeout(resolve, 1000));
153
+ // await this.jupyterServer.enablePoolWarming("javascript", 1);
144
154
  } catch (error) {
145
155
  console.error("[JupyterService] Error pre-warming context pools:", error);
156
+ console.error(
157
+ "[JupyterService] Pre-warming failed but service continues"
158
+ );
146
159
  }
147
160
  }
148
161
 
@@ -0,0 +1,48 @@
1
+ """
2
+ Minimal Jupyter configuration focused on kernel-only usage
3
+ """
4
+
5
+ c = get_config() # noqa
6
+
7
+ # Disable all authentication - we handle security at container level
8
+ c.ServerApp.token = ''
9
+ c.ServerApp.password = ''
10
+ c.IdentityProvider.token = ''
11
+ c.ServerApp.allow_origin = '*'
12
+ c.ServerApp.allow_remote_access = True
13
+ c.ServerApp.disable_check_xsrf = True
14
+ c.ServerApp.allow_root = True
15
+ c.ServerApp.allow_credentials = True
16
+
17
+ # Also set NotebookApp settings for compatibility
18
+ c.NotebookApp.token = ''
19
+ c.NotebookApp.password = ''
20
+ c.NotebookApp.allow_origin = '*'
21
+ c.NotebookApp.allow_remote_access = True
22
+ c.NotebookApp.disable_check_xsrf = True
23
+ c.NotebookApp.allow_credentials = True
24
+
25
+ # Performance settings
26
+ c.ServerApp.iopub_data_rate_limit = 1000000000 # E2B uses 1GB/s
27
+
28
+ # Minimal logging
29
+ c.Application.log_level = 'ERROR'
30
+
31
+ # Disable browser
32
+ c.ServerApp.open_browser = False
33
+
34
+ # Optimize for container environment
35
+ c.ServerApp.ip = '0.0.0.0'
36
+ c.ServerApp.port = 8888
37
+
38
+ # Kernel optimizations
39
+ c.KernelManager.shutdown_wait_time = 0.0
40
+ c.MappingKernelManager.cull_idle_timeout = 0
41
+ c.MappingKernelManager.cull_interval = 0
42
+
43
+ # Disable terminals
44
+ c.ServerApp.terminals_enabled = False
45
+
46
+ # Disable all extensions to speed up startup
47
+ c.ServerApp.jpserver_extensions = {}
48
+ c.ServerApp.nbserver_extensions = {}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Secure shell command utilities to prevent injection attacks
3
+ */
4
+
5
+ /**
6
+ * Escapes a string for safe use in shell commands.
7
+ * This follows POSIX shell escaping rules to prevent command injection.
8
+ *
9
+ * @param str - The string to escape
10
+ * @returns The escaped string safe for shell use
11
+ */
12
+ export function escapeShellArg(str: string): string {
13
+ // If string is empty, return empty quotes
14
+ if (str === '') {
15
+ return "''";
16
+ }
17
+
18
+ // Check if string contains any characters that need escaping
19
+ // Safe characters: alphanumeric, dash, underscore, dot, slash
20
+ if (/^[a-zA-Z0-9._\-/]+$/.test(str)) {
21
+ return str;
22
+ }
23
+
24
+ // For strings with special characters, use single quotes and escape single quotes
25
+ // Single quotes preserve all characters literally except the single quote itself
26
+ // To include a single quote, we end the quoted string, add an escaped quote, and start a new quoted string
27
+ return `'${str.replace(/'/g, "'\\''")}'`;
28
+ }
29
+
30
+ /**
31
+ * Escapes a file path for safe use in shell commands.
32
+ *
33
+ * @param path - The file path to escape
34
+ * @returns The escaped path safe for shell use
35
+ */
36
+ export function escapeShellPath(path: string): string {
37
+ // Normalize path to prevent issues with multiple slashes
38
+ const normalizedPath = path.replace(/\/+/g, '/');
39
+
40
+ // Apply standard shell escaping
41
+ return escapeShellArg(normalizedPath);
42
+ }
@@ -2,7 +2,8 @@
2
2
 
3
3
  # Function to check if Jupyter is ready
4
4
  check_jupyter_ready() {
5
- curl -s http://localhost:8888/api > /dev/null 2>&1
5
+ # Check if API is responsive and kernelspecs are available
6
+ curl -s http://localhost:8888/api/kernelspecs > /dev/null 2>&1
6
7
  }
7
8
 
8
9
  # Function to notify Bun server that Jupyter is ready
@@ -12,19 +13,10 @@ notify_jupyter_ready() {
12
13
  echo "[Startup] Jupyter is ready, notified Bun server"
13
14
  }
14
15
 
15
- # Start Jupyter notebook server in background
16
+ # Start Jupyter server in background
16
17
  echo "[Startup] Starting Jupyter server..."
17
- jupyter notebook \
18
- --ip=0.0.0.0 \
19
- --port=8888 \
20
- --no-browser \
21
- --allow-root \
22
- --NotebookApp.token='' \
23
- --NotebookApp.password='' \
24
- --NotebookApp.allow_origin='*' \
25
- --NotebookApp.disable_check_xsrf=True \
26
- --NotebookApp.allow_remote_access=True \
27
- --NotebookApp.allow_credentials=True \
18
+ jupyter server \
19
+ --config=/container-server/jupyter_config.py \
28
20
  > /tmp/jupyter.log 2>&1 &
29
21
 
30
22
  JUPYTER_PID=$!
@@ -37,18 +29,21 @@ BUN_PID=$!
37
29
  # Monitor Jupyter readiness in background
38
30
  (
39
31
  echo "[Startup] Monitoring Jupyter readiness in background..."
40
- MAX_ATTEMPTS=30
32
+ MAX_ATTEMPTS=60
41
33
  ATTEMPT=0
42
- DELAY=0.5
43
- MAX_DELAY=5
44
-
34
+
35
+ # Track start time for reporting
36
+ START_TIME=$(date +%s.%N)
37
+
45
38
  while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
46
39
  if check_jupyter_ready; then
47
40
  notify_jupyter_ready
48
- echo "[Startup] Jupyter server is ready after $ATTEMPT attempts"
41
+ END_TIME=$(date +%s.%N)
42
+ ELAPSED=$(awk "BEGIN {printf \"%.2f\", $END_TIME - $START_TIME}")
43
+ echo "[Startup] Jupyter server is ready after $ELAPSED seconds ($ATTEMPT attempts)"
49
44
  break
50
45
  fi
51
-
46
+
52
47
  # Check if Jupyter process is still running
53
48
  if ! kill -0 $JUPYTER_PID 2>/dev/null; then
54
49
  echo "[Startup] WARNING: Jupyter process died. Check /tmp/jupyter.log for details"
@@ -56,21 +51,27 @@ BUN_PID=$!
56
51
  # Don't exit - let Bun server continue running in degraded mode
57
52
  break
58
53
  fi
59
-
54
+
60
55
  ATTEMPT=$((ATTEMPT + 1))
61
- echo "[Startup] Jupyter not ready yet (attempt $ATTEMPT/$MAX_ATTEMPTS, delay ${DELAY}s)"
62
-
63
- # Sleep with exponential backoff
64
- sleep $DELAY
65
-
66
- # Increase delay exponentially with jitter, cap at MAX_DELAY
67
- DELAY=$(awk "BEGIN {printf \"%.2f\", $DELAY * 1.5 + (rand() * 0.5)}")
68
- # Use awk for comparison since bc might not be available
69
- if [ $(awk "BEGIN {print ($DELAY > $MAX_DELAY)}") -eq 1 ]; then
70
- DELAY=$MAX_DELAY
56
+
57
+ # Start with faster checks
58
+ if [ $ATTEMPT -eq 1 ]; then
59
+ DELAY=0.5 # Start at 0.5s
60
+ else
61
+ # Exponential backoff with 1.3x multiplier (less aggressive than 1.5x)
62
+ DELAY=$(awk "BEGIN {printf \"%.2f\", $DELAY * 1.3}")
63
+ # Cap at 2s max (instead of 5s)
64
+ if [ $(awk "BEGIN {print ($DELAY > 2)}") -eq 1 ]; then
65
+ DELAY=2
66
+ fi
71
67
  fi
68
+
69
+ # Log with current delay for transparency
70
+ echo "[Startup] Jupyter not ready yet (attempt $ATTEMPT/$MAX_ATTEMPTS, next check in ${DELAY}s)"
71
+
72
+ sleep $DELAY
72
73
  done
73
-
74
+
74
75
  if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
75
76
  echo "[Startup] WARNING: Jupyter failed to become ready within attempts"
76
77
  echo "[Startup] Jupyter logs:"
@@ -80,4 +81,4 @@ BUN_PID=$!
80
81
  ) &
81
82
 
82
83
  # Wait for Bun server (main process)
83
- wait $BUN_PID
84
+ wait $BUN_PID
@@ -17,19 +17,20 @@ export interface ProcessRecord {
17
17
  startTime: Date;
18
18
  endTime?: Date;
19
19
  exitCode?: number;
20
- sessionId?: string;
21
- childProcess?: ChildProcess;
20
+ stdoutFile?: string; // Path to temp file containing stdout
21
+ stderrFile?: string; // Path to temp file containing stderr
22
22
  stdout: string;
23
23
  stderr: string;
24
24
  outputListeners: Set<(stream: 'stdout' | 'stderr', data: string) => void>;
25
25
  statusListeners: Set<(status: ProcessStatus) => void>;
26
+ monitoringInterval?: NodeJS.Timeout; // For polling temp files when streaming
26
27
  }
27
28
 
28
29
  export interface StartProcessRequest {
29
30
  command: string;
31
+ sessionId: string;
30
32
  options?: {
31
33
  processId?: string;
32
- sessionId?: string;
33
34
  timeout?: number;
34
35
  env?: Record<string, string>;
35
36
  cwd?: string;
@@ -39,7 +40,6 @@ export interface StartProcessRequest {
39
40
  }
40
41
 
41
42
  export interface ExecuteOptions {
42
- sessionId?: string | null;
43
43
  background?: boolean;
44
44
  cwd?: string | URL;
45
45
  env?: Record<string, string>;
@@ -47,49 +47,59 @@ export interface ExecuteOptions {
47
47
 
48
48
  export interface ExecuteRequest extends ExecuteOptions {
49
49
  command: string;
50
+ sessionId: string;
50
51
  }
51
52
 
52
53
  export interface GitCheckoutRequest {
53
54
  repoUrl: string;
54
55
  branch?: string;
55
56
  targetDir?: string;
56
- sessionId?: string;
57
+ sessionId: string;
57
58
  }
58
59
 
59
60
  export interface MkdirRequest {
60
61
  path: string;
61
62
  recursive?: boolean;
62
- sessionId?: string;
63
+ sessionId: string;
63
64
  }
64
65
 
65
66
  export interface WriteFileRequest {
66
67
  path: string;
67
68
  content: string;
68
69
  encoding?: string;
69
- sessionId?: string;
70
+ sessionId: string;
70
71
  }
71
72
 
72
73
  export interface ReadFileRequest {
73
74
  path: string;
74
75
  encoding?: string;
75
- sessionId?: string;
76
+ sessionId: string;
76
77
  }
77
78
 
78
79
  export interface DeleteFileRequest {
79
80
  path: string;
80
- sessionId?: string;
81
+ sessionId: string;
81
82
  }
82
83
 
83
84
  export interface RenameFileRequest {
84
85
  oldPath: string;
85
86
  newPath: string;
86
- sessionId?: string;
87
+ sessionId: string;
87
88
  }
88
89
 
89
90
  export interface MoveFileRequest {
90
91
  sourcePath: string;
91
92
  destinationPath: string;
92
- sessionId?: string;
93
+ sessionId: string;
94
+ }
95
+
96
+ export interface ListFilesRequest {
97
+ path: string;
98
+ options?: {
99
+ recursive?: boolean;
100
+ includeHidden?: boolean;
101
+ };
102
+ sessionId: string;
93
103
  }
94
104
 
95
105
  export interface ExposePortRequest {
@@ -102,7 +112,20 @@ export interface UnexposePortRequest {
102
112
  }
103
113
 
104
114
  export interface SessionData {
105
- sessionId: string;
115
+ id: string;
106
116
  activeProcess: ChildProcess | null;
107
117
  createdAt: Date;
108
118
  }
119
+
120
+ // Session management API types
121
+ export interface CreateSessionRequest {
122
+ id: string;
123
+ env?: Record<string, string>;
124
+ cwd?: string;
125
+ isolation?: boolean;
126
+ }
127
+
128
+ export interface SessionExecRequest {
129
+ id: string;
130
+ command: string;
131
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudflare/sandbox",
3
- "version": "0.0.0-4aceb32",
3
+ "version": "0.0.0-4bedc3a",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cloudflare/sandbox-sdk"