Speedofmastery commited on
Commit
743b049
·
1 Parent(s): 54c9eed

Deploy Docker sandbox with isolated container execution

Browse files
Files changed (3) hide show
  1. Dockerfile +50 -0
  2. app.py +330 -0
  3. requirements.txt +5 -0
Dockerfile ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-Language Code Execution Sandbox - Secure Docker Container
2
+ FROM python:3.11-slim
3
+
4
+ # Install system dependencies for multiple languages and security tools
5
+ RUN apt-get update && apt-get install -y \
6
+ # Node.js & npm for React/JavaScript
7
+ nodejs npm \
8
+ # Build tools
9
+ build-essential \
10
+ gcc g++ \
11
+ # Security: Run as non-root user
12
+ sudo \
13
+ # Cleanup
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # Create non-root user for sandbox execution
17
+ RUN useradd -m -u 1000 -s /bin/bash sandbox && \
18
+ echo "sandbox ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
19
+
20
+ # Set working directory
21
+ WORKDIR /app
22
+
23
+ # Copy requirements and install Python dependencies
24
+ COPY requirements_docker.txt /app/requirements.txt
25
+ RUN pip install --no-cache-dir -r requirements.txt
26
+
27
+ # Install React dependencies globally
28
+ RUN npm install -g react react-dom
29
+
30
+ # Copy application code
31
+ COPY app_docker.py /app/app.py
32
+ COPY sandbox_executor.py /app/
33
+
34
+ # Create execution directory with restricted permissions
35
+ RUN mkdir -p /sandbox && \
36
+ chown sandbox:sandbox /sandbox && \
37
+ chmod 755 /sandbox
38
+
39
+ # Switch to non-root user
40
+ USER sandbox
41
+
42
+ # Expose port 7860
43
+ EXPOSE 7860
44
+
45
+ # Health check
46
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
47
+ CMD python -c "import requests; requests.get('http://localhost:7860/health')" || exit 1
48
+
49
+ # Run the application
50
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Docker-Based Sandbox Executor for Hugging Face Spaces
3
+ Secure code execution using Docker containers
4
+ """
5
+ from fastapi import FastAPI, HTTPException
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from pydantic import BaseModel
8
+ import subprocess
9
+ import tempfile
10
+ import os
11
+ import time
12
+ import base64
13
+ from typing import Optional
14
+ import docker
15
+ from pathlib import Path
16
+
17
+ app = FastAPI(title="Docker Sandbox Executor")
18
+
19
+ # Enable CORS
20
+ app.add_middleware(
21
+ CORSMiddleware,
22
+ allow_origins=["*"],
23
+ allow_credentials=True,
24
+ allow_methods=["*"],
25
+ allow_headers=["*"],
26
+ )
27
+
28
+ # Initialize Docker client
29
+ try:
30
+ docker_client = docker.from_env()
31
+ DOCKER_AVAILABLE = True
32
+ print("✓ Docker client initialized successfully")
33
+ except Exception as e:
34
+ DOCKER_AVAILABLE = False
35
+ print(f"⚠️ Docker not available: {e}")
36
+
37
+ class CodeExecutionRequest(BaseModel):
38
+ language: str
39
+ code: str
40
+ timeout: Optional[int] = 30
41
+
42
+ class SandboxDockerExecutor:
43
+ """Execute code in isolated Docker containers"""
44
+
45
+ def __init__(self):
46
+ self.execution_count = 0
47
+
48
+ def execute_in_docker(self, language: str, code: str, timeout: int = 30):
49
+ """Execute code in Docker container"""
50
+ self.execution_count += 1
51
+ start_time = time.time()
52
+
53
+ try:
54
+ if language.lower() == "python":
55
+ return self._execute_python_docker(code, timeout)
56
+ elif language.lower() == "react":
57
+ return self._execute_react_docker(code, timeout)
58
+ elif language.lower() == "html":
59
+ return self._execute_html_docker(code, timeout)
60
+ elif language.lower() == "javascript":
61
+ return self._execute_javascript_docker(code, timeout)
62
+ else:
63
+ return False, "", f"❌ Unsupported language: {language}"
64
+
65
+ except Exception as e:
66
+ return False, "", f"❌ Docker execution error: {str(e)}"
67
+
68
+ def _execute_python_docker(self, code: str, timeout: int):
69
+ """Execute Python in Docker"""
70
+ container = None
71
+ try:
72
+ # Create container with resource limits
73
+ container = docker_client.containers.run(
74
+ "python:3.11-alpine",
75
+ f"python -c '{code}'",
76
+ detach=True,
77
+ mem_limit="256m",
78
+ cpu_period=100000,
79
+ cpu_quota=50000, # 50% CPU
80
+ network_mode="none", # No network access
81
+ remove=True
82
+ )
83
+
84
+ # Wait for completion with timeout
85
+ result = container.wait(timeout=timeout)
86
+ logs = container.logs().decode('utf-8')
87
+
88
+ success = result['StatusCode'] == 0
89
+ return success, logs, "" if success else logs
90
+
91
+ except docker.errors.ContainerError as e:
92
+ return False, "", str(e)
93
+ except Exception as e:
94
+ return False, "", f"Container error: {str(e)}"
95
+ finally:
96
+ if container:
97
+ try:
98
+ container.remove(force=True)
99
+ except:
100
+ pass
101
+
102
+ def _execute_react_docker(self, code: str, timeout: int):
103
+ """Execute React (generate HTML for client-side rendering)"""
104
+ html_content = f"""<!DOCTYPE html>
105
+ <html lang="en">
106
+ <head>
107
+ <meta charset="UTF-8">
108
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
109
+ <title>React Sandbox #{self.execution_count}</title>
110
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
111
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
112
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
113
+ <style>
114
+ body {{
115
+ margin: 0;
116
+ padding: 0;
117
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
118
+ }}
119
+ #root {{
120
+ width: 100%;
121
+ min-height: 100vh;
122
+ }}
123
+ </style>
124
+ </head>
125
+ <body>
126
+ <div id="root"></div>
127
+ <script type="text/babel">
128
+ {code}
129
+ </script>
130
+ </body>
131
+ </html>"""
132
+ return True, html_content, ""
133
+
134
+ def _execute_html_docker(self, code: str, timeout: int):
135
+ """Execute HTML"""
136
+ if not code.strip().startswith('<!DOCTYPE') and not code.strip().startswith('<html'):
137
+ code = f"<!DOCTYPE html>\n{code}"
138
+ return True, code, ""
139
+
140
+ def _execute_javascript_docker(self, code: str, timeout: int):
141
+ """Execute JavaScript in Docker"""
142
+ container = None
143
+ try:
144
+ container = docker_client.containers.run(
145
+ "node:18-alpine",
146
+ f"node -e '{code}'",
147
+ detach=True,
148
+ mem_limit="256m",
149
+ cpu_period=100000,
150
+ cpu_quota=50000,
151
+ network_mode="none",
152
+ remove=True
153
+ )
154
+
155
+ result = container.wait(timeout=timeout)
156
+ logs = container.logs().decode('utf-8')
157
+
158
+ success = result['StatusCode'] == 0
159
+ return success, logs, "" if success else logs
160
+
161
+ except Exception as e:
162
+ return False, "", f"Container error: {str(e)}"
163
+ finally:
164
+ if container:
165
+ try:
166
+ container.remove(force=True)
167
+ except:
168
+ pass
169
+
170
+ # Global executor
171
+ executor = SandboxDockerExecutor()
172
+
173
+ def format_terminal_html(language: str, success: bool, stdout: str, stderr: str,
174
+ execution_time: float, exec_id: int) -> str:
175
+ """Format terminal output as HTML"""
176
+ import html as html_module
177
+
178
+ status_color = "#00ff00" if success else "#ff4444"
179
+ status_text = "✓ SUCCESS" if success else "✗ FAILED"
180
+
181
+ stdout_escaped = html_module.escape(stdout) if stdout else ""
182
+ stderr_escaped = html_module.escape(stderr) if stderr else ""
183
+
184
+ return f"""<!DOCTYPE html>
185
+ <html>
186
+ <head>
187
+ <meta charset="UTF-8">
188
+ <title>🐳 Docker Sandbox Terminal #{exec_id}</title>
189
+ <style>
190
+ * {{ margin: 0; padding: 0; box-sizing: border-box; }}
191
+ body {{
192
+ background: #000;
193
+ color: #00ff00;
194
+ font-family: 'Courier New', monospace;
195
+ padding: 20px;
196
+ line-height: 1.6;
197
+ }}
198
+ .header {{
199
+ border-bottom: 2px solid #00ff00;
200
+ padding-bottom: 15px;
201
+ margin-bottom: 20px;
202
+ }}
203
+ h1 {{ color: #00ff00; font-size: 24px; margin-bottom: 10px; }}
204
+ .info {{
205
+ display: grid;
206
+ grid-template-columns: 150px 1fr;
207
+ gap: 10px;
208
+ margin-bottom: 20px;
209
+ font-size: 14px;
210
+ }}
211
+ .info-label {{ color: #888; }}
212
+ .info-value {{ color: #00ff00; }}
213
+ .status {{ color: {status_color}; font-weight: bold; font-size: 18px; }}
214
+ .output-section {{ margin: 20px 0; }}
215
+ .output-title {{
216
+ color: #00aaff;
217
+ font-size: 16px;
218
+ margin-bottom: 10px;
219
+ border-bottom: 1px solid #00aaff;
220
+ padding-bottom: 5px;
221
+ }}
222
+ .output-content {{
223
+ background: #111;
224
+ padding: 15px;
225
+ border-left: 3px solid #00ff00;
226
+ white-space: pre-wrap;
227
+ word-wrap: break-word;
228
+ max-height: 400px;
229
+ overflow-y: auto;
230
+ }}
231
+ .stderr {{ color: #ff6666; border-left-color: #ff4444; }}
232
+ .empty {{ color: #666; font-style: italic; }}
233
+ ::-webkit-scrollbar {{ width: 10px; }}
234
+ ::-webkit-scrollbar-track {{ background: #111; }}
235
+ ::-webkit-scrollbar-thumb {{ background: #00ff00; border-radius: 5px; }}
236
+ </style>
237
+ </head>
238
+ <body>
239
+ <div class="header">
240
+ <h1>🐳 DOCKER SANDBOX TERMINAL</h1>
241
+ </div>
242
+ <div class="info">
243
+ <div class="info-label">Execution ID:</div>
244
+ <div class="info-value">#{exec_id}</div>
245
+ <div class="info-label">Language:</div>
246
+ <div class="info-value">{language.upper()}</div>
247
+ <div class="info-label">Execution Time:</div>
248
+ <div class="info-value">{execution_time:.3f}s</div>
249
+ <div class="info-label">Status:</div>
250
+ <div class="status">{status_text}</div>
251
+ </div>
252
+ <div class="output-section">
253
+ <div class="output-title">📤 STDOUT</div>
254
+ <div class="output-content {'empty' if not stdout_escaped else ''}">
255
+ {stdout_escaped if stdout_escaped else '(no output)'}
256
+ </div>
257
+ </div>
258
+ {f'<div class="output-section"><div class="output-title">⚠️ STDERR</div><div class="output-content stderr">{stderr_escaped}</div></div>' if stderr_escaped else ''}
259
+ </body>
260
+ </html>"""
261
+
262
+ @app.post("/execute")
263
+ async def execute_code(request: CodeExecutionRequest):
264
+ """Execute code in Docker sandbox"""
265
+
266
+ if not DOCKER_AVAILABLE:
267
+ raise HTTPException(status_code=503, detail="Docker not available in this environment")
268
+
269
+ start_time = time.time()
270
+
271
+ # Execute in Docker
272
+ success, stdout, stderr = executor.execute_in_docker(
273
+ request.language,
274
+ request.code,
275
+ request.timeout
276
+ )
277
+
278
+ execution_time = time.time() - start_time
279
+
280
+ # Generate terminal view
281
+ terminal_html = format_terminal_html(
282
+ request.language,
283
+ success,
284
+ stdout,
285
+ stderr,
286
+ execution_time,
287
+ executor.execution_count
288
+ )
289
+
290
+ # Encode as data URLs
291
+ terminal_b64 = base64.b64encode(terminal_html.encode('utf-8')).decode('utf-8')
292
+ terminal_url = f"data:text/html;base64,{terminal_b64}"
293
+
294
+ # For HTML/React, use stdout as preview
295
+ preview_url = None
296
+ if request.language.lower() in ["html", "react"]:
297
+ preview_b64 = base64.b64encode(stdout.encode('utf-8')).decode('utf-8')
298
+ preview_url = f"data:text/html;base64,{preview_b64}"
299
+
300
+ return {
301
+ "status": "success" if success else "error",
302
+ "preview_url": preview_url,
303
+ "terminal_view_url": terminal_url,
304
+ "execution_time": execution_time,
305
+ "docker_enabled": True
306
+ }
307
+
308
+ @app.get("/health")
309
+ async def health_check():
310
+ """Health check endpoint"""
311
+ return {
312
+ "status": "healthy",
313
+ "docker_available": DOCKER_AVAILABLE
314
+ }
315
+
316
+ @app.get("/")
317
+ async def root():
318
+ """Root endpoint"""
319
+ return {
320
+ "message": "🐳 Docker Sandbox Executor API",
321
+ "docker_enabled": DOCKER_AVAILABLE,
322
+ "endpoints": {
323
+ "/execute": "POST - Execute code in Docker sandbox",
324
+ "/health": "GET - Health check"
325
+ }
326
+ }
327
+
328
+ if __name__ == "__main__":
329
+ import uvicorn
330
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ docker
4
+ pydantic
5
+ python-multipart