qyro 2.0.0__py3-none-any.whl

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.
Files changed (45) hide show
  1. qyro/__init__.py +17 -0
  2. qyro/adapters/__init__.py +4 -0
  3. qyro/adapters/language_adapters/__init__.py +4 -0
  4. qyro/adapters/language_adapters/c/__init__.py +4 -0
  5. qyro/adapters/language_adapters/python/__init__.py +4 -0
  6. qyro/adapters/language_adapters/python/python_adapter.py +584 -0
  7. qyro/cli/__init__.py +8 -0
  8. qyro/cli/__main__.py +5 -0
  9. qyro/cli/cli.py +392 -0
  10. qyro/cli/interactive.py +297 -0
  11. qyro/common/__init__.py +37 -0
  12. qyro/common/animation.py +82 -0
  13. qyro/common/builder.py +434 -0
  14. qyro/common/compiler.py +895 -0
  15. qyro/common/config.py +93 -0
  16. qyro/common/constants.py +99 -0
  17. qyro/common/errors.py +176 -0
  18. qyro/common/frontend.py +74 -0
  19. qyro/common/health.py +358 -0
  20. qyro/common/kafka_manager.py +192 -0
  21. qyro/common/logging.py +149 -0
  22. qyro/common/memory.py +147 -0
  23. qyro/common/metrics.py +301 -0
  24. qyro/common/monitoring.py +468 -0
  25. qyro/common/parser.py +91 -0
  26. qyro/common/platform.py +609 -0
  27. qyro/common/redis_memory.py +1108 -0
  28. qyro/common/rpc.py +287 -0
  29. qyro/common/sandbox.py +191 -0
  30. qyro/common/schema_loader.py +33 -0
  31. qyro/common/secure_sandbox.py +490 -0
  32. qyro/common/toolchain_validator.py +617 -0
  33. qyro/common/type_generator.py +176 -0
  34. qyro/common/validation.py +401 -0
  35. qyro/common/validator.py +204 -0
  36. qyro/gateway/__init__.py +8 -0
  37. qyro/gateway/gateway.py +303 -0
  38. qyro/orchestrator/__init__.py +8 -0
  39. qyro/orchestrator/orchestrator.py +1223 -0
  40. qyro-2.0.0.dist-info/METADATA +244 -0
  41. qyro-2.0.0.dist-info/RECORD +45 -0
  42. qyro-2.0.0.dist-info/WHEEL +5 -0
  43. qyro-2.0.0.dist-info/entry_points.txt +2 -0
  44. qyro-2.0.0.dist-info/licenses/LICENSE +21 -0
  45. qyro-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,490 @@
1
+ """
2
+ Nexus Secure Sandbox System
3
+ Production-grade containerized execution for untrusted code using Docker isolation.
4
+ """
5
+
6
+ import docker
7
+ import tempfile
8
+ import os
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Dict, Any, Optional, List
12
+ import time
13
+ import signal
14
+ import subprocess
15
+ import hashlib
16
+ import secrets
17
+ from contextlib import contextmanager
18
+ import logging
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class SecureSandbox:
24
+ """
25
+ Production-grade secure execution environment for untrusted code using Docker containers.
26
+
27
+ Features:
28
+ - Isolated container execution with minimal privileges
29
+ - Strict resource limits (CPU, memory, disk)
30
+ - Complete network isolation
31
+ - File system restrictions with read-only mounts
32
+ - Execution timeout enforcement
33
+ - Comprehensive security policies
34
+ """
35
+
36
+ def __init__(self, max_memory: str = "128m", max_cpu: float = 0.5, timeout: int = 30):
37
+ try:
38
+ self.client = docker.from_env()
39
+ # Test connection
40
+ self.client.ping()
41
+ except Exception as e:
42
+ raise RuntimeError(f"Docker is required for sandboxing: {e}")
43
+
44
+ self.max_memory = max_memory
45
+ self.max_cpu = max_cpu
46
+ self.timeout = timeout
47
+
48
+ # Define supported languages and their security configurations
49
+ self.language_configs = {
50
+ 'python': {
51
+ 'image': 'python:3.11-alpine',
52
+ 'entrypoint': ['python'],
53
+ 'security_opts': ['no-new-privileges:true'],
54
+ 'readonly_rootfs': True
55
+ },
56
+ 'javascript': {
57
+ 'image': 'node:18-alpine',
58
+ 'entrypoint': ['node'],
59
+ 'security_opts': ['no-new-privileges:true'],
60
+ 'readonly_rootfs': True
61
+ },
62
+ 'go': {
63
+ 'image': 'golang:1.21-alpine',
64
+ 'entrypoint': ['sh', '-c'],
65
+ 'security_opts': ['no-new-privileges:true'],
66
+ 'readonly_rootfs': True
67
+ },
68
+ 'rust': {
69
+ 'image': 'rust:1.75-alpine',
70
+ 'entrypoint': ['sh', '-c'],
71
+ 'security_opts': ['no-new-privileges:true'],
72
+ 'readonly_rootfs': True
73
+ },
74
+ 'java': {
75
+ 'image': 'openjdk:17-jdk-alpine',
76
+ 'entrypoint': ['sh', '-c'],
77
+ 'security_opts': ['no-new-privileges:true'],
78
+ 'readonly_rootfs': True
79
+ },
80
+ 'c': {
81
+ 'image': 'gcc:11.4-alpine',
82
+ 'entrypoint': ['sh', '-c'],
83
+ 'security_opts': ['no-new-privileges:true'],
84
+ 'readonly_rootfs': True
85
+ }
86
+ }
87
+
88
+ def _validate_code(self, code: str, language: str) -> bool:
89
+ """
90
+ Validate code for dangerous patterns before execution.
91
+
92
+ Args:
93
+ code: Source code to validate
94
+ language: Programming language
95
+
96
+ Returns:
97
+ True if code is safe, False otherwise
98
+ """
99
+ # Dangerous patterns to detect
100
+ dangerous_patterns = [
101
+ r'\b(import|from)\s+os\b', # OS module imports
102
+ r'\b(import|from)\s+subprocess\b', # Subprocess imports
103
+ r'\b(import|from)\s+sys\b', # Sys module (can affect interpreter)
104
+ r'\bexec\b', # Dynamic code execution
105
+ r'\beval\b', # Dynamic code execution
106
+ r'\bcompile\b', # Dynamic code compilation
107
+ r'\bglobals\b', # Access to global namespace
108
+ r'\blocals\b', # Access to local namespace
109
+ r'\bopen\b', # File operations
110
+ r'\bfile\b', # File operations
111
+ r'\binput\b', # Interactive input
112
+ r'\braw_input\b', # Interactive input (Python 2)
113
+ r'\bprint\b', # Output operations (we'll redirect this)
114
+ r'\bsystem\b', # System calls
115
+ r'\bpopen\b', # Process creation
116
+ r'\bcall\b', # Process creation
117
+ r'\bcheck_call\b', # Process creation
118
+ r'\bcheck_output\b', # Process creation
119
+ r'\bsocket\b', # Network operations
120
+ r'\bconnect\b', # Network operations
121
+ r'\bbind\b', # Network operations
122
+ r'\blisten\b', # Network operations
123
+ r'\baccept\b', # Network operations
124
+ r'\bgetaddrinfo\b', # Network operations
125
+ r'\bgethostbyname\b', # Network operations
126
+ ]
127
+
128
+ # Language-specific patterns
129
+ if language == 'python':
130
+ dangerous_patterns.extend([
131
+ r'\b__import__\b', # Dynamic imports
132
+ r'\bgetattr\b', # Attribute access
133
+ r'\bsetattr\b', # Attribute modification
134
+ r'\bdelattr\b', # Attribute deletion
135
+ r'\bhasattr\b', # Attribute checking
136
+ ])
137
+ elif language == 'javascript':
138
+ dangerous_patterns.extend([
139
+ r'\brequire\(', # Module imports
140
+ r'\bprocess\.', # Process manipulation
141
+ r'\bchild_process\.', # Child process creation
142
+ r'\bfs\.', # File system operations
143
+ r'\brequire\(\s*["\']child_process["\']\s*\)', # Child process module
144
+ r'\brequire\(\s*["\']fs["\']\s*\)', # File system module
145
+ r'\brequire\(\s*["\']net["\']\s*\)', # Network module
146
+ r'\brequire\(\s*["\']dns["\']\s*\)', # DNS module
147
+ ])
148
+ elif language == 'java':
149
+ dangerous_patterns.extend([
150
+ r'Runtime\.getRuntime\(\)',
151
+ r'System\.exec\(',
152
+ r'ProcessBuilder\(',
153
+ r'new\s+File\(',
154
+ r'FileReader\(',
155
+ r'FileWriter\(',
156
+ r'RandomAccessFile\(',
157
+ r'new\s+Socket\(',
158
+ r'new\s+ServerSocket\(',
159
+ r'URL\(',
160
+ r'URLConnection\(',
161
+ ])
162
+ elif language == 'c':
163
+ dangerous_patterns.extend([
164
+ r'\bsystem\s*\(', # System calls
165
+ r'\bpopen\s*\(', # Process creation
166
+ r'\bexecve\s*\(', # Process execution
167
+ r'\bfork\s*\(', # Process creation
168
+ r'\bvfork\s*\(', # Process creation
169
+ r'\bopen\s*\(', # File operations
170
+ r'\bcreat\s*\(', # File creation
171
+ r'\baccess\s*\(', # File access checks
172
+ r'\bchmod\s*\(', # File permission changes
173
+ r'\bchown\s*\(', # File ownership changes
174
+ r'\bsocket\s*\(', # Socket creation
175
+ r'\bconnect\s*\(', # Socket connection
176
+ r'\bbind\s*\(', # Socket binding
177
+ r'\blisten\s*\(', # Socket listening
178
+ r'\baccept\s*\(', # Socket acceptance
179
+ ])
180
+
181
+ # Check for dangerous patterns
182
+ for pattern in dangerous_patterns:
183
+ import re
184
+ if re.search(pattern, code, re.IGNORECASE):
185
+ logger.warning(f"Dangerous pattern detected in {language} code: {pattern}")
186
+ return False
187
+
188
+ return True
189
+
190
+ def _sanitize_code(self, code: str, language: str) -> str:
191
+ """
192
+ Sanitize code to remove or neutralize potentially harmful constructs.
193
+
194
+ Args:
195
+ code: Source code to sanitize
196
+ language: Programming language
197
+
198
+ Returns:
199
+ Sanitized code
200
+ """
201
+ # For Python, wrap code in a restricted environment
202
+ if language == 'python':
203
+ # Create a safe execution wrapper
204
+ wrapper = '''
205
+ import sys
206
+ import io
207
+ from contextlib import redirect_stdout, redirect_stderr
208
+
209
+ # Restrict built-ins
210
+ safe_builtins = {{
211
+ 'abs', 'all', 'any', 'bool', 'chr', 'complex', 'dict', 'dir', 'divmod',
212
+ 'enumerate', 'filter', 'float', 'format', 'frozenset', 'hash', 'hex',
213
+ 'int', 'isinstance', 'issubclass', 'iter', 'len', 'list', 'map', 'max',
214
+ 'min', 'next', 'object', 'oct', 'ord', 'pow', 'range', 'repr', 'reversed',
215
+ 'round', 'set', 'slice', 'sorted', 'str', 'sum', 'super', 'tuple', 'type',
216
+ 'zip', 'True', 'False', 'None'
217
+ }}
218
+
219
+ # Create restricted globals
220
+ restricted_globals = {{
221
+ '__builtins__': {{k: __builtins__[k] for k in safe_builtins if k in __builtins__}},
222
+ '__name__': '__main__',
223
+ '__doc__': None,
224
+ }}
225
+
226
+ # Capture output
227
+ output_buffer = io.StringIO()
228
+ error_buffer = io.StringIO()
229
+
230
+ try:
231
+ with redirect_stdout(output_buffer), redirect_stderr(error_buffer):
232
+ exec("""{code}""", restricted_globals)
233
+
234
+ print("===OUTPUT===")
235
+ print(output_buffer.getvalue())
236
+ print("===ERRORS===")
237
+ print(error_buffer.getvalue())
238
+ except Exception as e:
239
+ print("===ERRORS===")
240
+ print(f"Execution error: {{e}}")
241
+ '''.format(code=code.replace('"', '\\"').replace('\n', '\\n'))
242
+ return wrapper
243
+
244
+ return code
245
+
246
+ @contextmanager
247
+ def _create_secure_temp_dir(self):
248
+ """Create a temporary directory with secure permissions."""
249
+ temp_dir = tempfile.mkdtemp(prefix="nexus_sandbox_")
250
+ try:
251
+ # Set restrictive permissions
252
+ os.chmod(temp_dir, 0o700) # Only owner can read/write/execute
253
+ yield Path(temp_dir)
254
+ finally:
255
+ # Clean up
256
+ import shutil
257
+ shutil.rmtree(temp_dir, ignore_errors=True)
258
+
259
+ def execute_untrusted_code(
260
+ self,
261
+ code: str,
262
+ language: str,
263
+ timeout: Optional[int] = None,
264
+ memory_limit: Optional[str] = None,
265
+ cpu_quota: Optional[float] = None
266
+ ) -> Dict[str, Any]:
267
+ """
268
+ Execute untrusted code in a secure Docker container.
269
+
270
+ Args:
271
+ code: Source code to execute
272
+ language: Programming language ('python', 'javascript', 'go', etc.)
273
+ timeout: Execution timeout in seconds (overrides default)
274
+ memory_limit: Memory limit (e.g., '128m', '1g') (overrides default)
275
+ cpu_quota: CPU quota as fraction of one CPU (overrides default)
276
+
277
+ Returns:
278
+ Dictionary with execution results
279
+ """
280
+ # Validate inputs
281
+ language = language.lower()
282
+ if language not in self.language_configs:
283
+ return {
284
+ 'success': False,
285
+ 'stdout': '',
286
+ 'stderr': '',
287
+ 'exit_code': -1,
288
+ 'error': f"Unsupported language for sandbox: {language}"
289
+ }
290
+
291
+ # Use provided values or defaults
292
+ timeout = timeout or self.timeout
293
+ memory_limit = memory_limit or self.max_memory
294
+ cpu_quota = cpu_quota or self.max_cpu
295
+
296
+ # Validate code for dangerous patterns
297
+ if not self._validate_code(code, language):
298
+ return {
299
+ 'success': False,
300
+ 'stdout': '',
301
+ 'stderr': '',
302
+ 'exit_code': -1,
303
+ 'error': 'Code contains dangerous patterns and was rejected'
304
+ }
305
+
306
+ # Sanitize code
307
+ sanitized_code = self._sanitize_code(code, language)
308
+
309
+ # Create temporary directory for code
310
+ with self._create_secure_temp_dir() as temp_dir:
311
+ temp_path = Path(temp_dir)
312
+
313
+ # Write code to temporary file based on language
314
+ file_extensions = {
315
+ 'python': '.py',
316
+ 'javascript': '.js',
317
+ 'go': '.go',
318
+ 'rust': '.rs',
319
+ 'java': '.java',
320
+ 'c': '.c',
321
+ }
322
+
323
+ ext = file_extensions[language]
324
+ code_file = temp_path / f"code{ext}"
325
+
326
+ # Write the sanitized code
327
+ with open(code_file, 'w', encoding='utf-8') as f:
328
+ f.write(sanitized_code)
329
+
330
+ # Determine execution command based on language
331
+ config = self.language_configs[language]
332
+ image = config['image']
333
+
334
+ # Create a unique container name to avoid conflicts
335
+ container_name = f"nexus_sandbox_{secrets.token_hex(8)}"
336
+
337
+ # Calculate CPU period and quota
338
+ cpu_period = 100000 # 100ms in microseconds
339
+ cpu_quota = int(cpu_quota * cpu_period) # Convert fraction to quota
340
+
341
+ try:
342
+ # Run container with security restrictions
343
+ container = self.client.containers.run(
344
+ image=image,
345
+ command=[code_file.name] if language != 'go' else ['sh', '-c', f'cd /code && go run {code_file.name}'],
346
+ volumes={str(temp_path): {'bind': '/code', 'mode': 'ro'}}, # Read-only mount
347
+ network_mode='none', # Complete network isolation
348
+ mem_limit=memory_limit,
349
+ cpu_period=cpu_period,
350
+ cpu_quota=cpu_quota,
351
+ security_opt=config['security_opts'],
352
+ readonly_rootfs=config['readonly_rootfs'],
353
+ environment={
354
+ 'HOME': '/tmp',
355
+ 'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
356
+ 'PYTHONUNBUFFERED': '1', # For Python
357
+ },
358
+ working_dir='/code',
359
+ remove=True, # Auto-remove when done
360
+ stdout=True,
361
+ stderr=True,
362
+ detach=False,
363
+ timeout=timeout,
364
+ user='nobody:nobody' if self._container_supports_user(image) else None, # Run as unprivileged user
365
+ cap_drop=['ALL'], # Drop all capabilities
366
+ privileged=False, # Never run privileged
367
+ )
368
+
369
+ # Parse output
370
+ stdout_raw = container[0] if container[0] else b""
371
+ stderr_raw = container[1] if container[1] else b""
372
+
373
+ stdout = stdout_raw.decode('utf-8', errors='replace') if stdout_raw else ""
374
+ stderr = stderr_raw.decode('utf-8', errors='replace') if stderr_raw else ""
375
+
376
+ # Extract actual output if wrapped (for Python)
377
+ if language == 'python':
378
+ # Extract output from our wrapper
379
+ if '===OUTPUT===' in stdout:
380
+ output_part = stdout.split('===OUTPUT===')[1]
381
+ if '===ERRORS===' in output_part:
382
+ actual_output = output_part.split('===ERRORS===')[0].strip()
383
+ else:
384
+ actual_output = output_part.strip()
385
+ stdout = actual_output
386
+
387
+ return {
388
+ 'success': True,
389
+ 'stdout': stdout,
390
+ 'stderr': stderr,
391
+ 'exit_code': 0 # Docker run returns combined output
392
+ }
393
+
394
+ except docker.errors.ContainerError as e:
395
+ return {
396
+ 'success': False,
397
+ 'stdout': e.stdout.decode('utf-8', errors='replace') if e.stdout else "",
398
+ 'stderr': e.stderr.decode('utf-8', errors='replace') if e.stderr else "",
399
+ 'exit_code': e.exit_code,
400
+ 'error': f"Container execution failed: {e}"
401
+ }
402
+ except docker.errors.ImageNotFound:
403
+ return {
404
+ 'success': False,
405
+ 'stdout': '',
406
+ 'stderr': '',
407
+ 'exit_code': -1,
408
+ 'error': f"Docker image not found: {image}"
409
+ }
410
+ except docker.errors.APIError as e:
411
+ return {
412
+ 'success': False,
413
+ 'stdout': '',
414
+ 'stderr': '',
415
+ 'exit_code': -1,
416
+ 'error': f"Docker API error: {e}"
417
+ }
418
+ except Exception as e:
419
+ return {
420
+ 'success': False,
421
+ 'stdout': '',
422
+ 'stderr': '',
423
+ 'exit_code': -1,
424
+ 'error': str(e)
425
+ }
426
+
427
+ def _container_supports_user(self, image: str) -> bool:
428
+ """
429
+ Check if the container image supports running as a specific user.
430
+ """
431
+ try:
432
+ # Try to inspect the image to see if it has a default user
433
+ img = self.client.images.get(image)
434
+ # Check if image has USER instruction
435
+ return True # For simplicity, assume most images support user switching
436
+ except:
437
+ return False
438
+
439
+ def is_available(self) -> bool:
440
+ """Check if Docker is available for sandboxing."""
441
+ try:
442
+ self.client.ping()
443
+ return True
444
+ except:
445
+ return False
446
+
447
+ def get_supported_languages(self) -> List[str]:
448
+ """Get list of supported languages for sandboxing."""
449
+ return list(self.language_configs.keys())
450
+
451
+
452
+ # Singleton instance
453
+ _secure_sandbox = None
454
+
455
+
456
+ def get_secure_sandbox(max_memory: str = "128m", max_cpu: float = 0.5, timeout: int = 30) -> Optional[SecureSandbox]:
457
+ """Get the global secure sandbox instance."""
458
+ global _secure_sandbox
459
+ if _secure_sandbox is None:
460
+ try:
461
+ _secure_sandbox = SecureSandbox(max_memory, max_cpu, timeout)
462
+ except RuntimeError:
463
+ # Docker not available
464
+ return None
465
+ return _secure_sandbox
466
+
467
+
468
+ def execute_secure_code(code: str, language: str, **kwargs) -> Dict[str, Any]:
469
+ """
470
+ Convenience function to execute code securely.
471
+
472
+ Args:
473
+ code: Source code to execute
474
+ language: Programming language
475
+ **kwargs: Additional options (timeout, memory_limit, cpu_quota)
476
+
477
+ Returns:
478
+ Execution results
479
+ """
480
+ sandbox = get_secure_sandbox()
481
+ if not sandbox:
482
+ return {
483
+ 'success': False,
484
+ 'stdout': '',
485
+ 'stderr': '',
486
+ 'exit_code': -1,
487
+ 'error': 'Secure sandbox not available (Docker required)'
488
+ }
489
+
490
+ return sandbox.execute_untrusted_code(code, language, **kwargs)