sandboxbox 2.0.7 → 2.0.9

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/CLAUDE.md CHANGED
@@ -132,7 +132,60 @@ exec claude # Changes save directly to local repo
132
132
  ### Path resolution issues
133
133
  **Solution**: Use explicit REPO_PATH environment variable
134
134
 
135
+ ## Command Isolation Principles
136
+
137
+ ### Unified Architecture - All Commands Use Isolation
138
+ - **`run` command**: Creates isolated temporary environment - changes DO NOT affect host
139
+ - **`claude` command**: Creates isolated temporary environment - changes DO NOT affect host
140
+ - **`shell` command**: Creates isolated temporary environment - changes DO NOT affect host
141
+
142
+ ### Isolation Workflow
143
+ 1. Copy project to temporary directory (including hidden files like .git)
144
+ 2. Mount temporary directory as /workspace in container
145
+ 3. Run commands in isolated environment
146
+ 4. Clean up temporary directory on exit
147
+ 5. Changes are persisted via git commands (commit/push) if needed
148
+
149
+ ### Shared Isolation Utility (utils/isolation.js)
150
+ ```javascript
151
+ // All commands use the same isolation pattern
152
+ import { createIsolatedEnvironment, setupCleanupHandlers } from './utils/isolation.js';
153
+
154
+ // Create isolated environment
155
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
156
+
157
+ // Set up cleanup handlers
158
+ setupCleanupHandlers(cleanup);
159
+
160
+ // Run command with isolated directory
161
+ execSync(`podman run --rm -it -v "${tempProjectDir}:/workspace:rw" -w /workspace sandboxbox:latest ${cmd}`, {
162
+ stdio: 'inherit',
163
+ shell: process.platform === 'win32'
164
+ });
165
+
166
+ // Clean up on completion
167
+ cleanup();
168
+ ```
169
+
170
+ ### Container Naming and Cleanup
171
+ - Use random short names: `sandboxbox-run-${Math.random().toString(36).substr(2, 9)}`
172
+ - Force cleanup: `podman rm -f container-name`
173
+ - Automatic cleanup handlers for all exit scenarios
174
+ - Cross-platform signal handling (SIGINT, SIGTERM)
175
+
176
+ ### Cross-Platform Path Handling
177
+ ```javascript
178
+ // Normalize Windows paths for podman cp command
179
+ const normalizedProjectDir = projectDir.replace(/\\/g, '/');
180
+ ```
181
+
135
182
  ## Version Management
136
183
  - Publish new version when fixing critical Windows issues
137
184
  - Clear npm cache: `npm cache clean --force`
138
- - Use specific version: `npx sandboxbox@latest`
185
+ - Use specific version: `npx sandboxbox@latest`
186
+
187
+ ## File Cleanup Requirements
188
+ - All temporary containers auto-cleanup on exit
189
+ - All temporary directories auto-cleanup on exit
190
+ - Error handling for cleanup failures (ignore errors)
191
+ - Signal handlers ensure cleanup on interrupts
@@ -0,0 +1,18 @@
1
+ FROM node:20
2
+
3
+ # Install basic development tools
4
+ RUN apt-get update && apt-get install -y --no-install-recommends \
5
+ git \
6
+ bash \
7
+ curl \
8
+ nano \
9
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Create workspace directory
12
+ WORKDIR /workspace
13
+
14
+ # Set up non-root user
15
+ USER node
16
+
17
+ # Install Claude Code
18
+ RUN npm install -g @anthropic-ai/claude-code@latest
package/cli.js CHANGED
@@ -15,6 +15,7 @@ import { fileURLToPath } from 'url';
15
15
  import { color } from './utils/colors.js';
16
16
  import { checkPodman, getPodmanPath } from './utils/podman.js';
17
17
  import { buildClaudeContainerCommand, createClaudeDockerfile } from './utils/claude-workspace.js';
18
+ import { createIsolatedEnvironment, setupCleanupHandlers } from './utils/isolation.js';
18
19
 
19
20
  const __filename = fileURLToPath(import.meta.url);
20
21
  const __dirname = dirname(__filename);
@@ -85,20 +86,32 @@ function runClaudeWorkspace(projectDir, command = 'claude') {
85
86
  return false;
86
87
  }
87
88
 
88
- console.log(color('blue', 'šŸš€ Starting Claude Code with local repository...'));
89
+ console.log(color('blue', 'šŸš€ Starting Claude Code in isolated environment...'));
89
90
  console.log(color('yellow', `Project: ${projectDir}`));
90
- console.log(color('yellow', `Command: ${command}\n`));
91
+ console.log(color('yellow', `Command: ${command}`));
92
+ console.log(color('cyan', 'šŸ“¦ Note: Changes will be isolated and will NOT affect the original repository\n'));
91
93
 
92
94
  const podmanPath = checkPodman();
93
95
  if (!podmanPath) return false;
94
96
 
95
97
  try {
96
- const containerCommand = buildClaudeContainerCommand(projectDir, podmanPath, command);
98
+ // Create isolated environment
99
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
100
+
101
+ // Set up cleanup handlers
102
+ setupCleanupHandlers(cleanup);
103
+
104
+ // Build container command with isolated project directory
105
+ const containerCommand = buildClaudeContainerCommand(tempProjectDir, podmanPath, command);
97
106
  execSync(containerCommand, {
98
107
  stdio: 'inherit',
99
108
  shell: process.platform === 'win32'
100
109
  });
101
- console.log(color('green', '\nāœ… Claude Code session completed!'));
110
+
111
+ // Clean up the temporary directory
112
+ cleanup();
113
+
114
+ console.log(color('green', '\nāœ… Claude Code session completed! (Isolated - no host changes)'));
102
115
  return true;
103
116
  } catch (error) {
104
117
  console.log(color('red', `\nāŒ Claude Code failed: ${error.message}`));
@@ -161,19 +174,31 @@ async function main() {
161
174
  process.exit(1);
162
175
  }
163
176
 
164
- console.log(color('blue', 'šŸš€ Running project in container...'));
177
+ console.log(color('blue', 'šŸš€ Running project in isolated container...'));
165
178
  console.log(color('yellow', `Project: ${projectDir}`));
166
179
  console.log(color('yellow', `Command: ${cmd}\n`));
180
+ console.log(color('cyan', 'šŸ“¦ Note: Changes will NOT affect host files (isolated environment)'));
167
181
 
168
182
  const runPodman = checkPodman();
169
183
  if (!runPodman) process.exit(1);
170
184
 
171
185
  try {
172
- execSync(`"${runPodman}" run --rm -it -v "${projectDir}:/workspace" -w /workspace sandboxbox:latest ${cmd}`, {
186
+ // Create isolated environment
187
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
188
+
189
+ // Set up cleanup handlers
190
+ setupCleanupHandlers(cleanup);
191
+
192
+ // Run the command in isolated container with temporary directory
193
+ execSync(`"${runPodman}" run --rm -it -v "${tempProjectDir}:/workspace:rw" -w /workspace sandboxbox:latest ${cmd}`, {
173
194
  stdio: 'inherit',
174
195
  shell: process.platform === 'win32'
175
196
  });
176
- console.log(color('green', '\nāœ… Container execution completed!'));
197
+
198
+ // Clean up the temporary directory
199
+ cleanup();
200
+
201
+ console.log(color('green', '\nāœ… Container execution completed! (Isolated - no host changes)'));
177
202
  } catch (error) {
178
203
  console.log(color('red', `\nāŒ Run failed: ${error.message}`));
179
204
  process.exit(1);
@@ -194,17 +219,28 @@ async function main() {
194
219
  process.exit(1);
195
220
  }
196
221
 
197
- console.log(color('blue', '🐚 Starting interactive shell...'));
222
+ console.log(color('blue', '🐚 Starting interactive shell in isolated container...'));
198
223
  console.log(color('yellow', `Project: ${shellProjectDir}\n`));
224
+ console.log(color('cyan', 'šŸ“¦ Note: Changes will NOT affect host files (isolated environment)'));
199
225
 
200
226
  const shellPodman = checkPodman();
201
227
  if (!shellPodman) process.exit(1);
202
228
 
203
229
  try {
204
- execSync(`"${shellPodman}" run --rm -it -v "${shellProjectDir}:/workspace" -w /workspace sandboxbox:latest /bin/bash`, {
230
+ // Create isolated environment
231
+ const { tempProjectDir, cleanup } = createIsolatedEnvironment(shellProjectDir);
232
+
233
+ // Set up cleanup handlers
234
+ setupCleanupHandlers(cleanup);
235
+
236
+ // Start interactive shell in isolated container with temporary directory
237
+ execSync(`"${shellPodman}" run --rm -it -v "${tempProjectDir}:/workspace:rw" -w /workspace sandboxbox:latest /bin/bash`, {
205
238
  stdio: 'inherit',
206
239
  shell: process.platform === 'win32'
207
240
  });
241
+
242
+ // Clean up the temporary directory
243
+ cleanup();
208
244
  } catch (error) {
209
245
  console.log(color('red', `\nāŒ Shell failed: ${error.message}`));
210
246
  process.exit(1);
package/file.txt ADDED
@@ -0,0 +1 @@
1
+ 'modified in container'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sandboxbox",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "description": "Portable container runner with Podman - Claude Code & Playwright support. Works on Windows, macOS, and Linux.",
5
5
  "type": "module",
6
6
  "main": "cli.js",
@@ -22,12 +22,11 @@ export function buildClaudeContainerCommand(projectPath, podmanPath, command = '
22
22
  const homeDir = process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME;
23
23
 
24
24
  return `${podmanPath} run --rm -it \
25
- -v "${projectPath}:/project:rw" \
25
+ -v "${projectPath}:/workspace:rw" \
26
26
  -v "${homeDir}/.claude:/root/.claude" \
27
27
  -v "${homeDir}/.ssh:/root/.ssh:ro" \
28
28
  -v "${homeDir}/.gitconfig:/root/.gitconfig:ro" \
29
29
  ${envArgs} \
30
- --env REPO_PATH=/project \
31
30
  --env HOME=/root \
32
31
  sandboxbox-local:latest \
33
32
  ${command}`;
@@ -46,32 +45,27 @@ WORKDIR /workspace
46
45
  # Install Claude Code
47
46
  RUN npm install -g @anthropic-ai/claude-code@latest
48
47
 
49
- # Create local workspace script
48
+ # Create isolated workspace script
50
49
  RUN echo '#!/bin/bash
51
50
  set -e
52
51
 
53
- REPO_PATH=\${REPO_PATH:-"/project"}
54
- WORKSPACE_DIR="/workspace/project"
52
+ echo "šŸš€ Starting SandboxBox with Claude Code in isolated environment..."
53
+ echo "šŸ“ Working directory: /workspace"
54
+ echo "šŸŽÆ This is an isolated copy of your repository"
55
55
 
56
- echo "šŸš€ Starting SandboxBox with Claude Code..."
57
- echo "šŸ“ Working with local repository: \$REPO_PATH"
58
- echo "šŸŽÆ Workspace: \$WORKSPACE_DIR"
59
-
60
- if [ -d "\$REPO_PATH" ] && [ -d "\$REPO_PATH/.git" ]; then
61
- echo "šŸ“‚ Creating workspace symlink to local repository..."
62
- ln -sf "\$REPO_PATH" "\$WORKSPACE_DIR"
63
- cd "\$WORKSPACE_DIR"
64
- echo "āœ… Workspace linked successfully!"
56
+ if [ -d "/workspace/.git" ]; then
57
+ echo "āœ… Git repository detected in workspace"
65
58
  echo "šŸ“‹ Current status:"
66
59
  git status
67
60
  echo ""
68
61
  echo "šŸ”§ Starting Claude Code..."
69
- echo "šŸ’” Changes will be saved directly to the local repository"
62
+ echo "šŸ’” Changes will be isolated and will NOT affect the original repository"
63
+ echo "šŸ“ To save changes, use git commands to commit and push before exiting"
70
64
  exec claude
71
65
  else
72
- echo "āŒ Error: \$REPO_PATH is not a valid git repository"
66
+ echo "āŒ Error: /workspace is not a valid git repository"
73
67
  exit 1
74
- fi' > /usr/local/bin/start-local-sandbox.sh && chmod +x /usr/local/bin/start-local-sandbox.sh
68
+ fi' > /usr/local/bin/start-isolated-sandbox.sh && chmod +x /usr/local/bin/start-isolated-sandbox.sh
75
69
 
76
- CMD ["/usr/local/bin/start-local-sandbox.sh"]`;
70
+ CMD ["/usr/local/bin/start-isolated-sandbox.sh"]`;
77
71
  }
@@ -0,0 +1,74 @@
1
+ import { mkdtempSync, rmSync } from 'fs';
2
+ import { tmpdir } from 'os';
3
+ import { join } from 'path';
4
+ import { execSync } from 'child_process';
5
+
6
+ /**
7
+ * Creates an isolated temporary environment for running commands
8
+ * @param {string} projectDir - Source project directory
9
+ * @returns {Object} - Contains tempDir, tempProjectDir, and cleanup function
10
+ */
11
+ export function createIsolatedEnvironment(projectDir) {
12
+ const tempDir = mkdtempSync(join(tmpdir(), 'sandboxbox-'));
13
+ const projectName = projectDir.split(/[\\\/]/).pop() || 'project';
14
+ const tempProjectDir = join(tempDir, projectName);
15
+
16
+ // Copy project to temporary directory (creates isolation)
17
+ // First create the directory (cross-platform)
18
+ if (process.platform === 'win32') {
19
+ execSync(`powershell -Command "New-Item -ItemType Directory -Path '${tempProjectDir}' -Force"`, {
20
+ stdio: 'pipe',
21
+ shell: true
22
+ });
23
+ } else {
24
+ execSync(`mkdir -p "${tempProjectDir}"`, {
25
+ stdio: 'pipe',
26
+ shell: true
27
+ });
28
+ }
29
+
30
+ if (process.platform === 'win32') {
31
+ // Windows approach - include hidden files like .git
32
+ execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force -Exclude 'node_modules'"`, {
33
+ stdio: 'pipe',
34
+ shell: true
35
+ });
36
+ // Also copy hidden files separately
37
+ execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
38
+ stdio: 'pipe',
39
+ shell: true
40
+ });
41
+ } else {
42
+ // Unix approach - include hidden files
43
+ execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
44
+ stdio: 'pipe',
45
+ shell: true
46
+ });
47
+ execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
48
+ stdio: 'pipe',
49
+ shell: true
50
+ });
51
+ }
52
+
53
+ // Ensure cleanup on exit
54
+ const cleanup = () => {
55
+ try {
56
+ rmSync(tempDir, { recursive: true, force: true });
57
+ } catch (cleanupError) {
58
+ // Ignore cleanup errors
59
+ }
60
+ };
61
+
62
+ return { tempDir, tempProjectDir, cleanup };
63
+ }
64
+
65
+ /**
66
+ * Sets up cleanup handlers for process signals
67
+ * @param {Function} cleanup - Cleanup function to call
68
+ */
69
+ export function setupCleanupHandlers(cleanup) {
70
+ // Set up cleanup handlers
71
+ process.on('exit', cleanup);
72
+ process.on('SIGINT', () => { cleanup(); process.exit(130); });
73
+ process.on('SIGTERM', () => { cleanup(); process.exit(143); });
74
+ }