Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion examples/code-interpreter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,29 @@ The script creates a Sandbox + CodeInterpreter, runs a Python code snippet and p
- `SANDBOX_DOMAIN`: Sandbox service address (default: `localhost:8080`)
- `SANDBOX_API_KEY`: API key if your server requires authentication
- `SANDBOX_IMAGE`: Sandbox image to use (default: `opensandbox/code-interpreter:latest`)
- `PYTHON_VERSION`: Python version inside the sandbox (default: `3.11`)

## Example output

```text
=== Python example ===
[Python stdout] Hello from Python!

[Python result] {'py': '3.14.2', 'sum': 4}

=== Java example ===
[Java stdout] Hello from Java!

[Java stdout] 2 + 3 = 5

[Java result] 5

=== Go example ===
[Go stdout] Hello from Go!
3 + 4 = 7


=== TypeScript example ===
[TypeScript stdout] Hello from TypeScript!

[TypeScript stdout] sum = 6
```
75 changes: 62 additions & 13 deletions examples/code-interpreter/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@
from opensandbox import Sandbox
from opensandbox.config import ConnectionConfig


async def main() -> None:
domain = os.getenv("SANDBOX_DOMAIN", "localhost:8080")
api_key = os.getenv("SANDBOX_API_KEY")
image = os.getenv("SANDBOX_IMAGE", "opensandbox/code-interpreter:latest")
entry_point = "/opt/opensandbox/code-interpreter.sh"
python_version = os.getenv("PYTHON_VERSION", "3.11")

config = ConnectionConfig(
domain=domain,
Expand All @@ -36,27 +35,77 @@ async def main() -> None:
sandbox = await Sandbox.create(
image,
connection_config=config,
entrypoint=[entry_point],
env={"PYTHON_VERSION": python_version},
entrypoint=["/opt/opensandbox/code-interpreter.sh"]
)

async with sandbox:
interpreter = await CodeInterpreter.create(sandbox=sandbox)
ctx = await interpreter.codes.create_context(SupportedLanguage.PYTHON)

execution = await interpreter.codes.run(
# Python example: show runtime info and return a simple calculation.
py_ctx = await interpreter.codes.create_context(SupportedLanguage.PYTHON)
py_exec = await interpreter.codes.run(
"import platform\n"
"print('hello from code-interpreter sandbox')\n"
"result = {'py': platform.python_version(), 'sum': 1 + 1}\n"
"print('Hello from Python!')\n"
"result = {'py': platform.python_version(), 'sum': 2 + 2}\n"
"result",
context=py_ctx,
)
print("\n=== Python example ===")
for msg in py_exec.logs.stdout:
print(f"[Python stdout] {msg.text}")
if py_exec.result:
for res in py_exec.result:
print(f"[Python result] {res.text}")

# Java example: print to stdout and return the final result line.
java_ctx = await interpreter.codes.create_context(SupportedLanguage.JAVA)
java_exec = await interpreter.codes.run(
"System.out.println(\"Hello from Java!\");\n"
"int result = 2 + 3;\n"
"System.out.println(\"2 + 3 = \" + result);\n"
"result",
context=ctx,
context=java_ctx,
)
print("\n=== Java example ===")
for msg in java_exec.logs.stdout:
print(f"[Java stdout] {msg.text}")
if java_exec.result:
for res in java_exec.result:
print(f"[Java result] {res.text}")
if java_exec.error:
print(f"[Java error] {java_exec.error.name}: {java_exec.error.value}")

for msg in execution.logs.stdout:
print(f"[stdout] {msg.text}")
# Go example: print logs and demonstrate a main function structure.
go_ctx = await interpreter.codes.create_context(SupportedLanguage.GO)
go_exec = await interpreter.codes.run(
"package main\n"
"import \"fmt\"\n"
"func main() {\n"
" fmt.Println(\"Hello from Go!\")\n"
" sum := 3 + 4\n"
" fmt.Println(\"3 + 4 =\", sum)\n"
"}",
context=go_ctx,
)
print("\n=== Go example ===")
for msg in go_exec.logs.stdout:
print(f"[Go stdout] {msg.text}")
if go_exec.error:
print(f"[Go error] {go_exec.error.name}: {go_exec.error.value}")

if execution.result:
print(f"[result] {execution.result[0].text}")
# TypeScript example: use typing and sum an array.
ts_ctx = await interpreter.codes.create_context(SupportedLanguage.TYPESCRIPT)
ts_exec = await interpreter.codes.run(
"console.log('Hello from TypeScript!');\n"
"const nums: number[] = [1, 2, 3];\n"
"console.log('sum =', nums.reduce((a, b) => a + b, 0));",
context=ts_ctx,
)
print("\n=== TypeScript example ===")
for msg in ts_exec.logs.stdout:
print(f"[TypeScript stdout] {msg.text}")
if ts_exec.error:
print(f"[TypeScript error] {ts_exec.error.name}: {ts_exec.error.value}")

await interpreter.kill()

Expand Down
1 change: 0 additions & 1 deletion server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ cp example.config.toml ~/.sandbox.toml
# Limit fork bombs and optionally enforce seccomp / read-only rootfs
pids_limit = 512 # set to null to disable
seccomp_profile = "" # path or profile name; empty uses Docker default
read_only_rootfs = false # enable for stricter isolation if your image allows it
```
Further reading on Docker container security: https://docs.docker.com/engine/security/

Expand Down
1 change: 0 additions & 1 deletion server/README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ apparmor_profile = ""
# 限制进程数量,可选的 seccomp/只读根文件系统
pids_limit = 512 # 设为 null 可关闭
seccomp_profile = "" # 配置文件路径或名称;为空使用 Docker 默认
read_only_rootfs = false # 如果镜像允许写 /,可开启以进一步隔离
```
更多 Docker 安全参考:https://docs.docker.com/engine/security/

Expand Down
2 changes: 0 additions & 2 deletions server/example.config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ apparmor_profile = ""
pids_limit = 512
# Seccomp profile: empty string uses Docker default; set to an absolute path for a custom profile
seccomp_profile = ""
# Make root filesystem read-only to avoid host writes via mounted volumes; disable if your image needs write access to /
read_only_rootfs = true


# -----------------------------------------------------------------
Expand Down
2 changes: 0 additions & 2 deletions server/example.config.zh.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ apparmor_profile = ""
pids_limit = 512
# Seccomp profile: empty string uses Docker default; set to an absolute path for a custom profile
seccomp_profile = ""
# Make root filesystem read-only to avoid host writes via mounted volumes; disable if your image needs write access to /
read_only_rootfs = true


# -----------------------------------------------------------------
Expand Down
4 changes: 0 additions & 4 deletions server/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,6 @@ class DockerConfig(BaseModel):
ge=1,
description="Maximum number of processes allowed per sandbox container. Set to null to disable the limit.",
)
read_only_rootfs: bool = Field(
default=False,
description="Mount the container root filesystem as read-only. Disable if images need write access to /.",
)


class AppConfig(BaseModel):
Expand Down
2 changes: 0 additions & 2 deletions server/src/services/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,8 +799,6 @@ def _provision_sandbox(
host_config_kwargs["cap_drop"] = docker_cfg.drop_capabilities
if docker_cfg.pids_limit is not None:
host_config_kwargs["pids_limit"] = docker_cfg.pids_limit
if docker_cfg.read_only_rootfs:
host_config_kwargs["read_only"] = True
if mem_limit:
host_config_kwargs["mem_limit"] = mem_limit
if nano_cpus:
Expand Down