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 +54 -1
- package/Dockerfile.simple +18 -0
- package/cli.js +45 -9
- package/file.txt +1 -0
- package/package.json +1 -1
- package/utils/claude-workspace.js +12 -18
- package/utils/isolation.js +74 -0
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
|
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}
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
@@ -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
|
+
}
|