From a0c0b256ad3824387de7fbe0043ac80d22082e74 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Sat, 25 Oct 2025 00:02:34 +0400 Subject: [PATCH 1/7] adding docker support --- examples/docker_sandbox/bash_source/main.go | 133 +++++++ examples/docker_sandbox/python_source/main.go | 301 +++++++++++++++ examples/docker_sandbox/run_commands/main.go | 81 ++++ go.mod | 34 +- go.sum | 123 ++++++- gozero.go | 64 ++++ sandbox/sandbox.go | 22 ++ sandbox/sandbox_darwin.go | 10 + sandbox/sandbox_linux.go | 13 + sandbox/sandbox_other.go | 10 + sandbox/sandbox_window.go | 10 + sandbox/virtual_env_docker.go | 346 ++++++++++++++++++ 12 files changed, 1139 insertions(+), 8 deletions(-) create mode 100644 examples/docker_sandbox/bash_source/main.go create mode 100644 examples/docker_sandbox/python_source/main.go create mode 100644 examples/docker_sandbox/run_commands/main.go create mode 100644 sandbox/virtual_env_docker.go diff --git a/examples/docker_sandbox/bash_source/main.go b/examples/docker_sandbox/bash_source/main.go new file mode 100644 index 0000000..c0d8d6f --- /dev/null +++ b/examples/docker_sandbox/bash_source/main.go @@ -0,0 +1,133 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/projectdiscovery/gozero/sandbox" +) + +func main() { + ctx := context.Background() + + // Check if Docker is available + installed, err := sandbox.IsDockerInstalled(ctx) + if err != nil { + log.Fatalf("Error checking Docker installation: %v", err) + } + if !installed { + log.Fatal("Docker is not installed") + } + + enabled, err := sandbox.IsDockerEnabled(ctx) + if err != nil { + log.Fatalf("Error checking Docker status: %v", err) + } + if !enabled { + log.Fatal("Docker daemon is not running") + } + + // Create Docker sandbox configuration for shell execution + config := &sandbox.DockerConfiguration{ + Image: "alpine:latest", + WorkingDir: "/tmp", + Environment: map[string]string{ + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + }, + NetworkMode: "bridge", + NetworkDisabled: false, + User: "root", + Memory: "128m", + CPULimit: "0.5", + Timeout: 30 * time.Second, + Remove: true, + } + + // Create Docker sandbox + sandboxInstance, err := sandbox.NewDockerSandbox(ctx, config) + if err != nil { + log.Fatalf("Failed to create Docker sandbox: %v", err) + } + defer sandboxInstance.Clear() + + // Test shell scripts using RunSource + scripts := []struct { + name string + script string + }{ + { + name: "Simple Hello World", + script: `#!/bin/sh +echo "Hello from shell script!" +echo "Current user: $(whoami)" +echo "Current directory: $(pwd)" +echo "System info: $(uname -a)" +`, + }, + { + name: "File Operations", + script: `#!/bin/sh +echo "Creating test files..." +echo "File 1 content" > /tmp/test1.txt +echo "File 2 content" > /tmp/test2.txt +echo "Files created:" +ls -la /tmp/test*.txt +echo "File contents:" +cat /tmp/test1.txt +cat /tmp/test2.txt +`, + }, + { + name: "System Information", + script: `#!/bin/sh +echo "=== System Information ===" +echo "Hostname: $(hostname)" +echo "User: $(whoami)" +echo "UID: $(id -u)" +echo "GID: $(id -g)" +echo "Groups: $(id -G)" +echo "Home: $HOME" +echo "Shell: $SHELL" +echo "PATH: $PATH" +echo "" +echo "=== Memory Information ===" +cat /proc/meminfo | head -5 +echo "" +echo "=== CPU Information ===" +cat /proc/cpuinfo | head -10 +`, + }, + { + name: "Network Test", + script: `#!/bin/sh +echo "=== Network Configuration ===" +echo "Hostname: $(hostname)" +echo "IP addresses:" +ip addr show 2>/dev/null || ifconfig 2>/dev/null || echo "Network tools not available" +echo "" +echo "=== DNS Resolution Test ===" +nslookup google.com 2>/dev/null || echo "DNS resolution not available" +`, + }, + } + + for _, test := range scripts { + fmt.Printf("\n=== Running: %s ===\n", test.name) + result, err := sandboxInstance.RunSource(ctx, test.script) + if err != nil { + fmt.Printf("Error: %v\n", err) + continue + } + + fmt.Printf("Exit Code: %d\n", result.GetExitCode()) + fmt.Printf("Stdout:\n%s\n", result.Stdout.String()) + if result.Stderr.Len() > 0 { + fmt.Printf("Stderr:\n%s\n", result.Stderr.String()) + } + fmt.Println("---") + } + + fmt.Println("\n=== Shell source execution test completed ===") +} diff --git a/examples/docker_sandbox/python_source/main.go b/examples/docker_sandbox/python_source/main.go new file mode 100644 index 0000000..47bdaa5 --- /dev/null +++ b/examples/docker_sandbox/python_source/main.go @@ -0,0 +1,301 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/projectdiscovery/gozero/sandbox" +) + +func main() { + ctx := context.Background() + + // Check if Docker is available + installed, err := sandbox.IsDockerInstalled(ctx) + if err != nil { + log.Fatalf("Error checking Docker installation: %v", err) + } + if !installed { + log.Fatal("Docker is not installed") + } + + enabled, err := sandbox.IsDockerEnabled(ctx) + if err != nil { + log.Fatalf("Error checking Docker status: %v", err) + } + if !enabled { + log.Fatal("Docker daemon is not running") + } + + // Create Docker sandbox configuration for Python execution + // Using Alpine Python image for minimal size + config := &sandbox.DockerConfiguration{ + Image: "python:3.11-alpine", // Alpine-based Python image + WorkingDir: "/tmp", + Environment: map[string]string{ + "PATH": "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "PYTHONPATH": "/tmp", + }, + NetworkMode: "bridge", + NetworkDisabled: false, + User: "root", + Memory: "256m", // Python needs a bit more memory + CPULimit: "0.5", + Timeout: 30 * time.Second, + Remove: true, + } + + // Create Docker sandbox + sandboxInstance, err := sandbox.NewDockerSandbox(ctx, config) + if err != nil { + log.Fatalf("Failed to create Docker sandbox: %v", err) + } + defer sandboxInstance.Clear() + + // Test Python scripts using RunSource + scripts := []struct { + name string + script string + }{ + { + name: "Simple Hello World", + script: `#!/usr/bin/env python3 +import sys +import os +import platform + +print("Hello from Python script!") +print(f"Python version: {sys.version}") +print(f"Platform: {platform.platform()}") +print(f"Current user: {os.getenv('USER', 'unknown')}") +print(f"Current directory: {os.getcwd()}") +print(f"Python executable: {sys.executable}") +`, + }, + { + name: "Math and Data Processing", + script: `#!/usr/bin/env python3 +import math +import random +import json +from datetime import datetime + +print("=== Math Operations ===") +print(f"Pi: {math.pi}") +print(f"E: {math.e}") +print(f"Square root of 16: {math.sqrt(16)}") +print(f"2^10: {2**10}") + +print("\n=== Random Numbers ===") +random.seed(42) # For reproducible results +for i in range(5): + print(f"Random number {i+1}: {random.randint(1, 100)}") + +print("\n=== JSON Processing ===") +data = { + "name": "Python Script", + "timestamp": datetime.now().isoformat(), + "values": [1, 2, 3, 4, 5], + "nested": {"key": "value"} +} +json_str = json.dumps(data, indent=2) +print("JSON data:") +print(json_str) + +print("\n=== List Comprehensions ===") +squares = [x**2 for x in range(1, 6)] +print(f"Squares of 1-5: {squares}") + +even_squares = [x**2 for x in range(1, 11) if x % 2 == 0] +print(f"Even squares of 1-10: {even_squares}") +`, + }, + { + name: "File Operations", + script: `#!/usr/bin/env python3 +import os +import tempfile +import json + +print("=== File Operations ===") + +# Create some test files +test_data = { + "numbers": [1, 2, 3, 4, 5], + "text": "Hello from Python!", + "nested": {"a": 1, "b": 2} +} + +# Write JSON file +with open('/tmp/test.json', 'w') as f: + json.dump(test_data, f, indent=2) + +# Write text file +with open('/tmp/test.txt', 'w') as f: + f.write("This is a test file created by Python\n") + f.write("Line 2\n") + f.write("Line 3\n") + +# Read and display files +print("Files created:") +for filename in ['/tmp/test.json', '/tmp/test.txt']: + if os.path.exists(filename): + print(f"\\n--- {filename} ---") + with open(filename, 'r') as f: + print(f.read()) + +# List directory contents +print("\\nDirectory contents:") +for item in os.listdir('/tmp'): + if item.startswith('test'): + full_path = os.path.join('/tmp', item) + size = os.path.getsize(full_path) + print(f" {item} ({size} bytes)") +`, + }, + { + name: "System Information", + script: `#!/usr/bin/env python3 +import sys +import os +import platform +import subprocess +import json + +print("=== Python Environment ===") +print(f"Python version: {sys.version}") +print(f"Python executable: {sys.executable}") +print(f"Platform: {platform.platform()}") +print(f"Architecture: {platform.architecture()}") +print(f"Machine: {platform.machine()}") +print(f"Processor: {platform.processor()}") + +print("\\n=== System Information ===") +print(f"Current working directory: {os.getcwd()}") +print(f"User: {os.getenv('USER', 'unknown')}") +print(f"Home directory: {os.getenv('HOME', 'unknown')}") +print(f"PATH: {os.getenv('PATH', 'unknown')}") + +print("\\n=== Environment Variables ===") +env_vars = ['PATH', 'HOME', 'USER', 'SHELL', 'PYTHONPATH'] +for var in env_vars: + value = os.getenv(var, 'Not set') + print(f"{var}: {value}") + +print("\\n=== Process Information ===") +print(f"Process ID: {os.getpid()}") +print(f"Parent Process ID: {os.getppid()}") + +# Try to get system info (may not work in all containers) +try: + result = subprocess.run(['uname', '-a'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + print(f"\\nSystem info: {result.stdout.strip()}") +except Exception as e: + print(f"\\nCould not get system info: {e}") + +print("\\n=== Memory Usage ===") +try: + import psutil + memory = psutil.virtual_memory() + print(f"Total memory: {memory.total / (1024**3):.2f} GB") + print(f"Available memory: {memory.available / (1024**3):.2f} GB") +except ImportError: + print("psutil not available for memory info") +except Exception as e: + print(f"Could not get memory info: {e}") +`, + }, + { + name: "Error Handling and Exception Testing", + script: `#!/usr/bin/env python3 +import sys +import traceback + +print("=== Error Handling Examples ===") + +# Test different types of errors +def test_division_by_zero(): + try: + result = 10 / 0 + return result + except ZeroDivisionError as e: + return f"Caught ZeroDivisionError: {e}" + +def test_file_not_found(): + try: + with open('/nonexistent/file.txt', 'r') as f: + return f.read() + except FileNotFoundError as e: + return f"Caught FileNotFoundError: {e}" + +def test_value_error(): + try: + result = int("not_a_number") + return result + except ValueError as e: + return f"Caught ValueError: {e}" + +def test_key_error(): + try: + my_dict = {"a": 1, "b": 2} + return my_dict["c"] + except KeyError as e: + return f"Caught KeyError: {e}" + +# Run error tests +print("1. Division by zero:") +print(test_division_by_zero()) + +print("\\n2. File not found:") +print(test_file_not_found()) + +print("\\n3. Value error:") +print(test_value_error()) + +print("\\n4. Key error:") +print(test_key_error()) + +print("\\n=== Exception with traceback ===") +try: + # This will raise an exception + x = 1 / 0 +except Exception as e: + print(f"Exception occurred: {e}") + print("Traceback:") + traceback.print_exc() + +print("\\n=== Custom Exception ===") +class CustomError(Exception): + def __init__(self, message): + self.message = message + super().__init__(self.message) + +try: + raise CustomError("This is a custom error!") +except CustomError as e: + print(f"Caught custom error: {e}") +`, + }, + } + + for _, test := range scripts { + fmt.Printf("\n=== Running: %s ===\n", test.name) + result, err := sandboxInstance.RunSource(ctx, test.script) + if err != nil { + fmt.Printf("Error: %v\n", err) + continue + } + + fmt.Printf("Exit Code: %d\n", result.GetExitCode()) + fmt.Printf("Stdout:\n%s\n", result.Stdout.String()) + if result.Stderr.Len() > 0 { + fmt.Printf("Stderr:\n%s\n", result.Stderr.String()) + } + fmt.Println("---") + } + + fmt.Println("\n=== Python source execution test completed ===") +} diff --git a/examples/docker_sandbox/run_commands/main.go b/examples/docker_sandbox/run_commands/main.go new file mode 100644 index 0000000..f57a8e3 --- /dev/null +++ b/examples/docker_sandbox/run_commands/main.go @@ -0,0 +1,81 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/projectdiscovery/gozero/sandbox" +) + +func main() { + ctx := context.Background() + + // Check if Docker is available + installed, err := sandbox.IsDockerInstalled(ctx) + if err != nil { + log.Fatalf("Error checking Docker installation: %v", err) + } + if !installed { + log.Fatal("Docker is not installed") + } + + enabled, err := sandbox.IsDockerEnabled(ctx) + if err != nil { + log.Fatalf("Error checking Docker status: %v", err) + } + if !enabled { + log.Fatal("Docker daemon is not running") + } + + // Create Docker sandbox configuration + // Note: The image will be automatically pulled if it doesn't exist locally + config := &sandbox.DockerConfiguration{ + Image: "alpine:latest", + WorkingDir: "/tmp", + Environment: map[string]string{ + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + }, + NetworkMode: "bridge", // or "host", "none", etc. + NetworkDisabled: false, // Set to true to disable networking entirely + User: "root", + Memory: "128m", // Alpine is much lighter, can use less memory + CPULimit: "0.5", + Timeout: 30 * time.Second, + Remove: true, + } + + // Create Docker sandbox + sandboxInstance, err := sandbox.NewDockerSandbox(ctx, config) + if err != nil { + log.Fatalf("Failed to create Docker sandbox: %v", err) + } + defer sandboxInstance.Clear() + + // Test commands + commands := []string{ + "echo 'Hello from Docker sandbox!'", + "whoami", + "pwd", + "ls -la /", + "uname -a", + } + + for _, cmd := range commands { + fmt.Printf("\n=== Running: %s ===\n", cmd) + result, err := sandboxInstance.Run(ctx, cmd) + if err != nil { + fmt.Printf("Error: %v\n", err) + continue + } + + fmt.Printf("Exit Code: %d\n", result.GetExitCode()) + fmt.Printf("Stdout: %s\n", result.Stdout.String()) + if result.Stderr.Len() > 0 { + fmt.Printf("Stderr: %s\n", result.Stderr.String()) + } + } + + fmt.Println("\n=== Docker sandbox test completed ===") +} diff --git a/go.mod b/go.mod index 95c58a4..99ebd95 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,51 @@ module github.com/projectdiscovery/gozero -go 1.21.0 +go 1.24.0 + toolchain go1.24.1 require ( + github.com/docker/docker v27.1.1+incompatible github.com/projectdiscovery/utils v0.0.55 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.11.1 ) require ( + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/microcosm-cc/bluemonday v1.0.25 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/projectdiscovery/blackrock v0.0.1 // indirect github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect - golang.org/x/net v0.38.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.8.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/grpc v1.76.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.2 // indirect ) diff --git a/go.sum b/go.sum index 2d6976c..1c1e0cf 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,60 @@ +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -16,13 +63,79 @@ github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= github.com/projectdiscovery/utils v0.0.55 h1:QcJhedFVr13ZIJwr81fdXVxylhrub6oQCTFqweDjxe8= github.com/projectdiscovery/utils v0.0.55/go.mod h1:WhzbWSyGkTDn4Jvw+7jM2yP675/RARegNjoA6S7zYcc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= +go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= diff --git a/gozero.go b/gozero.go index 52b2ef6..320309b 100644 --- a/gozero.go +++ b/gozero.go @@ -2,12 +2,24 @@ package gozero import ( "context" + "fmt" "os/exec" "github.com/projectdiscovery/gozero/cmdexec" + "github.com/projectdiscovery/gozero/sandbox" "github.com/projectdiscovery/gozero/types" ) +// VirtualEnvType represents the type of virtual environment +type VirtualEnvType uint8 + +const ( + VirtualEnvLinux VirtualEnvType = iota + VirtualEnvDarwin + VirtualEnvWindows + VirtualEnvDocker +) + // Gozero is executor for gozero type Gozero struct { Options *Options @@ -60,3 +72,55 @@ func (g *Gozero) Eval(ctx context.Context, src, input *Source, args ...string) ( gcmd.AddVars(input.Variables...) return gcmd.Execute(ctx) } + +// EvalWithVirtualEnv evaluates the source code in a virtual environment and returns the output +// This function passes the source code into the virtual environment and external parameters as environment variables +func (g *Gozero) EvalWithVirtualEnv(ctx context.Context, envType VirtualEnvType, src, input *Source, dockerConfig *sandbox.DockerConfiguration, args ...string) (*types.Result, error) { + // Read source code content + srcContent, err := src.ReadAll() + if err != nil { + return nil, err + } + + // Prepare environment variables from source and input variables + envVars := make(map[string]string) + + // Add source variables as environment variables + for _, variable := range src.Variables { + envVars[variable.Name] = variable.Value + } + + // Add input variables as environment variables + for _, variable := range input.Variables { + envVars[variable.Name] = variable.Value + } + + // Handle different virtual environment types + switch envType { + case VirtualEnvDocker: + // Update Docker configuration with environment variables + dockerConfig.Environment = envVars + + // Create Docker sandbox with updated configuration + dockerSandbox, err := sandbox.NewDockerSandbox(ctx, dockerConfig) + if err != nil { + return nil, err + } + + // Execute the source code in the Docker container + result, err := dockerSandbox.RunSource(ctx, string(srcContent)) + if err != nil { + return nil, err + } + + return result, nil + + case VirtualEnvLinux, VirtualEnvDarwin, VirtualEnvWindows: + // For now, these are not implemented - they would use the regular Eval method + // In the future, these could be implemented to use different sandboxing mechanisms + return nil, fmt.Errorf("virtual environment type %d is not yet implemented", envType) + + default: + return nil, fmt.Errorf("unsupported virtual environment type: %d", envType) + } +} diff --git a/sandbox/sandbox.go b/sandbox/sandbox.go index 07d4377..c4c7f16 100644 --- a/sandbox/sandbox.go +++ b/sandbox/sandbox.go @@ -22,8 +22,30 @@ func Deactivate(ctx context.Context) (bool, error) { return deactivate(ctx) } +// IsDockerEnabled checks if Docker sandbox is available +func IsDockerEnabled(ctx context.Context) (bool, error) { + return isDockerEnabled(ctx) +} + +// IsDockerInstalled checks if Docker is installed +func IsDockerInstalled(ctx context.Context) (bool, error) { + return isDockerInstalled(ctx) +} + +// ActivateDocker attempts to start Docker daemon +func ActivateDocker(ctx context.Context) (bool, error) { + return activateDocker(ctx) +} + +// DeactivateDocker attempts to stop Docker daemon +func DeactivateDocker(ctx context.Context) (bool, error) { + return deactivateDocker(ctx) +} + type Sandbox interface { Run(ctx context.Context, cmd string) (*types.Result, error) + RunScript(ctx context.Context, source string) (*types.Result, error) + RunSource(ctx context.Context, source string) (*types.Result, error) Start() error Wait() error Stop() error diff --git a/sandbox/sandbox_darwin.go b/sandbox/sandbox_darwin.go index 0f72b59..acc95e1 100644 --- a/sandbox/sandbox_darwin.go +++ b/sandbox/sandbox_darwin.go @@ -118,6 +118,16 @@ func (s *SandboxDarwin) Run(ctx context.Context, cmd string) (*types.Result, err return cmdContext.Execute(ctx) } +// RunScript executes a script or source code in the sandbox +func (s *SandboxDarwin) RunScript(ctx context.Context, source string) (*types.Result, error) { + return nil, ErrNotImplemented +} + +// RunSource writes source code to a temporary file, executes it with proper permissions, and cleans up +func (s *SandboxDarwin) RunSource(ctx context.Context, source string) (*types.Result, error) { + return nil, ErrNotImplemented +} + // Start the instance func (s *SandboxDarwin) Start() error { return ErrNotImplemented diff --git a/sandbox/sandbox_linux.go b/sandbox/sandbox_linux.go index a9a857d..8e3cd19 100644 --- a/sandbox/sandbox_linux.go +++ b/sandbox/sandbox_linux.go @@ -112,9 +112,22 @@ func (s *SandboxLinux) Run(ctx context.Context, cmd string) (*types.Result, erro params = append(params, s.conf...) params = append(params, strings.Split(cmd, " ")...) cmdContext, err := cmdexec.NewCommand("systemd-run", params...) + if err != nil { + return nil, err + } return cmdContext.Execute(ctx) } +// RunScript executes a script or source code in the sandbox +func (s *SandboxLinux) RunScript(ctx context.Context, source string) (*types.Result, error) { + return nil, ErrNotImplemented +} + +// RunSource writes source code to a temporary file, executes it with proper permissions, and cleans up +func (s *SandboxLinux) RunSource(ctx context.Context, source string) (*types.Result, error) { + return nil, ErrNotImplemented +} + // Start the instance func (s *SandboxLinux) Start() error { return ErrNotImplemented diff --git a/sandbox/sandbox_other.go b/sandbox/sandbox_other.go index c4cfd49..8a2067f 100644 --- a/sandbox/sandbox_other.go +++ b/sandbox/sandbox_other.go @@ -29,6 +29,16 @@ func (s *SandboxOther) Run(ctx context.Context, cmd string) (*types.Result, erro return nil, ErrNotImplemented } +// RunScript executes a script or source code in the sandbox +func (s *SandboxOther) RunScript(ctx context.Context, source string) (*types.Result, error) { + return nil, ErrNotImplemented +} + +// RunSource writes source code to a temporary file, executes it with proper permissions, and cleans up +func (s *SandboxOther) RunSource(ctx context.Context, source string) (*types.Result, error) { + return nil, ErrNotImplemented +} + // Start the instance func (s *SandboxOther) Start() error { return ErrNotImplemented diff --git a/sandbox/sandbox_window.go b/sandbox/sandbox_window.go index 8f1c72d..b5e1578 100644 --- a/sandbox/sandbox_window.go +++ b/sandbox/sandbox_window.go @@ -120,6 +120,16 @@ func (s *SandboxWindows) Run(ctx context.Context, cmd string) (*types.Result, er return nil, ErrAgentRequired } +// RunScript executes a script or source code in the sandbox +func (s *SandboxWindows) RunScript(ctx context.Context, source string) (*types.Result, error) { + return nil, ErrNotImplemented +} + +// RunSource writes source code to a temporary file, executes it with proper permissions, and cleans up +func (s *SandboxWindows) RunSource(ctx context.Context, source string) (*types.Result, error) { + return nil, ErrNotImplemented +} + // Start the instance func (s *SandboxWindows) Start() error { return s.instance.Start() diff --git a/sandbox/virtual_env_docker.go b/sandbox/virtual_env_docker.go new file mode 100644 index 0000000..d66cc42 --- /dev/null +++ b/sandbox/virtual_env_docker.go @@ -0,0 +1,346 @@ +package sandbox + +import ( + "context" + "fmt" + "os/exec" + "strings" + "time" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/client" + "github.com/projectdiscovery/gozero/types" +) + +// DockerConfiguration represents the configuration for Docker sandbox +type DockerConfiguration struct { + Image string // Docker image to use (e.g., "ubuntu:20.04", "alpine:latest") + WorkingDir string // Working directory inside container + Environment map[string]string // Environment variables + NetworkMode string // Network mode (bridge, host, etc.) + NetworkDisabled bool // Disable networking entirely + User string // User to run as inside container + Memory string // Memory limit (e.g., "512m", "1g") + CPULimit string // CPU limit (e.g., "0.5", "1.0") + Timeout time.Duration // Command timeout + Remove bool // Whether to remove container after execution +} + +// SandboxDocker implements the Sandbox interface using Docker containers +type SandboxDocker struct { + config *DockerConfiguration + dockerClient *client.Client +} + +// NewDockerSandbox creates a new Docker-based sandbox +func NewDockerSandbox(ctx context.Context, config *DockerConfiguration) (Sandbox, error) { + // Check if Docker is available + if ok, err := isDockerInstalled(ctx); err != nil || !ok { + return nil, fmt.Errorf("docker not available: %w", err) + } + + // Create Docker client + dockerClient, err := client.NewClientWithOpts( + client.WithAPIVersionNegotiation(), + client.FromEnv, + ) + if err != nil { + return nil, fmt.Errorf("failed to create docker client: %w", err) + } + + // Test Docker connection + _, err = dockerClient.Ping(ctx) + if err != nil { + return nil, fmt.Errorf("failed to connect to docker daemon: %w", err) + } + + // Validate required configuration + if config.Image == "" { + return nil, fmt.Errorf("docker image must be specified") + } + if config.WorkingDir == "" { + return nil, fmt.Errorf("working directory must be specified") + } + if config.Timeout == 0 { + config.Timeout = 30 * time.Second + } + if config.Remove { + config.Remove = true // Default to removing containers + } + + return &SandboxDocker{ + config: config, + dockerClient: dockerClient, + }, nil +} + +// runCommand executes a command in the Docker container with the given command parts +func (s *SandboxDocker) runCommand(ctx context.Context, cmdParts []string, command string, createFile bool, fileContent string) (*types.Result, error) { + if len(cmdParts) == 0 { + return nil, fmt.Errorf("empty command") + } + + // Create a new context with timeout + runCtx, cancel := context.WithTimeout(ctx, s.config.Timeout) + defer cancel() + + // Prepare environment variables + env := []string{} + for key, value := range s.config.Environment { + env = append(env, fmt.Sprintf("%s=%s", key, value)) + } + + // If we need to create a file, modify the command to create it first + finalCmd := cmdParts + if createFile && len(fileContent) > 0 { + // Generate a temporary filename + tmpFileName := fmt.Sprintf("/tmp/gozero_script_%d.sh", time.Now().UnixNano()) + + // Create a script that writes the file content and then executes it + scriptContent := fmt.Sprintf(`#!/bin/sh +cat > %s << 'EOF' +%s +EOF +chmod +x %s +exec %s +`, tmpFileName, fileContent, tmpFileName, tmpFileName) + + // Create a command that writes the script and executes it + finalCmd = []string{"/bin/sh", "-c", scriptContent} + } + + // Create container configuration + containerConfig := &container.Config{ + Image: s.config.Image, + Cmd: finalCmd, + WorkingDir: s.config.WorkingDir, + Env: env, + User: s.config.User, + AttachStdout: true, + AttachStderr: true, + } + + // Create host configuration + hostConfig := &container.HostConfig{ + AutoRemove: false, // Don't auto-remove so we can get logs + } + + // Set network configuration + if s.config.NetworkDisabled { + hostConfig.NetworkMode = "none" + } else if s.config.NetworkMode != "" { + hostConfig.NetworkMode = container.NetworkMode(s.config.NetworkMode) + } + + // Set resource limits if specified + if s.config.Memory != "" { + hostConfig.Memory = parseMemoryLimit(s.config.Memory) + } + + // Pull image if it doesn't exist locally + err := s.pullImageIfNeeded(runCtx, s.config.Image) + if err != nil { + return nil, fmt.Errorf("failed to pull image %s: %w", s.config.Image, err) + } + + // Create container + createResp, err := s.dockerClient.ContainerCreate(runCtx, containerConfig, hostConfig, nil, nil, "") + if err != nil { + return nil, fmt.Errorf("failed to create container: %w", err) + } + + containerID := createResp.ID + + // Start container + err = s.dockerClient.ContainerStart(runCtx, containerID, container.StartOptions{}) + if err != nil { + s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) + return nil, fmt.Errorf("failed to start container: %w", err) + } + + // Wait for container to finish + waitCh, errCh := s.dockerClient.ContainerWait(runCtx, containerID, container.WaitConditionNotRunning) + + select { + case err := <-errCh: + s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) + return nil, fmt.Errorf("container wait error: %w", err) + case result := <-waitCh: + // Get container logs + logs, err := s.dockerClient.ContainerLogs(runCtx, containerID, container.LogsOptions{ + ShowStdout: true, + ShowStderr: true, + }) + if err != nil { + s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) + return nil, fmt.Errorf("failed to get container logs: %w", err) + } + defer logs.Close() + + // Read logs + logData := make([]byte, 1024*1024) // 1MB buffer + n, err := logs.Read(logData) + if err != nil && err.Error() != "EOF" { + s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) + return nil, fmt.Errorf("failed to read container logs: %w", err) + } + + // Create result + cmdResult := &types.Result{ + Command: command, + } + cmdResult.Stdout.Write(logData[:n]) + + // Set exit code + if result.StatusCode != 0 { + cmdResult.SetExitError(&exec.ExitError{}) + } + + // Always clean up container manually + s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) + + return cmdResult, nil + } +} + +// Run executes a command in the Docker container (synchronous execution) +func (s *SandboxDocker) Run(ctx context.Context, cmd string) (*types.Result, error) { + // Parse command into parts + cmdParts := strings.Fields(cmd) + return s.runCommand(ctx, cmdParts, cmd, false, "") +} + +// RunScript executes a script or source code in the Docker container +func (s *SandboxDocker) RunScript(ctx context.Context, source string) (*types.Result, error) { + return nil, ErrNotImplemented +} + +// RunSource writes source code to a temporary file inside the container, executes it with proper permissions, and cleans up +func (s *SandboxDocker) RunSource(ctx context.Context, source string) (*types.Result, error) { + // Generate a temporary filename + tmpFileName := fmt.Sprintf("/tmp/gozero_script_%d.sh", time.Now().UnixNano()) + + // Create a script that writes the source content and then executes it + scriptContent := fmt.Sprintf(`#!/bin/sh +cat > %s << 'EOF' +%s +EOF +chmod +x %s +exec %s +`, tmpFileName, source, tmpFileName, tmpFileName) + + // Execute the script directly + cmdParts := []string{"/bin/sh", "-c", scriptContent} + return s.runCommand(ctx, cmdParts, fmt.Sprintf("sh -c 'script with %s'", tmpFileName), false, "") +} + +// Start is not implemented for Docker sandbox as it's stateless +func (s *SandboxDocker) Start() error { + return ErrNotImplemented +} + +// Wait is not implemented for Docker sandbox as it's stateless +func (s *SandboxDocker) Wait() error { + return ErrNotImplemented +} + +// Stop is not implemented for Docker sandbox as it's stateless +func (s *SandboxDocker) Stop() error { + return ErrNotImplemented +} + +// Clear cleans up Docker resources (containers, images, etc.) +func (s *SandboxDocker) Clear() error { + // For now, we don't need to clean up anything as containers are removed after execution + // In the future, we could add cleanup of unused images or containers + return nil +} + +// isDockerInstalled checks if Docker is installed and available +func isDockerInstalled(ctx context.Context) (bool, error) { + _, err := exec.LookPath("docker") + if err != nil { + return false, err + } + return true, nil +} + +// isDockerEnabled checks if Docker daemon is running +func isDockerEnabled(ctx context.Context) (bool, error) { + cmd := exec.CommandContext(ctx, "docker", "info") + err := cmd.Run() + return err == nil, err +} + +// activateDocker attempts to start Docker daemon (platform-specific) +func activateDocker(ctx context.Context) (bool, error) { + // This is platform-specific and would need to be implemented + // For now, we assume Docker is already running + return isDockerEnabled(ctx) +} + +// deactivateDocker attempts to stop Docker daemon (platform-specific) +func deactivateDocker(ctx context.Context) (bool, error) { + // This is platform-specific and would need to be implemented + // For now, we don't support stopping Docker daemon + return false, fmt.Errorf("docker daemon cannot be stopped programmatically") +} + +// parseMemoryLimit parses memory limit string (e.g., "512m", "1g") to bytes +func parseMemoryLimit(memory string) int64 { + if memory == "" { + return 0 + } + + // Basic parsing - in production, use a proper library + // This is a simplified implementation + memory = strings.ToLower(strings.TrimSpace(memory)) + + var multiplier int64 = 1 + if strings.HasSuffix(memory, "g") { + multiplier = 1024 * 1024 * 1024 + memory = strings.TrimSuffix(memory, "g") + } else if strings.HasSuffix(memory, "m") { + multiplier = 1024 * 1024 + memory = strings.TrimSuffix(memory, "m") + } else if strings.HasSuffix(memory, "k") { + multiplier = 1024 + memory = strings.TrimSuffix(memory, "k") + } + + // Parse the numeric part + var value int64 + fmt.Sscanf(memory, "%d", &value) + return value * multiplier +} + +// pullImageIfNeeded pulls the Docker image if it doesn't exist locally +func (s *SandboxDocker) pullImageIfNeeded(ctx context.Context, imageName string) error { + // Check if image exists locally + _, _, err := s.dockerClient.ImageInspectWithRaw(ctx, imageName) + if err == nil { + // Image exists locally, no need to pull + return nil + } + + // Image doesn't exist, pull it + reader, err := s.dockerClient.ImagePull(ctx, imageName, image.PullOptions{}) + if err != nil { + return fmt.Errorf("failed to pull image: %w", err) + } + defer reader.Close() + + // Read the pull progress to completion + _, err = reader.Read(make([]byte, 1024)) + for err == nil { + _, err = reader.Read(make([]byte, 1024)) + } + + // EOF is expected when pull completes successfully + if err != nil && err.Error() != "EOF" { + return fmt.Errorf("failed to complete image pull: %w", err) + } + + return nil +} From ebdb82f85e924abdf426dc6a7cdd289a4f245a75 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Sat, 25 Oct 2025 00:05:05 +0400 Subject: [PATCH 2/7] fix lint --- examples/docker_sandbox/bash_source/main.go | 7 ++++++- examples/docker_sandbox/python_source/main.go | 7 ++++++- examples/docker_sandbox/run_commands/main.go | 7 ++++++- examples/simple_darwin/main.go | 2 +- gozero.go | 2 +- sandbox/virtual_env_docker.go | 20 +++++++++++-------- source_test.go | 5 ++++- 7 files changed, 36 insertions(+), 14 deletions(-) diff --git a/examples/docker_sandbox/bash_source/main.go b/examples/docker_sandbox/bash_source/main.go index c0d8d6f..8e6a985 100644 --- a/examples/docker_sandbox/bash_source/main.go +++ b/examples/docker_sandbox/bash_source/main.go @@ -50,7 +50,12 @@ func main() { if err != nil { log.Fatalf("Failed to create Docker sandbox: %v", err) } - defer sandboxInstance.Clear() + defer func() { + err := sandboxInstance.Clear() + if err != nil { + log.Fatalf("Failed to clear Docker sandbox: %v", err) + } + }() // Test shell scripts using RunSource scripts := []struct { diff --git a/examples/docker_sandbox/python_source/main.go b/examples/docker_sandbox/python_source/main.go index 47bdaa5..59f94a5 100644 --- a/examples/docker_sandbox/python_source/main.go +++ b/examples/docker_sandbox/python_source/main.go @@ -52,7 +52,12 @@ func main() { if err != nil { log.Fatalf("Failed to create Docker sandbox: %v", err) } - defer sandboxInstance.Clear() + defer func() { + err := sandboxInstance.Clear() + if err != nil { + log.Fatalf("Failed to clear Docker sandbox: %v", err) + } + }() // Test Python scripts using RunSource scripts := []struct { diff --git a/examples/docker_sandbox/run_commands/main.go b/examples/docker_sandbox/run_commands/main.go index f57a8e3..d286ef5 100644 --- a/examples/docker_sandbox/run_commands/main.go +++ b/examples/docker_sandbox/run_commands/main.go @@ -51,7 +51,12 @@ func main() { if err != nil { log.Fatalf("Failed to create Docker sandbox: %v", err) } - defer sandboxInstance.Clear() + defer func() { + err := sandboxInstance.Clear() + if err != nil { + log.Fatalf("Failed to clear Docker sandbox: %v", err) + } + }() // Test commands commands := []string{ diff --git a/examples/simple_darwin/main.go b/examples/simple_darwin/main.go index 7fbd4f4..9ef84c8 100644 --- a/examples/simple_darwin/main.go +++ b/examples/simple_darwin/main.go @@ -33,5 +33,5 @@ func main() { } time.Sleep(60 * time.Second) - instance.Clear() + _ = instance.Clear() } diff --git a/gozero.go b/gozero.go index 320309b..4712a3b 100644 --- a/gozero.go +++ b/gozero.go @@ -52,7 +52,7 @@ func New(options *Options) (*Gozero, error) { // input = stdin , src = source code , args = arguments func (g *Gozero) Eval(ctx context.Context, src, input *Source, args ...string) (*types.Result, error) { if g.Options.EarlyCloseFileDescriptor { - src.File.Close() + _ = src.File.Close() } allargs := []string{} allargs = append(allargs, g.Options.Args...) diff --git a/sandbox/virtual_env_docker.go b/sandbox/virtual_env_docker.go index d66cc42..4fed96e 100644 --- a/sandbox/virtual_env_docker.go +++ b/sandbox/virtual_env_docker.go @@ -155,7 +155,7 @@ exec %s // Start container err = s.dockerClient.ContainerStart(runCtx, containerID, container.StartOptions{}) if err != nil { - s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) + _ = s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) return nil, fmt.Errorf("failed to start container: %w", err) } @@ -164,7 +164,7 @@ exec %s select { case err := <-errCh: - s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) + _ = s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) return nil, fmt.Errorf("container wait error: %w", err) case result := <-waitCh: // Get container logs @@ -173,16 +173,18 @@ exec %s ShowStderr: true, }) if err != nil { - s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) + _ = s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) return nil, fmt.Errorf("failed to get container logs: %w", err) } - defer logs.Close() + defer func() { + _ = logs.Close() + }() // Read logs logData := make([]byte, 1024*1024) // 1MB buffer n, err := logs.Read(logData) if err != nil && err.Error() != "EOF" { - s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) + _ = s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) return nil, fmt.Errorf("failed to read container logs: %w", err) } @@ -198,7 +200,7 @@ exec %s } // Always clean up container manually - s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) + _ = s.dockerClient.ContainerRemove(runCtx, containerID, container.RemoveOptions{Force: true}) return cmdResult, nil } @@ -311,7 +313,7 @@ func parseMemoryLimit(memory string) int64 { // Parse the numeric part var value int64 - fmt.Sscanf(memory, "%d", &value) + _, _ = fmt.Sscanf(memory, "%d", &value) return value * multiplier } @@ -329,7 +331,9 @@ func (s *SandboxDocker) pullImageIfNeeded(ctx context.Context, imageName string) if err != nil { return fmt.Errorf("failed to pull image: %w", err) } - defer reader.Close() + defer func() { + _ = reader.Close() + }() // Read the pull progress to completion _, err = reader.Read(make([]byte, 1024)) diff --git a/source_test.go b/source_test.go index 52e4919..3f57b70 100644 --- a/source_test.go +++ b/source_test.go @@ -13,7 +13,10 @@ func TestNewSourceWithFile(t *testing.T) { if err != nil { t.Fatalf("Failed to create temporary file: %v", err) } - defer os.Remove(tempFile.Name()) // clean up + defer func() { + // clean up + _ = os.Remove(tempFile.Name()) + }() content := []byte("temporary file's content") if _, err := tempFile.Write(content); err != nil { From 95c9c1cf214df3ab09afa94d983ebfd4dcf1c5b3 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Sat, 25 Oct 2025 00:22:55 +0400 Subject: [PATCH 3/7] adding actions --- .github/auto_assign.yml | 7 +++++ .github/dependabot.yml | 44 ++++++++++++++++++++++++++++++++ .github/workflows/build-test.yml | 33 ++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 .github/auto_assign.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build-test.yml diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 0000000..8f713f8 --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,7 @@ +addReviewers: true +reviewers: + - mzack9999 + +numberOfReviewers: 1 +skipKeywords: + - '@dependabot' \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..19ece23 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,44 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + + # Maintain dependencies for go modules + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" + target-branch: "main" + commit-message: + prefix: "chore" + include: "scope" + allow: + - dependency-name: "github.com/projectdiscovery/*" + groups: + modules: + patterns: ["github.com/projectdiscovery/*"] + labels: + - "Type: Maintenance" + +# # Maintain dependencies for docker +# - package-ecosystem: "docker" +# directory: "/" +# schedule: +# interval: "weekly" +# target-branch: "main" +# commit-message: +# prefix: "chore" +# include: "scope" +# +# # Maintain dependencies for GitHub Actions +# - package-ecosystem: "github-actions" +# directory: "/" +# schedule: +# interval: "weekly" +# target-branch: "main" +# commit-message: +# prefix: "chore" +# include: "scope" diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 0000000..aae8557 --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,33 @@ +name: 🔨 Build Test + +on: + pull_request: + workflow_dispatch: + +jobs: + lint: + name: "Lint" + if: "${{ !endsWith(github.actor, '[bot]') }}" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: projectdiscovery/actions/setup/go@v1 + - uses: projectdiscovery/actions/golangci-lint@v1 + + build: + name: Test Builds + needs: ["lint"] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + steps: + - uses: actions/checkout@v4 + - uses: projectdiscovery/actions/setup/go@v1 + + - name: Test + run: go test ./... + + - name: Race Condition Tests + if: ${{ matrix.os != 'windows-latest' }} # false positives in windows + run: go test -race ./... \ No newline at end of file From f9c823537e1f1376b3f8134f94c4550d96c5c6e5 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Mon, 27 Oct 2025 19:04:09 +0400 Subject: [PATCH 4/7] adding bubblewrap + cleanup --- .DS_Store | Bin 0 -> 6148 bytes cmdexec/exec.go | 6 +- examples/bubblewrap_sandbox/main.go | 128 ++++++ examples/docker_sandbox/bash_source/main.go | 19 +- examples/docker_sandbox/python_source/main.go | 20 +- examples/docker_sandbox/run_commands/main.go | 19 +- examples/simple_darwin/main.go | 6 + examples/simple_linux/main.go | 5 + examples/simple_windows/main.go | 5 + go.mod | 16 +- go.sum | 35 +- sandbox/sandbox.go | 36 -- sandbox/sandbox_darwin.go | 19 +- sandbox/sandbox_linux_bubblewrap.go | 378 ++++++++++++++++++ ...dbox_linux.go => sandbox_linux_systemd.go} | 31 +- sandbox/sandbox_other.go | 76 ---- sandbox/sandbox_window.go | 5 +- sandbox/virtual_env_docker.go | 49 +-- 18 files changed, 599 insertions(+), 254 deletions(-) create mode 100644 .DS_Store create mode 100644 examples/bubblewrap_sandbox/main.go create mode 100644 sandbox/sandbox_linux_bubblewrap.go rename sandbox/{sandbox_linux.go => sandbox_linux_systemd.go} (89%) delete mode 100644 sandbox/sandbox_other.go diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5e0d09ef92e7e90b0499e6c9439e5cdd5d107e46 GIT binary patch literal 6148 zcmeHKJ8r`;3?A>JE0C0t{8|L0i z0E-2HHE|9^1g1d+2351g(4Zq;GOs4ifk79|=0o#l%??HVcAQ^4U9<*rqykjnRDs7> zwpRaN;1~M;QxaEHfC}7|0=nv7`wmaa+PZk0)!G7ogImrI+zfN4VDNGb^m2@amE&hm bio9ZT?AOFO(CLUf9mt;n(}hL_ZmqyC7#9_~ literal 0 HcmV?d00001 diff --git a/cmdexec/exec.go b/cmdexec/exec.go index 189f250..c8f9bda 100644 --- a/cmdexec/exec.go +++ b/cmdexec/exec.go @@ -8,7 +8,7 @@ import ( "os/exec" "github.com/projectdiscovery/gozero/types" - errorutil "github.com/projectdiscovery/utils/errors" + "github.com/projectdiscovery/utils/errkit" ) // Command is a command to execute. @@ -74,7 +74,7 @@ func (c *Command) Execute(ctx context.Context) (*types.Result, error) { if err := cmd.Start(); err != nil { // this error indicates that command did not start at all (e.g. binary not found) // or something similar - return res, errorutil.NewWithErr(err).Msgf("failed to start command got: %v", res.Stderr.String()) + return res, errkit.WithMessagef(err, "failed to start command got: %v", res.Stderr.String()) } if err := cmd.Wait(); err != nil { @@ -82,7 +82,7 @@ func (c *Command) Execute(ctx context.Context) (*types.Result, error) { res.SetExitError(execErr) } // this error indicates that command started but exited with non-zero exit code - return res, errorutil.NewWithErr(err).Msgf("failed to exec command got: %v", res.Stderr.String()) + return res, errkit.WithMessagef(err, "failed to exec command got: %v", res.Stderr.String()) } return res, nil } diff --git a/examples/bubblewrap_sandbox/main.go b/examples/bubblewrap_sandbox/main.go new file mode 100644 index 0000000..b4e4212 --- /dev/null +++ b/examples/bubblewrap_sandbox/main.go @@ -0,0 +1,128 @@ +//go:build linux + +package main + +import ( + "context" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/projectdiscovery/gozero/sandbox" + osutils "github.com/projectdiscovery/utils/os" +) + +func main() { + ctx := context.Background() + + if !osutils.IsLinux() { + log.Printf("This example is only supported on Linux") + return + } + + fmt.Println("=== Bubblewrap Sandbox Example ===") + fmt.Println() + + // Create bubblewrap configuration with static settings + config := &sandbox.BubblewrapConfiguration{ + TempDir: filepath.Join(os.TempDir(), "gozero-bubblewrap"), + UnsharePID: true, + UnshareIPC: true, + UnshareNetwork: true, + UnshareUTS: true, + UnshareUser: true, + NewSession: true, + HostFilesystem: true, + Environment: map[string]string{ + "PATH": "/usr/bin:/bin", + }, + } + + // Create bubblewrap sandbox + bwrap, err := sandbox.NewBubblewrapSandbox(ctx, config) + if err != nil { + log.Fatalf("Failed to create bubblewrap sandbox: %v", err) + } + defer bwrap.Clear() + + fmt.Println("1. Running a simple command in the sandbox...") + result, err := bwrap.Run(ctx, "echo 'Hello from bubblewrap sandbox!'") + if err != nil { + fmt.Printf("Error: %v\n", err) + } else { + fmt.Printf("Exit Code: %d\n", result.GetExitCode()) + fmt.Printf("Output: %s\n", result.Stdout.String()) + if result.Stderr.Len() > 0 { + fmt.Printf("Errors: %s\n", result.Stderr.String()) + } + } + fmt.Println("---") + + fmt.Println("2. Running ls command in the sandbox...") + result, err = bwrap.Run(ctx, "ls /") + if err != nil { + fmt.Printf("Error: %v\n", err) + } else { + fmt.Printf("Exit Code: %d\n", result.GetExitCode()) + fmt.Printf("Output: %s\n", result.Stdout.String()) + if result.Stderr.Len() > 0 { + fmt.Printf("Errors: %s\n", result.Stderr.String()) + } + } + fmt.Println("---") + + fmt.Println("3. Running env command to see sandbox environment...") + result, err = bwrap.Run(ctx, "env") + if err != nil { + fmt.Printf("Error: %v\n", err) + } else { + fmt.Printf("Exit Code: %d\n", result.GetExitCode()) + fmt.Printf("Output: %s\n", result.Stdout.String()) + if result.Stderr.Len() > 0 { + fmt.Printf("Errors: %s\n", result.Stderr.String()) + } + } + fmt.Println("---") + + fmt.Println("4. Running Python source code in the sandbox...") + pyCode := `print("Hello from Python in bubblewrap!") +print("Python is working in the sandbox!") +import sys +print(f"Python version: {sys.version}") +` + result, err = bwrap.RunSource(ctx, pyCode) + if err != nil { + fmt.Printf("Error: %v\n", err) + } else { + fmt.Printf("Exit Code: %d\n", result.GetExitCode()) + fmt.Printf("Output: %s\n", result.Stdout.String()) + if result.Stderr.Len() > 0 { + fmt.Printf("Errors: %s\n", result.Stderr.String()) + } + } + fmt.Println("---") + + fmt.Println("5. Using ExecuteWithOptions with per-command configuration...") + options := &sandbox.BubblewrapCommandOptions{ + Command: "bash", + Args: []string{"-c", "echo 'Hello from per-command options!' && ls /src"}, + CommandBinds: []sandbox.BindMount{ + {HostPath: "/usr/bin", SandboxPath: "/usr/bin"}, + }, + WorkingDir: "/src", + } + result, err = bwrap.ExecuteWithOptions(ctx, options) + if err != nil { + fmt.Printf("Error: %v\n", err) + } else { + fmt.Printf("Exit Code: %d\n", result.GetExitCode()) + fmt.Printf("Output: %s\n", result.Stdout.String()) + if result.Stderr.Len() > 0 { + fmt.Printf("Errors: %s\n", result.Stderr.String()) + } + } + fmt.Println("---") + + fmt.Println("=== Bubblewrap sandbox test completed ===") +} diff --git a/examples/docker_sandbox/bash_source/main.go b/examples/docker_sandbox/bash_source/main.go index 8e6a985..2dd584d 100644 --- a/examples/docker_sandbox/bash_source/main.go +++ b/examples/docker_sandbox/bash_source/main.go @@ -7,26 +7,15 @@ import ( "time" "github.com/projectdiscovery/gozero/sandbox" + osutils "github.com/projectdiscovery/utils/os" ) func main() { ctx := context.Background() - // Check if Docker is available - installed, err := sandbox.IsDockerInstalled(ctx) - if err != nil { - log.Fatalf("Error checking Docker installation: %v", err) - } - if !installed { - log.Fatal("Docker is not installed") - } - - enabled, err := sandbox.IsDockerEnabled(ctx) - if err != nil { - log.Fatalf("Error checking Docker status: %v", err) - } - if !enabled { - log.Fatal("Docker daemon is not running") + if !osutils.IsLinux() { + log.Printf("This example is only supported on Linux") + return } // Create Docker sandbox configuration for shell execution diff --git a/examples/docker_sandbox/python_source/main.go b/examples/docker_sandbox/python_source/main.go index 59f94a5..586816b 100644 --- a/examples/docker_sandbox/python_source/main.go +++ b/examples/docker_sandbox/python_source/main.go @@ -7,28 +7,16 @@ import ( "time" "github.com/projectdiscovery/gozero/sandbox" + osutils "github.com/projectdiscovery/utils/os" ) func main() { ctx := context.Background() - // Check if Docker is available - installed, err := sandbox.IsDockerInstalled(ctx) - if err != nil { - log.Fatalf("Error checking Docker installation: %v", err) - } - if !installed { - log.Fatal("Docker is not installed") + if !osutils.IsLinux() { + log.Printf("This example is only supported on Linux") + return } - - enabled, err := sandbox.IsDockerEnabled(ctx) - if err != nil { - log.Fatalf("Error checking Docker status: %v", err) - } - if !enabled { - log.Fatal("Docker daemon is not running") - } - // Create Docker sandbox configuration for Python execution // Using Alpine Python image for minimal size config := &sandbox.DockerConfiguration{ diff --git a/examples/docker_sandbox/run_commands/main.go b/examples/docker_sandbox/run_commands/main.go index d286ef5..8d12b5b 100644 --- a/examples/docker_sandbox/run_commands/main.go +++ b/examples/docker_sandbox/run_commands/main.go @@ -7,26 +7,15 @@ import ( "time" "github.com/projectdiscovery/gozero/sandbox" + osutils "github.com/projectdiscovery/utils/os" ) func main() { ctx := context.Background() - // Check if Docker is available - installed, err := sandbox.IsDockerInstalled(ctx) - if err != nil { - log.Fatalf("Error checking Docker installation: %v", err) - } - if !installed { - log.Fatal("Docker is not installed") - } - - enabled, err := sandbox.IsDockerEnabled(ctx) - if err != nil { - log.Fatalf("Error checking Docker status: %v", err) - } - if !enabled { - log.Fatal("Docker daemon is not running") + if !osutils.IsLinux() { + log.Printf("This example is only supported on Linux") + return } // Create Docker sandbox configuration diff --git a/examples/simple_darwin/main.go b/examples/simple_darwin/main.go index 9ef84c8..a79e069 100644 --- a/examples/simple_darwin/main.go +++ b/examples/simple_darwin/main.go @@ -9,9 +9,15 @@ import ( "time" "github.com/projectdiscovery/gozero/sandbox" + osutil "github.com/projectdiscovery/utils/os" ) func main() { + if !osutil.IsOSX() { + log.Printf("This example is only supported on Darwin") + return + } + command := "hostname" rules := []sandbox.Rule{ {Action: sandbox.Deny, Scope: sandbox.FileWrite}, diff --git a/examples/simple_linux/main.go b/examples/simple_linux/main.go index c667236..6a35e19 100644 --- a/examples/simple_linux/main.go +++ b/examples/simple_linux/main.go @@ -9,9 +9,14 @@ import ( "time" "github.com/projectdiscovery/gozero/sandbox" + osutil "github.com/projectdiscovery/utils/os" ) func main() { + if !osutil.IsLinux() { + log.Printf("This example is only supported on Linux") + return + } command := "hostname" rules := []sandbox.Rule{ {Filter: sandbox.DynamicUser, Arg: sandbox.Arg{Type: sandbox.Bool, Params: "yes"}}, diff --git a/examples/simple_windows/main.go b/examples/simple_windows/main.go index 0556464..15dfb4c 100644 --- a/examples/simple_windows/main.go +++ b/examples/simple_windows/main.go @@ -9,9 +9,14 @@ import ( "time" "github.com/projectdiscovery/gozero/sandbox" + osutil "github.com/projectdiscovery/utils/os" ) func main() { + if !osutil.IsWindows() { + log.Printf("This example is only supported on Windows") + return + } commands := []string{ "ipconfig", } diff --git a/go.mod b/go.mod index 99ebd95..2438416 100644 --- a/go.mod +++ b/go.mod @@ -6,16 +6,16 @@ toolchain go1.24.1 require ( github.com/docker/docker v27.1.1+incompatible - github.com/projectdiscovery/utils v0.0.55 + github.com/projectdiscovery/utils v0.6.0 github.com/stretchr/testify v1.11.1 ) require ( github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -23,17 +23,18 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/gorilla/css v1.0.0 // indirect - github.com/microcosm-cc/bluemonday v1.0.25 // indirect + github.com/gorilla/css v1.0.1 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.2 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/projectdiscovery/blackrock v0.0.1 // indirect - github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect + github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect @@ -42,6 +43,7 @@ require ( go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.8.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/time v0.14.0 // indirect diff --git a/go.sum b/go.sum index 1c1e0cf..5420677 100644 --- a/go.sum +++ b/go.sum @@ -2,16 +2,16 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= @@ -33,8 +33,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -43,8 +43,10 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= -github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= @@ -57,16 +59,16 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ= github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= -github.com/projectdiscovery/utils v0.0.55 h1:QcJhedFVr13ZIJwr81fdXVxylhrub6oQCTFqweDjxe8= -github.com/projectdiscovery/utils v0.0.55/go.mod h1:WhzbWSyGkTDn4Jvw+7jM2yP675/RARegNjoA6S7zYcc= +github.com/projectdiscovery/utils v0.6.0 h1:rH4Haei7uHgqEq6pFGe8U+iD4PoBWUDB8LhoNxPawkk= +github.com/projectdiscovery/utils v0.6.0/go.mod h1:NT7ExqILrDukgBFPPLBKQzSKYMBfecNcab7jT1bakRE= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= -github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -91,9 +93,13 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -108,6 +114,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/sandbox/sandbox.go b/sandbox/sandbox.go index c4c7f16..95c045f 100644 --- a/sandbox/sandbox.go +++ b/sandbox/sandbox.go @@ -6,42 +6,6 @@ import ( "github.com/projectdiscovery/gozero/types" ) -func IsEnabled(ctx context.Context) (bool, error) { - return isEnabled(ctx) -} - -func IsInstalled(ctx context.Context) (bool, error) { - return isInstalled(ctx) -} - -func Activate(ctx context.Context) (bool, error) { - return activate(ctx) -} - -func Deactivate(ctx context.Context) (bool, error) { - return deactivate(ctx) -} - -// IsDockerEnabled checks if Docker sandbox is available -func IsDockerEnabled(ctx context.Context) (bool, error) { - return isDockerEnabled(ctx) -} - -// IsDockerInstalled checks if Docker is installed -func IsDockerInstalled(ctx context.Context) (bool, error) { - return isDockerInstalled(ctx) -} - -// ActivateDocker attempts to start Docker daemon -func ActivateDocker(ctx context.Context) (bool, error) { - return activateDocker(ctx) -} - -// DeactivateDocker attempts to stop Docker daemon -func DeactivateDocker(ctx context.Context) (bool, error) { - return deactivateDocker(ctx) -} - type Sandbox interface { Run(ctx context.Context, cmd string) (*types.Result, error) RunScript(ctx context.Context, source string) (*types.Result, error) diff --git a/sandbox/sandbox_darwin.go b/sandbox/sandbox_darwin.go index acc95e1..28b254d 100644 --- a/sandbox/sandbox_darwin.go +++ b/sandbox/sandbox_darwin.go @@ -65,7 +65,7 @@ type SandboxDarwin struct { // New sandbox with the given configuration func New(ctx context.Context, config *Configuration) (Sandbox, error) { - if ok, err := IsInstalled(context.Background()); err != nil || !ok { + if ok, err := isSandboxExecInstalled(context.Background()); err != nil || !ok { return nil, errors.New("sandbox feature not installed") } @@ -148,22 +148,11 @@ func (s *SandboxDarwin) Clear() error { return os.RemoveAll(s.confFile) } -func isEnabled(ctx context.Context) (bool, error) { - return isInstalled(ctx) -} - -func isInstalled(ctx context.Context) (bool, error) { - _, err := exec.LookPath("sandbox-exec") +func isSandboxExecInstalled(ctx context.Context) (bool, error) { + cmd := exec.CommandContext(ctx, "sandbox-exec", "-p", "echo", "true") + err := cmd.Run() if err != nil { return false, err } return true, nil } - -func activate(ctx context.Context) (bool, error) { - return false, errors.New("sandbox is a darwin native functionality") -} - -func deactivate(ctx context.Context) (bool, error) { - return false, errors.New("sandbox can't be disabled") -} diff --git a/sandbox/sandbox_linux_bubblewrap.go b/sandbox/sandbox_linux_bubblewrap.go new file mode 100644 index 0000000..09c5015 --- /dev/null +++ b/sandbox/sandbox_linux_bubblewrap.go @@ -0,0 +1,378 @@ +//go:build linux + +package sandbox + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/projectdiscovery/gozero/types" + "github.com/projectdiscovery/utils/errkit" +) + +// BubblewrapConfiguration holds the general configuration for bubblewrap sandboxing +type BubblewrapConfiguration struct { + // Base directory for temporary sandbox files + TempDir string + + // Static namespace options (applied to all commands) + UnsharePID bool // Create a new PID namespace + UnshareIPC bool // Create a new IPC namespace + UnshareNetwork bool // Create a new network namespace + UnshareUTS bool // Create a new UTS namespace + UnshareUser bool // Create a new user namespace + UnshareCgroup bool // Create a new cgroup namespace + + // Static security options (applied to all commands) + NewSession bool // Create a new session (prevents TIOCSTI attacks) + UID int // UID to run as inside the sandbox + GID int // GID to run as inside the sandbox + + // Seccomp filter file + SeccompFile string + + // Static bind mounts (read-only system directories) + ReadOnlySystemBinds []BindMount + + // Static environment variables + Environment map[string]string + + // Enable host filesystem access (read-only) + HostFilesystem bool +} + +// BubblewrapCommandOptions holds per-command configuration +type BubblewrapCommandOptions struct { + // The command to execute + Command string + + // Arguments for the command + Args []string + + // Per-command bind mounts (e.g., source file location -> sandbox location) + CommandBinds []BindMount + + // Working directory for this command + WorkingDir string + + // Change to this directory before running + Chdir string + + // Per-command environment variables (merged with static ones) + Environment map[string]string + + // Input for stdin + Stdin string +} + +// BindMount represents a bind mount configuration +type BindMount struct { + HostPath string + SandboxPath string +} + +// Symlink represents a symlink to create in the sandbox +type Symlink struct { + Target string + Link string +} + +// BubblewrapSandbox implements sandboxing using bubblewrap (bwrap) +type BubblewrapSandbox struct { + config *BubblewrapConfiguration +} + +// NewBubblewrapSandbox creates a new bubblewrap sandbox +func NewBubblewrapSandbox(ctx context.Context, config *BubblewrapConfiguration) (*BubblewrapSandbox, error) { + if config == nil { + return nil, errors.New("config cannot be nil") + } + + // Check if bwrap is installed + installed, err := isBubblewrapInstalled(ctx) + if err != nil || !installed { + return nil, errors.New("bubblewrap (bwrap) is not installed") + } + + // Set default temp directory if not provided + if config.TempDir == "" { + config.TempDir = filepath.Join(os.TempDir(), "gozero-bubblewrap") + } + + // Validate configuration + if err := validateBubblewrapConfig(config); err != nil { + return nil, fmt.Errorf("invalid configuration: %w", err) + } + + return &BubblewrapSandbox{ + config: config, + }, nil +} + +// validateBubblewrapConfig validates the bubblewrap configuration +func validateBubblewrapConfig(config *BubblewrapConfiguration) error { + // Create temp directory if it doesn't exist + if err := os.MkdirAll(config.TempDir, 0755); err != nil { + return fmt.Errorf("failed to create temp directory: %w", err) + } + + return nil +} + +// isBubblewrapInstalled checks if bubblewrap (bwrap) is installed and available +func isBubblewrapInstalled(ctx context.Context) (bool, error) { + cmd := exec.CommandContext(ctx, "bwrap", "--help") + err := cmd.Run() + if err != nil { + return false, err + } + return true, nil +} + +// Run executes a command in the bubblewrap sandbox with default options +func (b *BubblewrapSandbox) Run(ctx context.Context, cmd string) (*types.Result, error) { + // Split command into executable and args + parts := strings.Fields(cmd) + if len(parts) == 0 { + return nil, errors.New("empty command") + } + + executable := parts[0] + args := parts[1:] + + options := &BubblewrapCommandOptions{ + Command: executable, + Args: args, + } + + return b.ExecuteWithOptions(ctx, options) +} + +// RunScript runs a script in the bubblewrap sandbox +func (b *BubblewrapSandbox) RunScript(ctx context.Context, source string) (*types.Result, error) { + return b.RunSource(ctx, source) +} + +// RunSource executes source code in the bubblewrap sandbox +// It creates a specific source directory, places the script file inside, and mounts only that directory +func (b *BubblewrapSandbox) RunSource(ctx context.Context, source string) (*types.Result, error) { + // Create a specific directory for this source execution + sourceDir, err := os.MkdirTemp(b.config.TempDir, "source_*") + if err != nil { + return nil, fmt.Errorf("failed to create source directory: %w", err) + } + defer os.RemoveAll(sourceDir) + + // Create the script file in the source directory + scriptPath := filepath.Join(sourceDir, "script.sh") + if err := os.WriteFile(scriptPath, []byte(source), 0755); err != nil { + return nil, fmt.Errorf("failed to write script to file: %w", err) + } + + // Create options with bind mount for the source directory + options := &BubblewrapCommandOptions{ + Command: "bash", + Args: []string{"/src/script.sh"}, + CommandBinds: []BindMount{ + { + HostPath: sourceDir, + SandboxPath: "/src", + }, + }, + WorkingDir: "/src", + } + + return b.ExecuteWithOptions(ctx, options) +} + +// ExecuteWithOptions executes a command with specific per-command options +func (b *BubblewrapSandbox) ExecuteWithOptions(ctx context.Context, options *BubblewrapCommandOptions) (*types.Result, error) { + if options == nil { + return nil, errors.New("options cannot be nil") + } + + if options.Command == "" { + return nil, errors.New("command cannot be empty") + } + + // Create a unique sandbox directory for this command execution + sandboxDir, err := os.MkdirTemp(b.config.TempDir, "sandbox_*") + if err != nil { + return nil, fmt.Errorf("failed to create sandbox directory: %w", err) + } + defer os.RemoveAll(sandboxDir) + + return b.executeInSandbox(ctx, sandboxDir, options) +} + +// executeInSandbox executes a command in the bubblewrap sandbox +func (b *BubblewrapSandbox) executeInSandbox(ctx context.Context, sandboxDir string, options *BubblewrapCommandOptions) (*types.Result, error) { + // Build the bwrap command with both static and per-command options + bwrapArgs := b.buildBubblewrapArgs(sandboxDir, options) + + // Add the command to execute + bwrapArgs = append(bwrapArgs, options.Command) + bwrapArgs = append(bwrapArgs, options.Args...) + + // Execute the command + cmd := exec.CommandContext(ctx, "bwrap", bwrapArgs...) + + // Create result + result := &types.Result{ + Command: fmt.Sprintf("bwrap %s", strings.Join(bwrapArgs, " ")), + } + + // Set stdin if provided + if options.Stdin != "" { + cmd.Stdin = strings.NewReader(options.Stdin) + } + + // Capture output + cmd.Stdout = &result.Stdout + cmd.Stderr = &result.Stderr + + // Run the command + if err := cmd.Start(); err != nil { + return result, errkit.New("failed to start bubblewrap command: %w", err) + } + + if err := cmd.Wait(); err != nil { + if execErr, ok := err.(*exec.ExitError); ok { + result.SetExitError(execErr) + } + return result, errkit.New("bubblewrap command failed: %w", err) + } + + return result, nil +} + +// buildBubblewrapArgs constructs the bwrap command arguments +func (b *BubblewrapSandbox) buildBubblewrapArgs(sandboxDir string, options *BubblewrapCommandOptions) []string { + args := []string{} + + // Create a temporary root filesystem + args = append(args, "--unshare-all") + args = append(args, "--die-with-parent") + + // Add proc and dev + args = append(args, "--proc", "/proc") + args = append(args, "--dev", "/dev") + + // Add tmpfs for /tmp and /run + args = append(args, "--tmpfs", "/tmp") + args = append(args, "--tmpfs", "/run") + + // Add the sandbox directory (unique per execution) as root + args = append(args, "--bind", sandboxDir, "/") + + // Add namespace options + if b.config.UnsharePID { + args = append(args, "--unshare-pid") + } + if b.config.UnshareIPC { + args = append(args, "--unshare-ipc") + } + if b.config.UnshareNetwork { + args = append(args, "--unshare-net") + } + if b.config.UnshareUTS { + args = append(args, "--unshare-uts") + } + if b.config.UnshareUser { + args = append(args, "--unshare-user") + } + + // Add session protection + if b.config.NewSession { + args = append(args, "--new-session") + } + + // Add UID/GID mapping + if b.config.UnshareUser { + if b.config.UID > 0 { + args = append(args, "--uid", fmt.Sprintf("%d", b.config.UID)) + } + if b.config.GID > 0 { + args = append(args, "--gid", fmt.Sprintf("%d", b.config.GID)) + } + } + + // Add host filesystem access if enabled + if b.config.HostFilesystem { + // Bind mount essential directories as read-only + args = append(args, "--ro-bind", "/usr", "/usr") + args = append(args, "--ro-bind", "/lib", "/lib") + args = append(args, "--ro-bind", "/lib64", "/lib64") + args = append(args, "--ro-bind", "/bin", "/bin") + args = append(args, "--ro-bind", "/sbin", "/sbin") + } + + // Add static read-only system binds + for _, bind := range b.config.ReadOnlySystemBinds { + hostPath, err := filepath.Abs(bind.HostPath) + if err != nil { + continue + } + args = append(args, "--ro-bind", hostPath, bind.SandboxPath) + } + + // Add static environment variables + for key, value := range b.config.Environment { + args = append(args, "--setenv", key, value) + } + + // Add per-command bind mounts (e.g., source directories) + for _, bind := range options.CommandBinds { + hostPath, err := filepath.Abs(bind.HostPath) + if err != nil { + continue + } + args = append(args, "--bind", hostPath, bind.SandboxPath) + } + + // Add per-command environment variables (merged with static ones) + for key, value := range options.Environment { + args = append(args, "--setenv", key, value) + } + + // Add chdir if specified + if options.Chdir != "" { + args = append(args, "--chdir", options.Chdir) + } + + // Add working directory if specified + if options.WorkingDir != "" { + // The working directory should already be accessible via the bind mounts + // bwrap will use it as the current working directory + } + + return args +} + +// Start starts the sandbox (no-op for bubblewrap as it runs per-command) +func (b *BubblewrapSandbox) Start() error { + return nil +} + +// Wait waits for the sandbox to finish (no-op for bubblewrap) +func (b *BubblewrapSandbox) Wait() error { + return nil +} + +// Stop stops the sandbox (no-op for bubblewrap as each command runs independently) +func (b *BubblewrapSandbox) Stop() error { + return nil +} + +// Clear cleans up the temp directory +func (b *BubblewrapSandbox) Clear() error { + if b.config.TempDir != "" { + return os.RemoveAll(b.config.TempDir) + } + return nil +} diff --git a/sandbox/sandbox_linux.go b/sandbox/sandbox_linux_systemd.go similarity index 89% rename from sandbox/sandbox_linux.go rename to sandbox/sandbox_linux_systemd.go index 8e3cd19..4cf5869 100644 --- a/sandbox/sandbox_linux.go +++ b/sandbox/sandbox_linux_systemd.go @@ -68,9 +68,18 @@ type SandboxLinux struct { conf []string } +func isSystemdInstalled(ctx context.Context) (bool, error) { + cmd := exec.CommandContext(ctx, "systemd-run", "--help") + err := cmd.Run() + if err != nil { + return false, err + } + return true, nil +} + // New sandbox with the given configuration func New(ctx context.Context, config *Configuration) (Sandbox, error) { - if ok, err := IsInstalled(context.Background()); err != nil || !ok { + if ok, err := isSystemdInstalled(context.Background()); err != nil || !ok { return nil, errors.New("sandbox feature not installed") } @@ -147,23 +156,3 @@ func (s *SandboxLinux) Stop() error { func (s *SandboxLinux) Clear() error { return ErrNotImplemented } - -func isEnabled(ctx context.Context) (bool, error) { - return isInstalled(ctx) -} - -func isInstalled(ctx context.Context) (bool, error) { - _, err := exec.LookPath("systemd-run") - if err != nil { - return false, err - } - return true, nil -} - -func activate(ctx context.Context) (bool, error) { - return false, errors.New("sandbox is a linux native functionality") -} - -func deactivate(ctx context.Context) (bool, error) { - return false, errors.New("can't be disabled") -} diff --git a/sandbox/sandbox_other.go b/sandbox/sandbox_other.go deleted file mode 100644 index 8a2067f..0000000 --- a/sandbox/sandbox_other.go +++ /dev/null @@ -1,76 +0,0 @@ -//go:build !(darwin || linux || windows) - -package sandbox - -import ( - "context" - - "github.com/projectdiscovery/gozero/types" -) - -type Configuration struct { - Rules []Rule -} - -type Rule struct{} - -// Sandbox native on other platforms -type SandboxOther struct { - Config *Configuration - conf []string -} - -// New sandbox with the given configuration -func New(ctx context.Context, config *Configuration) (Sandbox, error) { - return nil, ErrNotImplemented -} - -func (s *SandboxOther) Run(ctx context.Context, cmd string) (*types.Result, error) { - return nil, ErrNotImplemented -} - -// RunScript executes a script or source code in the sandbox -func (s *SandboxOther) RunScript(ctx context.Context, source string) (*types.Result, error) { - return nil, ErrNotImplemented -} - -// RunSource writes source code to a temporary file, executes it with proper permissions, and cleans up -func (s *SandboxOther) RunSource(ctx context.Context, source string) (*types.Result, error) { - return nil, ErrNotImplemented -} - -// Start the instance -func (s *SandboxOther) Start() error { - return ErrNotImplemented -} - -// Wait for the instance -func (s *SandboxOther) Wait() error { - return ErrNotImplemented -} - -// Stop the instance -func (s *SandboxOther) Stop() error { - return ErrNotImplemented -} - -// Clear the instance after stop -func (s *SandboxOther) Clear() error { - return ErrNotImplemented -} - -func isEnabled(ctx context.Context) (bool, error) { - return false, ErrNotImplemented -} - -func isInstalled(ctx context.Context) (bool, error) { - return false, ErrNotImplemented -} - -func activate(ctx context.Context) (bool, error) { - return false, ErrNotImplemented -} - -func deactivate(ctx context.Context) (bool, error) { - return false, ErrNotImplemented -} diff --git a/sandbox/sandbox_window.go b/sandbox/sandbox_window.go index b5e1578..89e2f76 100644 --- a/sandbox/sandbox_window.go +++ b/sandbox/sandbox_window.go @@ -6,7 +6,6 @@ import ( "bytes" "context" "encoding/xml" - "errors" "net" "os" "os/exec" @@ -66,8 +65,8 @@ type SandboxWindows struct { // New sandbox with the given configuration func New(ctx context.Context, config *Configuration) (Sandbox, error) { - if ok, err := IsInstalled(context.Background()); err != nil || !ok { - return nil, errors.New("sandbox feature not installed") + if ok, err := isInstalled(ctx); err != nil || !ok { + return nil, err } sharedFolder, err := os.MkdirTemp("", "") diff --git a/sandbox/virtual_env_docker.go b/sandbox/virtual_env_docker.go index 4fed96e..86ffa76 100644 --- a/sandbox/virtual_env_docker.go +++ b/sandbox/virtual_env_docker.go @@ -213,9 +213,9 @@ func (s *SandboxDocker) Run(ctx context.Context, cmd string) (*types.Result, err return s.runCommand(ctx, cmdParts, cmd, false, "") } -// RunScript executes a script or source code in the Docker container +// RunScript executes a script in the Docker container func (s *SandboxDocker) RunScript(ctx context.Context, source string) (*types.Result, error) { - return nil, ErrNotImplemented + return s.RunSource(ctx, source) } // RunSource writes source code to a temporary file inside the container, executes it with proper permissions, and cleans up @@ -259,36 +259,16 @@ func (s *SandboxDocker) Clear() error { return nil } -// isDockerInstalled checks if Docker is installed and available +// isDockerInstalled checks if Docker is installed and available by executing docker info func isDockerInstalled(ctx context.Context) (bool, error) { - _, err := exec.LookPath("docker") + cmd := exec.CommandContext(ctx, "docker", "info") + err := cmd.Run() if err != nil { return false, err } return true, nil } -// isDockerEnabled checks if Docker daemon is running -func isDockerEnabled(ctx context.Context) (bool, error) { - cmd := exec.CommandContext(ctx, "docker", "info") - err := cmd.Run() - return err == nil, err -} - -// activateDocker attempts to start Docker daemon (platform-specific) -func activateDocker(ctx context.Context) (bool, error) { - // This is platform-specific and would need to be implemented - // For now, we assume Docker is already running - return isDockerEnabled(ctx) -} - -// deactivateDocker attempts to stop Docker daemon (platform-specific) -func deactivateDocker(ctx context.Context) (bool, error) { - // This is platform-specific and would need to be implemented - // For now, we don't support stopping Docker daemon - return false, fmt.Errorf("docker daemon cannot be stopped programmatically") -} - // parseMemoryLimit parses memory limit string (e.g., "512m", "1g") to bytes func parseMemoryLimit(memory string) int64 { if memory == "" { @@ -336,14 +316,17 @@ func (s *SandboxDocker) pullImageIfNeeded(ctx context.Context, imageName string) }() // Read the pull progress to completion - _, err = reader.Read(make([]byte, 1024)) - for err == nil { - _, err = reader.Read(make([]byte, 1024)) - } - - // EOF is expected when pull completes successfully - if err != nil && err.Error() != "EOF" { - return fmt.Errorf("failed to complete image pull: %w", err) + buf := make([]byte, 1024) + for { + _, readErr := reader.Read(buf) + if readErr == nil { + continue + } + // EOF is expected when pull completes successfully + if readErr.Error() != "EOF" { + return fmt.Errorf("failed to complete image pull: %w", readErr) + } + break } return nil From 0a9fb1b84eb8e5da621abda9794079472b413dd4 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Mon, 27 Oct 2025 19:09:12 +0400 Subject: [PATCH 5/7] fixing lint --- sandbox/sandbox_linux_bubblewrap.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sandbox/sandbox_linux_bubblewrap.go b/sandbox/sandbox_linux_bubblewrap.go index 09c5015..f015447 100644 --- a/sandbox/sandbox_linux_bubblewrap.go +++ b/sandbox/sandbox_linux_bubblewrap.go @@ -166,7 +166,9 @@ func (b *BubblewrapSandbox) RunSource(ctx context.Context, source string) (*type if err != nil { return nil, fmt.Errorf("failed to create source directory: %w", err) } - defer os.RemoveAll(sourceDir) + defer func() { + _ = os.RemoveAll(sourceDir) + }() // Create the script file in the source directory scriptPath := filepath.Join(sourceDir, "script.sh") @@ -205,7 +207,9 @@ func (b *BubblewrapSandbox) ExecuteWithOptions(ctx context.Context, options *Bub if err != nil { return nil, fmt.Errorf("failed to create sandbox directory: %w", err) } - defer os.RemoveAll(sandboxDir) + defer func() { + _ = os.RemoveAll(sandboxDir) + }() return b.executeInSandbox(ctx, sandboxDir, options) } @@ -345,12 +349,6 @@ func (b *BubblewrapSandbox) buildBubblewrapArgs(sandboxDir string, options *Bubb args = append(args, "--chdir", options.Chdir) } - // Add working directory if specified - if options.WorkingDir != "" { - // The working directory should already be accessible via the bind mounts - // bwrap will use it as the current working directory - } - return args } From a4ea43320b8150485ec2a8dab99684e1d368c9c3 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Mon, 27 Oct 2025 23:19:44 +0400 Subject: [PATCH 6/7] updating function signature --- examples/docker_sandbox/bash_source/main.go | 2 +- examples/docker_sandbox/python_source/main.go | 3 +- gozero.go | 13 +++++++- sandbox/sandbox.go | 2 +- sandbox/sandbox_darwin.go | 2 +- sandbox/sandbox_linux_systemd.go | 2 +- sandbox/sandbox_window.go | 2 +- sandbox/virtual_env_docker.go | 31 +++++++++++++------ 8 files changed, 40 insertions(+), 17 deletions(-) diff --git a/examples/docker_sandbox/bash_source/main.go b/examples/docker_sandbox/bash_source/main.go index 2dd584d..cf6bdfe 100644 --- a/examples/docker_sandbox/bash_source/main.go +++ b/examples/docker_sandbox/bash_source/main.go @@ -109,7 +109,7 @@ nslookup google.com 2>/dev/null || echo "DNS resolution not available" for _, test := range scripts { fmt.Printf("\n=== Running: %s ===\n", test.name) - result, err := sandboxInstance.RunSource(ctx, test.script) + result, err := sandboxInstance.RunSource(ctx, test.script, "sh") if err != nil { fmt.Printf("Error: %v\n", err) continue diff --git a/examples/docker_sandbox/python_source/main.go b/examples/docker_sandbox/python_source/main.go index 586816b..5985138 100644 --- a/examples/docker_sandbox/python_source/main.go +++ b/examples/docker_sandbox/python_source/main.go @@ -17,6 +17,7 @@ func main() { log.Printf("This example is only supported on Linux") return } + // Create Docker sandbox configuration for Python execution // Using Alpine Python image for minimal size config := &sandbox.DockerConfiguration{ @@ -276,7 +277,7 @@ except CustomError as e: for _, test := range scripts { fmt.Printf("\n=== Running: %s ===\n", test.name) - result, err := sandboxInstance.RunSource(ctx, test.script) + result, err := sandboxInstance.RunSource(ctx, test.script, "python3") if err != nil { fmt.Printf("Error: %v\n", err) continue diff --git a/gozero.go b/gozero.go index 4712a3b..ad0f028 100644 --- a/gozero.go +++ b/gozero.go @@ -107,8 +107,19 @@ func (g *Gozero) EvalWithVirtualEnv(ctx context.Context, envType VirtualEnvType, return nil, err } + // Use the engine as the interpreter + interpreter := g.Options.engine + if interpreter == "" { + // Fallback to first engine if engine not set + if len(g.Options.Engines) > 0 { + interpreter = g.Options.Engines[0] + } else { + interpreter = "sh" // Default to shell + } + } + // Execute the source code in the Docker container - result, err := dockerSandbox.RunSource(ctx, string(srcContent)) + result, err := dockerSandbox.RunSource(ctx, string(srcContent), interpreter) if err != nil { return nil, err } diff --git a/sandbox/sandbox.go b/sandbox/sandbox.go index 95c045f..1a18e74 100644 --- a/sandbox/sandbox.go +++ b/sandbox/sandbox.go @@ -9,7 +9,7 @@ import ( type Sandbox interface { Run(ctx context.Context, cmd string) (*types.Result, error) RunScript(ctx context.Context, source string) (*types.Result, error) - RunSource(ctx context.Context, source string) (*types.Result, error) + RunSource(ctx context.Context, source string, interpreter string) (*types.Result, error) Start() error Wait() error Stop() error diff --git a/sandbox/sandbox_darwin.go b/sandbox/sandbox_darwin.go index 28b254d..8c3d991 100644 --- a/sandbox/sandbox_darwin.go +++ b/sandbox/sandbox_darwin.go @@ -124,7 +124,7 @@ func (s *SandboxDarwin) RunScript(ctx context.Context, source string) (*types.Re } // RunSource writes source code to a temporary file, executes it with proper permissions, and cleans up -func (s *SandboxDarwin) RunSource(ctx context.Context, source string) (*types.Result, error) { +func (s *SandboxDarwin) RunSource(ctx context.Context, source, interpreter string) (*types.Result, error) { return nil, ErrNotImplemented } diff --git a/sandbox/sandbox_linux_systemd.go b/sandbox/sandbox_linux_systemd.go index 4cf5869..7a1cf15 100644 --- a/sandbox/sandbox_linux_systemd.go +++ b/sandbox/sandbox_linux_systemd.go @@ -133,7 +133,7 @@ func (s *SandboxLinux) RunScript(ctx context.Context, source string) (*types.Res } // RunSource writes source code to a temporary file, executes it with proper permissions, and cleans up -func (s *SandboxLinux) RunSource(ctx context.Context, source string) (*types.Result, error) { +func (s *SandboxLinux) RunSource(ctx context.Context, source string, interpreter string) (*types.Result, error) { return nil, ErrNotImplemented } diff --git a/sandbox/sandbox_window.go b/sandbox/sandbox_window.go index 89e2f76..bfac0af 100644 --- a/sandbox/sandbox_window.go +++ b/sandbox/sandbox_window.go @@ -125,7 +125,7 @@ func (s *SandboxWindows) RunScript(ctx context.Context, source string) (*types.R } // RunSource writes source code to a temporary file, executes it with proper permissions, and cleans up -func (s *SandboxWindows) RunSource(ctx context.Context, source string) (*types.Result, error) { +func (s *SandboxWindows) RunSource(ctx context.Context, source string, interpreter string) (*types.Result, error) { return nil, ErrNotImplemented } diff --git a/sandbox/virtual_env_docker.go b/sandbox/virtual_env_docker.go index 86ffa76..bff0616 100644 --- a/sandbox/virtual_env_docker.go +++ b/sandbox/virtual_env_docker.go @@ -3,6 +3,7 @@ package sandbox import ( "context" "fmt" + "log" "os/exec" "strings" "time" @@ -34,7 +35,7 @@ type SandboxDocker struct { } // NewDockerSandbox creates a new Docker-based sandbox -func NewDockerSandbox(ctx context.Context, config *DockerConfiguration) (Sandbox, error) { +func NewDockerSandbox(ctx context.Context, config *DockerConfiguration) (*SandboxDocker, error) { // Check if Docker is available if ok, err := isDockerInstalled(ctx); err != nil || !ok { return nil, fmt.Errorf("docker not available: %w", err) @@ -214,27 +215,37 @@ func (s *SandboxDocker) Run(ctx context.Context, cmd string) (*types.Result, err } // RunScript executes a script in the Docker container -func (s *SandboxDocker) RunScript(ctx context.Context, source string) (*types.Result, error) { - return s.RunSource(ctx, source) +func (s *SandboxDocker) RunScript(ctx context.Context, source string, interpreter string) (*types.Result, error) { + return s.RunSource(ctx, source, interpreter) } -// RunSource writes source code to a temporary file inside the container, executes it with proper permissions, and cleans up -func (s *SandboxDocker) RunSource(ctx context.Context, source string) (*types.Result, error) { +// RunSource writes source code to a temporary file inside the container and executes it +func (s *SandboxDocker) RunSource(ctx context.Context, source string, interpreter string) (*types.Result, error) { // Generate a temporary filename - tmpFileName := fmt.Sprintf("/tmp/gozero_script_%d.sh", time.Now().UnixNano()) + tmpFileName := fmt.Sprintf("/tmp/gozero_script_%d", time.Now().UnixNano()) + + // Check if source has a shebang - if so, use it instead of the interpreter + // Otherwise, use the provided interpreter + execCmd := fmt.Sprintf("%s %s", interpreter, tmpFileName) + if strings.HasPrefix(source, "#!") { + // Source has shebang, let it execute directly + execCmd = tmpFileName + } // Create a script that writes the source content and then executes it - scriptContent := fmt.Sprintf(`#!/bin/sh -cat > %s << 'EOF' + scriptContent := fmt.Sprintf(`cat > %s << 'EOF' %s EOF chmod +x %s exec %s -`, tmpFileName, source, tmpFileName, tmpFileName) +`, tmpFileName, source, tmpFileName, execCmd) + + log.Println(tmpFileName) + log.Println(scriptContent) // Execute the script directly cmdParts := []string{"/bin/sh", "-c", scriptContent} - return s.runCommand(ctx, cmdParts, fmt.Sprintf("sh -c 'script with %s'", tmpFileName), false, "") + return s.runCommand(ctx, cmdParts, fmt.Sprintf("exec %s", tmpFileName), false, "") } // Start is not implemented for Docker sandbox as it's stateless From a161fcfa0c744ca622b4bf64a7e13c74dbac83d8 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Wed, 5 Nov 2025 13:48:29 +0400 Subject: [PATCH 7/7] removing .ds_store --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5e0d09ef92e7e90b0499e6c9439e5cdd5d107e46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJ8r`;3?A>JE0C0t{8|L0i z0E-2HHE|9^1g1d+2351g(4Zq;GOs4ifk79|=0o#l%??HVcAQ^4U9<*rqykjnRDs7> zwpRaN;1~M;QxaEHfC}7|0=nv7`wmaa+PZk0)!G7ogImrI+zfN4VDNGb^m2@amE&hm bio9ZT?AOFO(CLUf9mt;n(}hL_ZmqyC7#9_~