quartermaster-code-runner 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quartermaster_code_runner/__init__.py +38 -0
- quartermaster_code_runner/app.py +269 -0
- quartermaster_code_runner/config.py +175 -0
- quartermaster_code_runner/errors.py +88 -0
- quartermaster_code_runner/execution.py +231 -0
- quartermaster_code_runner/images.py +397 -0
- quartermaster_code_runner/runtime/bun/Dockerfile +22 -0
- quartermaster_code_runner/runtime/bun/completions.json +34 -0
- quartermaster_code_runner/runtime/bun/entrypoint.sh +32 -0
- quartermaster_code_runner/runtime/bun/sdk.ts +87 -0
- quartermaster_code_runner/runtime/deno/Dockerfile +22 -0
- quartermaster_code_runner/runtime/deno/completions.json +34 -0
- quartermaster_code_runner/runtime/deno/entrypoint.sh +32 -0
- quartermaster_code_runner/runtime/deno/sdk.ts +88 -0
- quartermaster_code_runner/runtime/go/Dockerfile +18 -0
- quartermaster_code_runner/runtime/go/completions.json +22 -0
- quartermaster_code_runner/runtime/go/entrypoint.sh +50 -0
- quartermaster_code_runner/runtime/go/sdk.go +101 -0
- quartermaster_code_runner/runtime/node/Dockerfile +31 -0
- quartermaster_code_runner/runtime/node/completions.json +34 -0
- quartermaster_code_runner/runtime/node/entrypoint.sh +33 -0
- quartermaster_code_runner/runtime/node/mcp-client.js +274 -0
- quartermaster_code_runner/runtime/node/sdk.js +109 -0
- quartermaster_code_runner/runtime/python/Dockerfile +42 -0
- quartermaster_code_runner/runtime/python/completions.json +34 -0
- quartermaster_code_runner/runtime/python/entrypoint.sh +30 -0
- quartermaster_code_runner/runtime/python/mcp-client.py +276 -0
- quartermaster_code_runner/runtime/python/sdk.py +103 -0
- quartermaster_code_runner/runtime/rust/Cargo.toml.default +9 -0
- quartermaster_code_runner/runtime/rust/Dockerfile +27 -0
- quartermaster_code_runner/runtime/rust/completions.json +34 -0
- quartermaster_code_runner/runtime/rust/entrypoint.sh +38 -0
- quartermaster_code_runner/runtime/rust/sdk/Cargo.toml +9 -0
- quartermaster_code_runner/runtime/rust/sdk/src/lib.rs +149 -0
- quartermaster_code_runner/schemas.py +154 -0
- quartermaster_code_runner/security.py +81 -0
- quartermaster_code_runner-0.0.1.dist-info/METADATA +322 -0
- quartermaster_code_runner-0.0.1.dist-info/RECORD +40 -0
- quartermaster_code_runner-0.0.1.dist-info/WHEEL +4 -0
- quartermaster_code_runner-0.0.1.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Pydantic request/response models for code execution API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, field_validator
|
|
8
|
+
|
|
9
|
+
from quartermaster_code_runner.config import (
|
|
10
|
+
DEFAULT_IMAGE,
|
|
11
|
+
PREBUILT_IMAGE_PREFIX,
|
|
12
|
+
SUPPORTED_IMAGES,
|
|
13
|
+
get_short_image_name,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PrebuildSpec(BaseModel):
|
|
18
|
+
"""Build recipe for a prebuilt image, used for on-the-fly rebuilds."""
|
|
19
|
+
|
|
20
|
+
base_image: str
|
|
21
|
+
setup_script: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CodeExecutionRequest(BaseModel):
|
|
25
|
+
"""Request model for code execution.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
code: Source code to execute.
|
|
29
|
+
image: Runtime image (python, node, go, rust, deno, bun).
|
|
30
|
+
files: Additional files to include {filename: content}.
|
|
31
|
+
entrypoint: Custom entrypoint command (overrides default).
|
|
32
|
+
timeout: Execution timeout in seconds.
|
|
33
|
+
mem_limit: Memory limit (e.g., "256m").
|
|
34
|
+
cpu_shares: CPU shares (Docker cpu_shares).
|
|
35
|
+
disk_limit: Disk limit for tmpfs (e.g., "512m").
|
|
36
|
+
allow_network: Whether to allow outbound network access.
|
|
37
|
+
environment: Environment variables to inject.
|
|
38
|
+
prebuild_spec: Optional prebuild specification for on-the-fly rebuilds.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
code: str
|
|
42
|
+
image: str = DEFAULT_IMAGE
|
|
43
|
+
files: Optional[dict[str, str]] = None
|
|
44
|
+
entrypoint: Optional[str] = None
|
|
45
|
+
timeout: Optional[int] = None
|
|
46
|
+
mem_limit: Optional[str] = None
|
|
47
|
+
cpu_shares: Optional[int] = None
|
|
48
|
+
disk_limit: Optional[str] = None
|
|
49
|
+
allow_network: bool = False
|
|
50
|
+
environment: Optional[dict[str, str]] = None
|
|
51
|
+
prebuild_spec: Optional[PrebuildSpec] = None
|
|
52
|
+
|
|
53
|
+
@field_validator("image")
|
|
54
|
+
@classmethod
|
|
55
|
+
def validate_image(cls, v: str) -> str:
|
|
56
|
+
short_name = get_short_image_name(v)
|
|
57
|
+
if short_name not in SUPPORTED_IMAGES:
|
|
58
|
+
full_prebuilt = (
|
|
59
|
+
v
|
|
60
|
+
if v.startswith(PREBUILT_IMAGE_PREFIX)
|
|
61
|
+
else f"{PREBUILT_IMAGE_PREFIX}{v}"
|
|
62
|
+
)
|
|
63
|
+
return full_prebuilt
|
|
64
|
+
return short_name
|
|
65
|
+
|
|
66
|
+
@field_validator("environment")
|
|
67
|
+
@classmethod
|
|
68
|
+
def validate_environment(
|
|
69
|
+
cls, v: Optional[dict[str, str]]
|
|
70
|
+
) -> Optional[dict[str, str]]:
|
|
71
|
+
if v is None:
|
|
72
|
+
return v
|
|
73
|
+
reserved_keys = {
|
|
74
|
+
"ENCODED_CODE",
|
|
75
|
+
"ENCODED_FILES",
|
|
76
|
+
"CUSTOM_ENTRYPOINT",
|
|
77
|
+
}
|
|
78
|
+
for key in v:
|
|
79
|
+
if key in reserved_keys:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"Environment variable '{key}' is reserved and cannot be set."
|
|
82
|
+
)
|
|
83
|
+
return v
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class CodeExecutionResponse(BaseModel):
|
|
87
|
+
"""Response model for code execution."""
|
|
88
|
+
|
|
89
|
+
stdout: str
|
|
90
|
+
stderr: str
|
|
91
|
+
exit_code: int
|
|
92
|
+
execution_time: float
|
|
93
|
+
metadata: Optional[dict[str, object]] = None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class PrebuildRequest(BaseModel):
|
|
97
|
+
"""Request model for building a prebuilt image."""
|
|
98
|
+
|
|
99
|
+
tag: str
|
|
100
|
+
base_image: str = DEFAULT_IMAGE
|
|
101
|
+
setup_script: str
|
|
102
|
+
|
|
103
|
+
@field_validator("base_image")
|
|
104
|
+
@classmethod
|
|
105
|
+
def validate_base_image(cls, v: str) -> str:
|
|
106
|
+
short_name = get_short_image_name(v)
|
|
107
|
+
if short_name not in SUPPORTED_IMAGES:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"Unsupported base image '{v}'. "
|
|
110
|
+
f"Supported: {', '.join(SUPPORTED_IMAGES)}"
|
|
111
|
+
)
|
|
112
|
+
return short_name
|
|
113
|
+
|
|
114
|
+
@field_validator("tag")
|
|
115
|
+
@classmethod
|
|
116
|
+
def validate_tag(cls, v: str) -> str:
|
|
117
|
+
v = v.strip()
|
|
118
|
+
if not v:
|
|
119
|
+
raise ValueError("Tag cannot be empty.")
|
|
120
|
+
if v.startswith(PREBUILT_IMAGE_PREFIX):
|
|
121
|
+
v = v[len(PREBUILT_IMAGE_PREFIX) :]
|
|
122
|
+
if not all(c.isalnum() or c in "-_." for c in v):
|
|
123
|
+
raise ValueError(
|
|
124
|
+
"Tag must contain only alphanumeric characters, "
|
|
125
|
+
"hyphens, underscores, or dots."
|
|
126
|
+
)
|
|
127
|
+
return v
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class HealthResponse(BaseModel):
|
|
131
|
+
"""Health check response."""
|
|
132
|
+
|
|
133
|
+
status: str
|
|
134
|
+
docker_connected: bool
|
|
135
|
+
auth_enabled: bool
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class RuntimeInfo(BaseModel):
|
|
139
|
+
"""Information about a single runtime."""
|
|
140
|
+
|
|
141
|
+
id: str
|
|
142
|
+
name: str
|
|
143
|
+
description: str
|
|
144
|
+
default_entrypoint: str
|
|
145
|
+
file_extension: str
|
|
146
|
+
main_file: str
|
|
147
|
+
completions: list[dict[str, object]] = []
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class RuntimesResponse(BaseModel):
|
|
151
|
+
"""Response for listing available runtimes."""
|
|
152
|
+
|
|
153
|
+
images: list[RuntimeInfo]
|
|
154
|
+
default: str
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""API key authentication middleware.
|
|
2
|
+
|
|
3
|
+
Supports two modes:
|
|
4
|
+
- API key via X-API-Key header (multiple keys, comma-separated)
|
|
5
|
+
- Bearer token via Authorization header
|
|
6
|
+
|
|
7
|
+
When no keys/tokens are configured, authentication is disabled and
|
|
8
|
+
all requests are allowed through.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import hmac
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from fastapi import HTTPException, Request, Security
|
|
17
|
+
from fastapi.security import APIKeyHeader
|
|
18
|
+
|
|
19
|
+
API_KEY_NAME = "X-API-Key"
|
|
20
|
+
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
|
|
21
|
+
|
|
22
|
+
# Populated at startup from settings
|
|
23
|
+
_api_keys: list[str] = []
|
|
24
|
+
_auth_token: str | None = None
|
|
25
|
+
_auth_enabled: bool = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def configure_auth(
|
|
29
|
+
api_keys: list[str] | None = None,
|
|
30
|
+
auth_token: str | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Configure authentication at startup.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
api_keys: List of valid API keys for X-API-Key header.
|
|
36
|
+
auth_token: Bearer token for Authorization header.
|
|
37
|
+
"""
|
|
38
|
+
global _api_keys, _auth_token, _auth_enabled
|
|
39
|
+
_api_keys = list(api_keys) if api_keys else []
|
|
40
|
+
_auth_token = auth_token
|
|
41
|
+
_auth_enabled = bool(_api_keys) or bool(_auth_token)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def verify_auth(
|
|
45
|
+
request: Request,
|
|
46
|
+
api_key: Optional[str] = Security(api_key_header),
|
|
47
|
+
) -> Optional[str]:
|
|
48
|
+
"""Verify request authentication.
|
|
49
|
+
|
|
50
|
+
Checks X-API-Key header first, then Authorization Bearer token.
|
|
51
|
+
If no auth is configured, allows all requests.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
The authenticated key/token, or None if auth is disabled.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
HTTPException: If authentication fails.
|
|
58
|
+
"""
|
|
59
|
+
if not _auth_enabled:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
# Check X-API-Key header
|
|
63
|
+
if api_key and any(hmac.compare_digest(api_key, k) for k in _api_keys):
|
|
64
|
+
return api_key
|
|
65
|
+
|
|
66
|
+
# Check Authorization Bearer token
|
|
67
|
+
auth_header = request.headers.get("Authorization", "")
|
|
68
|
+
if auth_header.startswith("Bearer ") and _auth_token:
|
|
69
|
+
token = auth_header[7:]
|
|
70
|
+
if hmac.compare_digest(token, _auth_token):
|
|
71
|
+
return token
|
|
72
|
+
|
|
73
|
+
# If we have an API key but it's invalid
|
|
74
|
+
if api_key:
|
|
75
|
+
raise HTTPException(status_code=403, detail="Invalid API Key")
|
|
76
|
+
|
|
77
|
+
# No credentials provided
|
|
78
|
+
raise HTTPException(
|
|
79
|
+
status_code=401,
|
|
80
|
+
detail="Not authenticated. Provide X-API-Key header or Authorization Bearer token.",
|
|
81
|
+
)
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quartermaster-code-runner
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Secure sandboxed code execution service supporting Python, Node.js, Go, Rust, Deno, and Bun via Docker containers
|
|
5
|
+
Project-URL: Homepage, https://github.com/MindMadeLab/quartermaster-sdk-py
|
|
6
|
+
Project-URL: Repository, https://github.com/MindMadeLab/quartermaster-sdk-py
|
|
7
|
+
Project-URL: Issues, https://github.com/MindMadeLab/quartermaster-sdk-py/issues
|
|
8
|
+
Author-email: MindMade <info@mindmade.io>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,code-execution,code-runner,docker,sandbox,security
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: docker>=7.0.0
|
|
23
|
+
Requires-Dist: fastapi>=0.100.0
|
|
24
|
+
Requires-Dist: httpx>=0.25.0
|
|
25
|
+
Requires-Dist: pydantic>=2.0.0
|
|
26
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
27
|
+
Requires-Dist: uvicorn[standard]>=0.20.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: filelock>=3.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: types-docker>=7.0.0; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# quartermaster-code-runner
|
|
39
|
+
|
|
40
|
+
Secure sandboxed code execution service. Runs untrusted code in isolated Docker containers with support for Python, Node.js, Go, Rust, Deno, and Bun.
|
|
41
|
+
|
|
42
|
+
[](https://pypi.org/project/quartermaster-code-runner/)
|
|
43
|
+
[](https://www.python.org/downloads/)
|
|
44
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- **6 Runtimes**: Python, Node.js, Go, Rust, Deno, Bun
|
|
49
|
+
- **Docker Isolation**: Each execution runs in its own container with read-only filesystem
|
|
50
|
+
- **Resource Limits**: Configurable CPU, memory, and disk quotas per execution
|
|
51
|
+
- **Network Control**: Outbound network access toggled per request
|
|
52
|
+
- **Timeout Enforcement**: Hard container kill after configurable duration
|
|
53
|
+
- **File Injection**: Send multiple source files alongside the main code
|
|
54
|
+
- **Prebuilt Images**: Extend base runtimes with custom dependencies
|
|
55
|
+
- **Auto Cleanup**: Orphaned containers and old images cleaned up automatically
|
|
56
|
+
- **API Key Auth**: Optional authentication via `X-API-Key` header or Bearer token
|
|
57
|
+
- **Health Checks**: Built-in `/health` endpoint with Docker connectivity status
|
|
58
|
+
- **Standalone**: No dependency on other Quartermaster packages
|
|
59
|
+
|
|
60
|
+
## Supported Runtimes
|
|
61
|
+
|
|
62
|
+
| Runtime | Image Name | Sandbox |
|
|
63
|
+
|---------|-----------|---------|
|
|
64
|
+
| Python | `python` | Alpine Linux |
|
|
65
|
+
| Node.js | `node` | Alpine Linux |
|
|
66
|
+
| Go | `go` | Alpine Linux |
|
|
67
|
+
| Rust | `rust` | Alpine Linux |
|
|
68
|
+
| Deno | `deno` | Alpine Linux |
|
|
69
|
+
| Bun | `bun` | Alpine Linux |
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
### As a Service (recommended)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install quartermaster-code-runner
|
|
77
|
+
uvicorn quartermaster_code_runner.app:app --host 0.0.0.0 --port 8000
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Requires Docker to be running and accessible.
|
|
81
|
+
|
|
82
|
+
### Docker Compose
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
services:
|
|
86
|
+
code-runner:
|
|
87
|
+
build: .
|
|
88
|
+
ports:
|
|
89
|
+
- "8000:8000"
|
|
90
|
+
volumes:
|
|
91
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
92
|
+
environment:
|
|
93
|
+
- LOG_LEVEL=info
|
|
94
|
+
- DEFAULT_TIMEOUT=30
|
|
95
|
+
- DEFAULT_MEMORY=256m
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Quick Start
|
|
99
|
+
|
|
100
|
+
### Execute Python Code
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
curl -X POST http://localhost:8000/run \
|
|
104
|
+
-H "Content-Type: application/json" \
|
|
105
|
+
-d '{
|
|
106
|
+
"code": "print(sum(range(1, 101)))",
|
|
107
|
+
"image": "python"
|
|
108
|
+
}'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Response:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"stdout": "5050\n",
|
|
116
|
+
"stderr": "",
|
|
117
|
+
"exit_code": 0,
|
|
118
|
+
"execution_time": 0.3421
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Execute Node.js Code
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
curl -X POST http://localhost:8000/run \
|
|
126
|
+
-H "Content-Type: application/json" \
|
|
127
|
+
-d '{
|
|
128
|
+
"code": "console.log(Array.from({length: 5}, (_, i) => i * i))",
|
|
129
|
+
"image": "node"
|
|
130
|
+
}'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### With Environment Variables and Resource Limits
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
curl -X POST http://localhost:8000/run \
|
|
137
|
+
-H "Content-Type: application/json" \
|
|
138
|
+
-d '{
|
|
139
|
+
"code": "import os; print(os.environ[\"API_KEY\"])",
|
|
140
|
+
"image": "python",
|
|
141
|
+
"timeout": 10,
|
|
142
|
+
"mem_limit": "128m",
|
|
143
|
+
"cpu_shares": 256,
|
|
144
|
+
"allow_network": false,
|
|
145
|
+
"environment": {
|
|
146
|
+
"API_KEY": "secret-123"
|
|
147
|
+
}
|
|
148
|
+
}'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### With Additional Files
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
curl -X POST http://localhost:8000/run \
|
|
155
|
+
-H "Content-Type: application/json" \
|
|
156
|
+
-d '{
|
|
157
|
+
"code": "from helpers import greet; print(greet(\"world\"))",
|
|
158
|
+
"image": "python",
|
|
159
|
+
"files": {
|
|
160
|
+
"helpers.py": "def greet(name): return f\"Hello, {name}!\""
|
|
161
|
+
}
|
|
162
|
+
}'
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Python Client Usage
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from quartermaster_code_runner import CodeExecutionRequest
|
|
169
|
+
|
|
170
|
+
request = CodeExecutionRequest(
|
|
171
|
+
code="print('Hello from the sandbox')",
|
|
172
|
+
image="python",
|
|
173
|
+
timeout=10,
|
|
174
|
+
mem_limit="128m",
|
|
175
|
+
allow_network=False,
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## API Reference
|
|
180
|
+
|
|
181
|
+
### POST /run
|
|
182
|
+
|
|
183
|
+
Execute code in a sandboxed Docker container.
|
|
184
|
+
|
|
185
|
+
**Request body** (`CodeExecutionRequest`):
|
|
186
|
+
|
|
187
|
+
| Field | Type | Default | Description |
|
|
188
|
+
|-------|------|---------|-------------|
|
|
189
|
+
| `code` | `string` | required | Source code to execute |
|
|
190
|
+
| `image` | `string` | `"python"` | Runtime: `python`, `node`, `go`, `rust`, `deno`, `bun` |
|
|
191
|
+
| `files` | `dict[str, str]` | `null` | Additional files `{filename: content}` |
|
|
192
|
+
| `entrypoint` | `string` | `null` | Custom entrypoint command |
|
|
193
|
+
| `timeout` | `int` | `null` (uses server default) | Execution timeout in seconds |
|
|
194
|
+
| `mem_limit` | `string` | `null` (uses server default) | Memory limit, e.g. `"256m"` |
|
|
195
|
+
| `cpu_shares` | `int` | `null` (uses server default) | Docker CPU shares |
|
|
196
|
+
| `disk_limit` | `string` | `null` (uses server default) | Disk limit for tmpfs |
|
|
197
|
+
| `allow_network` | `bool` | `true` | Allow outbound network access |
|
|
198
|
+
| `environment` | `dict[str, str]` | `null` | Environment variables to inject |
|
|
199
|
+
| `prebuild_spec` | `object` | `null` | Prebuild spec: `{base_image, setup_script}` |
|
|
200
|
+
|
|
201
|
+
**Response** (`CodeExecutionResponse`):
|
|
202
|
+
|
|
203
|
+
| Field | Type | Description |
|
|
204
|
+
|-------|------|-------------|
|
|
205
|
+
| `stdout` | `string` | Standard output |
|
|
206
|
+
| `stderr` | `string` | Standard error |
|
|
207
|
+
| `exit_code` | `int` | Process exit code |
|
|
208
|
+
| `execution_time` | `float` | Wall-clock time in seconds |
|
|
209
|
+
| `metadata` | `dict` | Optional metadata from the container |
|
|
210
|
+
|
|
211
|
+
### GET /health
|
|
212
|
+
|
|
213
|
+
Returns service health and Docker connectivity.
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"status": "ok",
|
|
218
|
+
"docker_connected": true,
|
|
219
|
+
"auth_enabled": false
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### GET /runtimes
|
|
224
|
+
|
|
225
|
+
List available runtime images with metadata (alias for `/images`).
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"images": [
|
|
230
|
+
{
|
|
231
|
+
"id": "code-runner-python",
|
|
232
|
+
"name": "Python",
|
|
233
|
+
"description": "Python runtime",
|
|
234
|
+
"default_entrypoint": "python main.py",
|
|
235
|
+
"file_extension": ".py",
|
|
236
|
+
"main_file": "main.py"
|
|
237
|
+
}
|
|
238
|
+
],
|
|
239
|
+
"default": "code-runner-python"
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### POST /prebuild
|
|
244
|
+
|
|
245
|
+
Build a prebuilt image extending a base runtime with custom dependencies.
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
curl -X POST http://localhost:8000/prebuild \
|
|
249
|
+
-H "Content-Type: application/json" \
|
|
250
|
+
-d '{
|
|
251
|
+
"tag": "my-scipy",
|
|
252
|
+
"base_image": "python",
|
|
253
|
+
"setup_script": "pip install scipy numpy"
|
|
254
|
+
}'
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### GET /prebuilds
|
|
258
|
+
|
|
259
|
+
List all prebuilt images. `DELETE /prebuilds/{tag}` removes one.
|
|
260
|
+
|
|
261
|
+
## Security Model
|
|
262
|
+
|
|
263
|
+
Each execution is isolated with multiple layers:
|
|
264
|
+
|
|
265
|
+
1. **Container isolation** -- separate Docker container per execution
|
|
266
|
+
2. **Read-only filesystem** -- containers run with `read_only=True`; writes go to tmpfs
|
|
267
|
+
3. **Network control** -- outbound access disabled when `allow_network=false`
|
|
268
|
+
4. **Resource limits** -- CPU shares, memory limit, disk quota enforced by Docker
|
|
269
|
+
5. **Timeout enforcement** -- containers killed after the configured timeout
|
|
270
|
+
6. **Input validation** -- code size capped at 1 MB; reserved env vars blocked; path traversal rejected
|
|
271
|
+
|
|
272
|
+
Reserved environment variables (`ENCODED_CODE`, `ENCODED_FILES`, `CUSTOM_ENTRYPOINT`) cannot be overridden by callers.
|
|
273
|
+
|
|
274
|
+
## Configuration
|
|
275
|
+
|
|
276
|
+
All settings load from environment variables with sensible defaults. Use a `.env` file or set them directly.
|
|
277
|
+
|
|
278
|
+
### Environment Variables
|
|
279
|
+
|
|
280
|
+
| Variable | Default | Description |
|
|
281
|
+
|----------|---------|-------------|
|
|
282
|
+
| `HOST` | `0.0.0.0` | Bind address |
|
|
283
|
+
| `PORT` | `8000` | Bind port |
|
|
284
|
+
| `LOG_LEVEL` | `info` | Log level (`debug`, `info`, `warning`, `error`) |
|
|
285
|
+
| `DEFAULT_TIMEOUT` | `30` | Default execution timeout (seconds) |
|
|
286
|
+
| `MAX_TIMEOUT` | `300` | Hard maximum timeout (seconds) |
|
|
287
|
+
| `DEFAULT_MEMORY` | `256m` | Default memory limit |
|
|
288
|
+
| `MAX_MEMORY_MB` | `2048` | Maximum memory (MB) |
|
|
289
|
+
| `DEFAULT_CPU_SHARES` | `512` | Default Docker CPU shares |
|
|
290
|
+
| `MAX_CPU_CORES` | `4.0` | Maximum CPU cores |
|
|
291
|
+
| `DEFAULT_DISK` | `512m` | Default tmpfs disk limit |
|
|
292
|
+
| `MAX_DISK_MB` | `5000` | Maximum disk (MB) |
|
|
293
|
+
| `DOCKER_SOCKET` | `/var/run/docker.sock` | Docker socket path |
|
|
294
|
+
| `AUTH_TOKEN` | _(none)_ | Bearer token for authentication |
|
|
295
|
+
| `CODE_RUNNER_API_KEYS` | _(none)_ | Comma-separated API keys for `X-API-Key` header |
|
|
296
|
+
| `CLEANUP_INTERVAL_HOURS` | `24` | Hours between automatic prebuild cleanup |
|
|
297
|
+
| `CLEANUP_MAX_AGE_DAYS` | `7` | Max age (days) for prebuilt images before cleanup |
|
|
298
|
+
| `RUNTIME_DIR` | _(auto)_ | Path to runtime Dockerfile directories |
|
|
299
|
+
|
|
300
|
+
When neither `AUTH_TOKEN` nor `CODE_RUNNER_API_KEYS` is set, authentication is disabled.
|
|
301
|
+
|
|
302
|
+
### Error Types
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from quartermaster_code_runner import (
|
|
306
|
+
CodeRunnerError, # Base exception
|
|
307
|
+
DockerError, # Docker communication failure
|
|
308
|
+
ExecutionError, # Code execution failure
|
|
309
|
+
InvalidLanguageError, # Unsupported runtime
|
|
310
|
+
ResourceExhaustedError, # Resource limit exceeded
|
|
311
|
+
RuntimeNotAvailableError, # Runtime image not found
|
|
312
|
+
TimeoutError, # Execution timeout
|
|
313
|
+
)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Contributing
|
|
317
|
+
|
|
318
|
+
Contributions welcome. See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines.
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
Apache License 2.0. See [LICENSE](../LICENSE) for details.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
quartermaster_code_runner/__init__.py,sha256=JGQbVP4IjEaknzHm5MffiJZ6jpCjtclxzyEq-IK5YCc,921
|
|
2
|
+
quartermaster_code_runner/app.py,sha256=o7tZ7TO0QWpWCSR98aOzLm72p1KryW5-MXLrpcu4GHY,7956
|
|
3
|
+
quartermaster_code_runner/config.py,sha256=4Y_uCigeVI-HZpls79eSDi0IvZjKH9-eioUQ7dVvOHE,5722
|
|
4
|
+
quartermaster_code_runner/errors.py,sha256=8TXnhLjbq-oxaxYUphtZafCqMan8zGqC7c_50hTv_K0,2112
|
|
5
|
+
quartermaster_code_runner/execution.py,sha256=1GITLL7_KPM1tgjZRgbttvKPO4wKAUoNACE9XeSoFrw,7539
|
|
6
|
+
quartermaster_code_runner/images.py,sha256=GTswsz2adF1ckPiumoMG5zsc3o5SNPMlz9I69j65Ao8,12255
|
|
7
|
+
quartermaster_code_runner/schemas.py,sha256=afljp0QqmigWtL98w7xS14Obk7tIKUIyHmdcHv6kVZQ,4344
|
|
8
|
+
quartermaster_code_runner/security.py,sha256=PjOJwmDM7tGvc6_PG7yQn9aD3IpvDe7dRjZw9DpbR84,2321
|
|
9
|
+
quartermaster_code_runner/runtime/bun/Dockerfile,sha256=TqfFRomPZDTOXqnLr3OoGQCvMq1aFNDD1pR80weXGHQ,552
|
|
10
|
+
quartermaster_code_runner/runtime/bun/completions.json,sha256=XGGmMUlQtR1xaH8XRO2-3XJmJ8m33zx2pUzWVOA4Cbc,1010
|
|
11
|
+
quartermaster_code_runner/runtime/bun/entrypoint.sh,sha256=JBAp3A2qW7GyGDUqbaiI3hzud6YLElttFRNefSSgvIY,730
|
|
12
|
+
quartermaster_code_runner/runtime/bun/sdk.ts,sha256=50yeEuzjxkYxM1V_iVXRGwp87bK2JAV_Pe4ekugztH8,2527
|
|
13
|
+
quartermaster_code_runner/runtime/deno/Dockerfile,sha256=O4IRPBZezD65W0MOlw7-2Y-Kb2FvnSHVRwiQyXb4Fug,581
|
|
14
|
+
quartermaster_code_runner/runtime/deno/completions.json,sha256=1ZNOI_q7wdzpqnQUBzddws1EQtd7gjhaOJFNf7ILOG0,1028
|
|
15
|
+
quartermaster_code_runner/runtime/deno/entrypoint.sh,sha256=3qy3yni0Vx26b2j9qDmMyTI5X7U3Xp99yVRgfHDaPA8,790
|
|
16
|
+
quartermaster_code_runner/runtime/deno/sdk.ts,sha256=4XlAqOXoUYnEfXEOCCu5gmxBbwaBqywvwdsMmZTOsbI,2547
|
|
17
|
+
quartermaster_code_runner/runtime/go/Dockerfile,sha256=TuUdzlKMxUE4DAcQVQ2U62FjrlTLoujOj9iajSTS4f8,373
|
|
18
|
+
quartermaster_code_runner/runtime/go/completions.json,sha256=U9RqmoFWH3BaXZQQZBvmoQqj2XsKAl0g1GHz-GMu0dE,654
|
|
19
|
+
quartermaster_code_runner/runtime/go/entrypoint.sh,sha256=So4SfF9TUbREvvD28DYEuWq4qoWvdpG9W-E6EBn-PE8,1158
|
|
20
|
+
quartermaster_code_runner/runtime/go/sdk.go,sha256=wPQO8pEfYpez2Vk7CeH6xtyROeaE5xyUMUb2iZDsvbg,2749
|
|
21
|
+
quartermaster_code_runner/runtime/node/Dockerfile,sha256=OsNWW5yiPpzm7HPmHvQPlCUrmj7zlF81pYNSxgSoLjM,865
|
|
22
|
+
quartermaster_code_runner/runtime/node/completions.json,sha256=gFqm6zHJFle58qcjfzIRgM9l8K5taatAiNww77F7oaU,1040
|
|
23
|
+
quartermaster_code_runner/runtime/node/entrypoint.sh,sha256=ocF3ZmyKdf8UF0W-6lfy92oTT6Nfy-CYxUuI6pF565I,788
|
|
24
|
+
quartermaster_code_runner/runtime/node/mcp-client.js,sha256=u_I9RCDAsaXfUrMMh4IIViswIDwS2WDVviGVIWqABHM,7478
|
|
25
|
+
quartermaster_code_runner/runtime/node/sdk.js,sha256=gaxpnRvSQlieGK3Sb_CMFiG6KTrU_VEo1aDXai1gA2Y,3735
|
|
26
|
+
quartermaster_code_runner/runtime/python/Dockerfile,sha256=G7rB7o3dcJ1ao5yhuwuBMnCJosNu_rtsXTdjoJSc8LM,1082
|
|
27
|
+
quartermaster_code_runner/runtime/python/completions.json,sha256=8TzaFwd0rGrEezTJtQS5Eo2BCWZcOSTdu6vCev5Dgro,971
|
|
28
|
+
quartermaster_code_runner/runtime/python/entrypoint.sh,sha256=zlB-MTfRaDBn7y7oxUMCxuzqwaWkox4v_EHKkIkw1zQ,663
|
|
29
|
+
quartermaster_code_runner/runtime/python/mcp-client.py,sha256=sFYP6A9e6adnZhe8kWokCqWIlhkbcy4tLSmC0feBxcw,9256
|
|
30
|
+
quartermaster_code_runner/runtime/python/sdk.py,sha256=q8EWYjNj7aZFnapcNdg8PE-nTEgDEufzi5d-aqgWM3c,2885
|
|
31
|
+
quartermaster_code_runner/runtime/rust/Cargo.toml.default,sha256=xvpoOpuOy2feL7R85bXW__304gX0GvHTouCm1hm5nm0,178
|
|
32
|
+
quartermaster_code_runner/runtime/rust/Dockerfile,sha256=MA6oXGB-9ywPoZ20OQInZtOmsbh11sqR_RLUbLplaaw,707
|
|
33
|
+
quartermaster_code_runner/runtime/rust/completions.json,sha256=_64n1o3bHW0v1GWw7-pa91LeCeFrjkU3UVAvz7JGE6c,992
|
|
34
|
+
quartermaster_code_runner/runtime/rust/entrypoint.sh,sha256=zy3Wq1OHhMkbfwbSemheKkhhfArIuSYtizUHLJkY8eA,969
|
|
35
|
+
quartermaster_code_runner/runtime/rust/sdk/Cargo.toml,sha256=EsV1PlN0GYlJx6sphZQRh0VAQeGbijQx1om7LjOxpog,155
|
|
36
|
+
quartermaster_code_runner/runtime/rust/sdk/src/lib.rs,sha256=90CPtVKBXlIIewyvuAT34wWFxB7k2gDf_TzjGUtlFvY,4074
|
|
37
|
+
quartermaster_code_runner-0.0.1.dist-info/METADATA,sha256=opoD6hGzmMpKB9ZtrCkngKXDixEsOhYpL9JwalUeWcw,10352
|
|
38
|
+
quartermaster_code_runner-0.0.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
39
|
+
quartermaster_code_runner-0.0.1.dist-info/licenses/LICENSE,sha256=E-YezCKmkJWerKMBOH9wLYcgDTX9MPImxNow_EwXsTs,10766
|
|
40
|
+
quartermaster_code_runner-0.0.1.dist-info/RECORD,,
|