sandboxbox 2.0.8 ā 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 +25 -39
- package/cli.js +28 -86
- package/package.json +1 -1
- package/utils/claude-workspace.js +12 -18
- package/utils/isolation.js +74 -0
package/CLAUDE.md
CHANGED
@@ -134,51 +134,37 @@ exec claude # Changes save directly to local repo
|
|
134
134
|
|
135
135
|
## Command Isolation Principles
|
136
136
|
|
137
|
-
###
|
137
|
+
### Unified Architecture - All Commands Use Isolation
|
138
138
|
- **`run` command**: Creates isolated temporary environment - changes DO NOT affect host
|
139
|
-
- **`claude` command**:
|
139
|
+
- **`claude` command**: Creates isolated temporary environment - changes DO NOT affect host
|
140
140
|
- **`shell` command**: Creates isolated temporary environment - changes DO NOT affect host
|
141
141
|
|
142
|
-
### Isolation
|
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)
|
143
150
|
```javascript
|
144
|
-
//
|
145
|
-
|
146
|
-
const tempProjectDir = join(tempDir, projectName);
|
147
|
-
|
148
|
-
// Cross-platform file copying with hidden files (.git, etc.)
|
149
|
-
if (process.platform === 'win32') {
|
150
|
-
execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force"`, {
|
151
|
-
stdio: 'pipe',
|
152
|
-
shell: true
|
153
|
-
});
|
154
|
-
// Copy hidden files separately
|
155
|
-
execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
|
156
|
-
stdio: 'pipe',
|
157
|
-
shell: true
|
158
|
-
});
|
159
|
-
} else {
|
160
|
-
execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
|
161
|
-
stdio: 'pipe',
|
162
|
-
shell: true
|
163
|
-
});
|
164
|
-
execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
|
165
|
-
stdio: 'pipe',
|
166
|
-
shell: true
|
167
|
-
});
|
168
|
-
}
|
151
|
+
// All commands use the same isolation pattern
|
152
|
+
import { createIsolatedEnvironment, setupCleanupHandlers } from './utils/isolation.js';
|
169
153
|
|
170
|
-
//
|
171
|
-
const cleanup = ()
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
}
|
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
|
+
});
|
178
165
|
|
179
|
-
|
180
|
-
|
181
|
-
process.on('SIGTERM', () => { cleanup(); process.exit(143); });
|
166
|
+
// Clean up on completion
|
167
|
+
cleanup();
|
182
168
|
```
|
183
169
|
|
184
170
|
### Container Naming and Cleanup
|
package/cli.js
CHANGED
@@ -7,15 +7,15 @@
|
|
7
7
|
* Works on Windows, macOS, and Linux
|
8
8
|
*/
|
9
9
|
|
10
|
-
import { readFileSync, existsSync, writeFileSync
|
10
|
+
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
11
11
|
import { execSync } from 'child_process';
|
12
|
-
import { resolve, dirname
|
12
|
+
import { resolve, dirname } from 'path';
|
13
13
|
import { fileURLToPath } from 'url';
|
14
|
-
import { tmpdir } from 'os';
|
15
14
|
|
16
15
|
import { color } from './utils/colors.js';
|
17
16
|
import { checkPodman, getPodmanPath } from './utils/podman.js';
|
18
17
|
import { buildClaudeContainerCommand, createClaudeDockerfile } from './utils/claude-workspace.js';
|
18
|
+
import { createIsolatedEnvironment, setupCleanupHandlers } from './utils/isolation.js';
|
19
19
|
|
20
20
|
const __filename = fileURLToPath(import.meta.url);
|
21
21
|
const __dirname = dirname(__filename);
|
@@ -86,20 +86,32 @@ function runClaudeWorkspace(projectDir, command = 'claude') {
|
|
86
86
|
return false;
|
87
87
|
}
|
88
88
|
|
89
|
-
console.log(color('blue', 'š Starting Claude Code
|
89
|
+
console.log(color('blue', 'š Starting Claude Code in isolated environment...'));
|
90
90
|
console.log(color('yellow', `Project: ${projectDir}`));
|
91
|
-
console.log(color('yellow', `Command: ${command}
|
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'));
|
92
93
|
|
93
94
|
const podmanPath = checkPodman();
|
94
95
|
if (!podmanPath) return false;
|
95
96
|
|
96
97
|
try {
|
97
|
-
|
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);
|
98
106
|
execSync(containerCommand, {
|
99
107
|
stdio: 'inherit',
|
100
108
|
shell: process.platform === 'win32'
|
101
109
|
});
|
102
|
-
|
110
|
+
|
111
|
+
// Clean up the temporary directory
|
112
|
+
cleanup();
|
113
|
+
|
114
|
+
console.log(color('green', '\nā
Claude Code session completed! (Isolated - no host changes)'));
|
103
115
|
return true;
|
104
116
|
} catch (error) {
|
105
117
|
console.log(color('red', `\nā Claude Code failed: ${error.message}`));
|
@@ -171,54 +183,11 @@ async function main() {
|
|
171
183
|
if (!runPodman) process.exit(1);
|
172
184
|
|
173
185
|
try {
|
174
|
-
// Create
|
175
|
-
const
|
176
|
-
const projectName = projectDir.split(/[\\\/]/).pop() || 'project';
|
177
|
-
const tempProjectDir = join(tempDir, projectName);
|
178
|
-
|
179
|
-
// Copy project to temporary directory (creates isolation)
|
180
|
-
// First create the directory
|
181
|
-
execSync(`mkdir -p "${tempProjectDir}"`, {
|
182
|
-
stdio: 'pipe',
|
183
|
-
shell: true
|
184
|
-
});
|
185
|
-
|
186
|
-
if (process.platform === 'win32') {
|
187
|
-
// Windows approach - include hidden files like .git
|
188
|
-
execSync(`powershell -Command "Copy-Item -Path '${projectDir}\\*' -Destination '${tempProjectDir}' -Recurse -Force -Exclude 'node_modules'"`, {
|
189
|
-
stdio: 'pipe',
|
190
|
-
shell: true
|
191
|
-
});
|
192
|
-
// Also copy hidden files separately
|
193
|
-
execSync(`powershell -Command "Get-ChildItem -Path '${projectDir}' -Force -Name | Where-Object { $_ -like '.*' } | ForEach-Object { Copy-Item -Path (Join-Path '${projectDir}' $_) -Destination '${tempProjectDir}' -Recurse -Force }"`, {
|
194
|
-
stdio: 'pipe',
|
195
|
-
shell: true
|
196
|
-
});
|
197
|
-
} else {
|
198
|
-
// Unix approach - include hidden files
|
199
|
-
execSync(`cp -r "${projectDir}"/.* "${tempProjectDir}/" 2>/dev/null || true`, {
|
200
|
-
stdio: 'pipe',
|
201
|
-
shell: true
|
202
|
-
});
|
203
|
-
execSync(`cp -r "${projectDir}"/* "${tempProjectDir}/"`, {
|
204
|
-
stdio: 'pipe',
|
205
|
-
shell: true
|
206
|
-
});
|
207
|
-
}
|
208
|
-
|
209
|
-
// Ensure cleanup on exit
|
210
|
-
const cleanup = () => {
|
211
|
-
try {
|
212
|
-
rmSync(tempDir, { recursive: true, force: true });
|
213
|
-
} catch (cleanupError) {
|
214
|
-
// Ignore cleanup errors
|
215
|
-
}
|
216
|
-
};
|
186
|
+
// Create isolated environment
|
187
|
+
const { tempProjectDir, cleanup } = createIsolatedEnvironment(projectDir);
|
217
188
|
|
218
189
|
// Set up cleanup handlers
|
219
|
-
|
220
|
-
process.on('SIGINT', () => { cleanup(); process.exit(130); });
|
221
|
-
process.on('SIGTERM', () => { cleanup(); process.exit(143); });
|
190
|
+
setupCleanupHandlers(cleanup);
|
222
191
|
|
223
192
|
// Run the command in isolated container with temporary directory
|
224
193
|
execSync(`"${runPodman}" run --rm -it -v "${tempProjectDir}:/workspace:rw" -w /workspace sandboxbox:latest ${cmd}`, {
|
@@ -258,46 +227,19 @@ async function main() {
|
|
258
227
|
if (!shellPodman) process.exit(1);
|
259
228
|
|
260
229
|
try {
|
261
|
-
// Create
|
262
|
-
const
|
263
|
-
|
264
|
-
// Ensure cleanup on exit
|
265
|
-
const cleanup = () => {
|
266
|
-
try {
|
267
|
-
execSync(`"${shellPodman}" rm -f "${tempShellContainerName}"`, {
|
268
|
-
stdio: 'pipe',
|
269
|
-
shell: process.platform === 'win32'
|
270
|
-
});
|
271
|
-
} catch (cleanupError) {
|
272
|
-
// Ignore cleanup errors
|
273
|
-
}
|
274
|
-
};
|
230
|
+
// Create isolated environment
|
231
|
+
const { tempProjectDir, cleanup } = createIsolatedEnvironment(shellProjectDir);
|
275
232
|
|
276
233
|
// Set up cleanup handlers
|
277
|
-
|
278
|
-
process.on('SIGINT', () => { cleanup(); process.exit(130); });
|
279
|
-
process.on('SIGTERM', () => { cleanup(); process.exit(143); });
|
234
|
+
setupCleanupHandlers(cleanup);
|
280
235
|
|
281
|
-
//
|
282
|
-
execSync(`"${shellPodman}"
|
283
|
-
stdio: 'pipe',
|
284
|
-
shell: process.platform === 'win32'
|
285
|
-
});
|
286
|
-
|
287
|
-
// Copy project files into isolated container - use proper path handling
|
288
|
-
const normalizedShellProjectDir = shellProjectDir.replace(/\\/g, '/');
|
289
|
-
execSync(`"${shellPodman}" cp "${normalizedShellProjectDir}" "${tempShellContainerName}:/workspace"`, {
|
290
|
-
stdio: 'pipe',
|
291
|
-
shell: process.platform === 'win32'
|
292
|
-
});
|
293
|
-
|
294
|
-
// Start interactive shell in the isolated container
|
295
|
-
execSync(`"${shellPodman}" start -i "${tempShellContainerName}"`, {
|
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`, {
|
296
238
|
stdio: 'inherit',
|
297
239
|
shell: process.platform === 'win32'
|
298
240
|
});
|
299
241
|
|
300
|
-
// Clean up the temporary
|
242
|
+
// Clean up the temporary directory
|
301
243
|
cleanup();
|
302
244
|
} catch (error) {
|
303
245
|
console.log(color('red', `\nā Shell failed: ${error.message}`));
|
package/package.json
CHANGED
@@ -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}:/
|
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
|
48
|
+
# Create isolated workspace script
|
50
49
|
RUN echo '#!/bin/bash
|
51
50
|
set -e
|
52
51
|
|
53
|
-
|
54
|
-
|
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
|
-
|
57
|
-
echo "
|
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
|
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:
|
66
|
+
echo "ā Error: /workspace is not a valid git repository"
|
73
67
|
exit 1
|
74
|
-
fi' > /usr/local/bin/start-
|
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-
|
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
|
+
}
|