-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsandbox-execute.sh
More file actions
189 lines (159 loc) · 6.91 KB
/
Copy pathsandbox-execute.sh
File metadata and controls
189 lines (159 loc) · 6.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/bin/bash
# Isolated Sandbox Command Executor for Agentic Sub-agents
# FIDUSGATE_ALLOW_HOST_FALLBACK defaults to false to enforce a fail-closed security posture.
# Set FIDUSGATE_ALLOW_HOST_FALLBACK=true in the calling environment for local dev without Docker.
export FIDUSGATE_ALLOW_HOST_FALLBACK="${FIDUSGATE_ALLOW_HOST_FALLBACK:-false}"
# Author: Antigravity Code Assistant
# macOS timeout utility fallback
if ! command -v timeout &> /dev/null; then
if command -v gtimeout &> /dev/null; then
timeout() { gtimeout "$@"; }
else
timeout() {
# Fallback to direct execution by shifting past the timeout limit argument
# which is the first argument in the standard 'timeout <limit> <cmd...>' call structure
local limit="$1"
shift
"$@"
}
fi
fi
COMMAND="$1"
MOUNT_DIR="$2"
SUBAGENT_ID="$3"
if [ -z "$COMMAND" ] || [ -z "$MOUNT_DIR" ]; then
echo "❌ Error: Missing parameters!"
echo "Usage: sandbox-execute.sh \"<command>\" \"<absolute_path_to_mount_dir>\""
exit 1
fi
if [ ! -d "$MOUNT_DIR" ]; then
echo "❌ Error: Mount directory does not exist!"
exit 1
fi
# Proactively ensure the memory folder exists on the host
if [ -n "$SUBAGENT_ID" ]; then
WORKSPACE_MEMORY_DIR="$MOUNT_DIR/.memory/subagents/$SUBAGENT_ID"
else
WORKSPACE_MEMORY_DIR="$MOUNT_DIR/.memory"
fi
mkdir -p "$WORKSPACE_MEMORY_DIR"
echo "🛡️ Initializing isolated Docker sandbox for [$MOUNT_DIR]..."
# Detect container runtime and daemon status
if ! command -v docker >/dev/null 2>&1 || ! docker info >/dev/null 2>&1; then
if [ "$FIDUSGATE_ALLOW_HOST_FALLBACK" = "true" ]; then
echo "⚠️ Docker not found or daemon not running. Falling back to host execution bypass..."
# Subprocess shell — do not eval in this process (extra expansion / current-shell side effects).
bash -c "$COMMAND"
exit $?
else
echo "❌ Error: Docker sandbox is unavailable and host execution fallback is disabled."
echo "To allow un-sandboxed execution for local development, run with FIDUSGATE_ALLOW_HOST_FALLBACK=true"
exit 127
fi
fi
# Determine the optimal Docker image based on the command content
# Check if fidusgate-sandbox-node exists, compile if not
if ! docker image inspect fidusgate-sandbox-node:latest >/dev/null 2>&1; then
echo "🐳 Compiling standardized fidusgate-sandbox-node:latest environment..."
docker build -t fidusgate-sandbox-node:latest -f scripts/sandbox/Dockerfile scripts/sandbox >/dev/null
fi
IMAGE="fidusgate-sandbox-node:latest"
RUN_USER=""
# Convert command to lowercase for matching
LOWER_CMD=$(echo "$COMMAND" | tr '[:upper:]' '[:lower:]')
if [[ "$LOWER_CMD" == *"pytest"* ]] || [[ "$LOWER_CMD" == *"python"* ]]; then
IMAGE="python:3.13-alpine"
elif [[ "$LOWER_CMD" == *"go test"* ]] || [[ "$LOWER_CMD" == *"go "* ]]; then
IMAGE="golang:alpine"
elif [[ "$LOWER_CMD" == *"cargo "* ]]; then
IMAGE="rust:alpine"
fi
echo "🐳 Selected Docker sandbox image: $IMAGE"
# Verify image is present locally, pull if not
if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
echo "🐳 Pulling Docker image $IMAGE (this might take a few moments)..."
docker pull "$IMAGE" >/dev/null
fi
# Generate temporary patch file name
TEMP_PATCH="/tmp/agent-diff-$(date +%s).patch"
CONTAINER_NAME="agent-sandbox-$(date +%s)"
# Recommendation #4: MicroVM Sandbox Isolation (gVisor runsc detection)
RUNTIME_FLAG=""
if docker info 2>/dev/null | grep -qi "runsc"; then
echo "🛡️ gVisor (runsc) secure runtime detected! Enforcing microVM guest-kernel isolation."
RUNTIME_FLAG="--runtime runsc"
else
echo "⚠️ gVisor (runsc) runtime not registered with Docker. Falling back to standard container namespaces."
fi
# Parse resource constraints from environment or default safely
CPU_LIMIT=${SANDBOX_CPU_LIMIT:-"1"}
MEM_LIMIT=${SANDBOX_MEM_LIMIT:-"2g"}
TIMEOUT_LIMIT=${SANDBOX_TIMEOUT:-"300"}
echo "🔒 Enforcing resource limits: CPU=$CPU_LIMIT, RAM=$MEM_LIMIT, Hard-Timeout=${TIMEOUT_LIMIT}s"
# Clear any previous pending patches
rm -f "$WORKSPACE_MEMORY_DIR/pending-sandbox.patch"
# Run compilation/tests inside isolated container
# Mounts primary workspace read-only, mounts workspace-memory read-write
USER_FLAG=""
if [ -n "$RUN_USER" ]; then
USER_FLAG="--user $RUN_USER"
fi
timeout "$TIMEOUT_LIMIT" docker run --name "$CONTAINER_NAME" \
$RUNTIME_FLAG \
--cpus="$CPU_LIMIT" \
--memory="$MEM_LIMIT" \
-v "$MOUNT_DIR:/workspace:ro" \
-v "$WORKSPACE_MEMORY_DIR:/workspace-memory:rw" \
--network none \
--cap-drop=ALL \
$USER_FLAG \
"$IMAGE" \
bash -c "
echo '🛡️ Preparing ephemeral copy-on-write workspace...' && \
mkdir -p /app && \
tar -cf - --exclude=node_modules --exclude=.git --exclude=.turbo --exclude=dist -C /workspace . | tar -xf - -C /app && \
find /workspace -maxdepth 3 -type d -name node_modules -prune 2>/dev/null | while read -r dir; do \
rel=\${dir#/workspace/}; \
mkdir -p \"/app/\${rel%/*}\" 2>/dev/null; \
ln -s \"\$dir\" \"/app/\$rel\" 2>/dev/null; \
done && \
cd /app && \
echo '🚀 Executing command in sandboxed environment...' && \
$COMMAND; \
EXIT_VAL=\$?; \
if [ \$EXIT_VAL -eq 0 ]; then \
if ! diff --help 2>&1 | grep -q exclude; then \
echo '🛡️ Installing GNU diffutils in container...' && \
if command -v apk >/dev/null 2>&1; then apk add --no-cache diffutils >/dev/null 2>&1; \
elif command -v apt-get >/dev/null 2>&1; then apt-get update -y >/dev/null 2>&1 && apt-get install -y diffutils >/dev/null 2>&1; fi; \
fi; \
echo '🔍 Generating git diff patch...' && \
diff -ruN --exclude=node_modules --exclude=.git --exclude=.turbo --exclude=dist /workspace /app | sed 's|^--- /workspace/|--- a/|; s|^+++ /app/|+++ b/|' > /workspace-memory/pending-sandbox.patch; \
fi; \
exit \$EXIT_VAL
"
EXIT_CODE=$?
# Intercept hard timeout kills (UNIX timeout tool returns exit code 124 on SIGTERM timeout)
if [ $EXIT_CODE -eq 124 ]; then
echo "❌ Error: Sandboxed command execution timed out after ${TIMEOUT_LIMIT}s! Forcefully terminating container."
docker kill "$CONTAINER_NAME" >/dev/null 2>&1
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1
exit 124
fi
# Check if sandbox succeeded and copy patch to output
if [ $EXIT_CODE -eq 0 ]; then
if [ -s "$WORKSPACE_MEMORY_DIR/pending-sandbox.patch" ]; then
echo "💾 Saved diff patch to: $WORKSPACE_MEMORY_DIR/pending-sandbox.patch"
# Also copy to temp directory for apply-patch.sh compatibility
cp "$WORKSPACE_MEMORY_DIR/pending-sandbox.patch" "$TEMP_PATCH"
echo "💾 Copied diff patch to: $TEMP_PATCH"
else
echo "ℹ️ No file modifications detected."
rm -f "$WORKSPACE_MEMORY_DIR/pending-sandbox.patch"
fi
else
echo "❌ Sandbox execution failed with exit code $EXIT_CODE. Workspace changes discarded."
fi
# Cleanup execution container
docker rm "$CONTAINER_NAME" > /dev/null 2>&1
exit $EXIT_CODE