docs: add CodeBuddy integration guide and example#718
Conversation
| && codebuddy --version \ | ||
| && npm cache clean --force \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
There was a problem hiding this comment.
Missing WORKDIR /workspace in this code snippet. The actual Dockerfile at examples/codebuddy-integration/template/Dockerfile:22 has a WORKDIR /workspace instruction between the RUN and ENTRYPOINT directives. A reader who copies this snippet verbatim would produce a functionally different Dockerfile — the shell's working directory would default to /, not /workspace, affecting where codebuddy -p and python3 commands execute.
| && npm cache clean --force \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| ENTRYPOINT ["/usr/local/bin/cube-entrypoint.sh"] |
There was a problem hiding this comment.
代码片段中缺少 WORKDIR /workspace。 实际的 Dockerfile (examples/codebuddy-integration/template/Dockerfile:22) 在 RUN 和 ENTRYPOINT 之间有一行 WORKDIR /workspace。按此片段逐字复制构建出的镜像行为不同——shell 默认工作目录将是 / 而非 /workspace,会影响 codebuddy -p 和 python3 命令的执行路径。
| printf '%s\n' "${create_output}" | ||
|
|
||
| if [[ "${WATCH_JOB:-0}" == "1" ]]; then | ||
| job_id="$(printf '%s\n' "${create_output}" | sed -n 's/.*job_id[=: ]*\([^ ]*\).*/\1/p' | tail -n 1)" |
There was a problem hiding this comment.
Fragile job_id extraction from unstructured output. This regex attempts to parse cubemastercli tpl create-from-image output to extract a job ID. Any change to that tool's output format (whitespace, punctuation, ordering) would silently produce an empty job_id, making WATCH_JOB=1 a no-op. Consider requesting structured output (JSON/YAML) if cubemastercli supports it, or at minimum print the raw create_output alongside the failure message so users can debug.
| python3 \ | ||
| python3-pip \ | ||
| ripgrep \ | ||
| && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ |
There was a problem hiding this comment.
Consider verifying the NodeSource setup script. curl -fsSL ... | bash - pipes the setup script directly into bash without checksum or signature verification. A compromise of the NodeSource CDN or a DNS hijack during build could execute arbitrary code. Other images in this repo (e.g. the base image) use pinned tarballs with GPG verification. Consider downloading the script first and verifying a checksum before execution, or pinning the tarball URL with GPG verification using the NodeSource release keys.
PR Review: CodeBuddy Integration Guide and Example (#718)Well-structured PR with thorough validation steps. The code uses modern practices ( Documentation
Code Quality
Security (Low Severity)
No Action Needed (Noted as Correct)
|
| && codebuddy --version \ | ||
| && npm cache clean --force \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
There was a problem hiding this comment.
Missing WORKDIR /workspace in this code snippet. The actual Dockerfile at examples/codebuddy-integration/template/Dockerfile:22 has a WORKDIR /workspace instruction between the RUN and ENTRYPOINT directives. A reader who copies this snippet verbatim would produce a functionally different Dockerfile — the shell's working directory would default to /, not /workspace, affecting where codebuddy -p and python3 commands execute.
| && npm cache clean --force \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| ENTRYPOINT ["/usr/local/bin/cube-entrypoint.sh"] |
There was a problem hiding this comment.
代码片段中缺少 WORKDIR /workspace。 实际的 Dockerfile (examples/codebuddy-integration/template/Dockerfile:22) 在 RUN 和 ENTRYPOINT 之间有一行 WORKDIR /workspace。按此片段逐字复制构建出的镜像行为不同——shell 默认工作目录将是 / 而非 /workspace,会影响 codebuddy -p 和 python3 命令的执行路径。
| printf '%s\n' "${create_output}" | ||
|
|
||
| if [[ "${WATCH_JOB:-0}" == "1" ]]; then | ||
| job_id="$(printf '%s\n' "${create_output}" | sed -n 's/.*job_id[=: ]*\([^ ]*\).*/\1/p' | tail -n 1)" |
There was a problem hiding this comment.
Fragile job_id extraction from unstructured output. This regex attempts to parse cubemastercli tpl create-from-image output to extract a job ID. Any change to that tool's output format (whitespace, punctuation, ordering) would silently produce an empty job_id, making WATCH_JOB=1 a no-op. Consider requesting structured output (JSON/YAML) if cubemastercli supports it, or at minimum print the raw create_output alongside the failure message so users can debug.
| python3 \ | ||
| python3-pip \ | ||
| ripgrep \ | ||
| && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ |
There was a problem hiding this comment.
Consider verifying the NodeSource setup script. curl -fsSL ... | bash - pipes the setup script directly into bash without checksum or signature verification. A compromise of the NodeSource CDN or a DNS hijack during build could execute arbitrary code. Other images in this repo use pinned tarballs with GPG verification. Consider downloading the script first and verifying a checksum before execution, or pinning the tarball URL with GPG verification using the NodeSource release keys.
| """ | ||
|
|
||
|
|
||
| def sandbox_env(config_dir: str) -> Dict[str, str]: |
There was a problem hiding this comment.
Hidden coupling: sandbox_env() reads CODEBUDDY_API_KEY directly from os.environ (line 78), which means it silently depends on require_env() having been called first. If a caller (or future refactor) calls this before the requirement check, it will raise a KeyError instead of the friendly "Missing required environment variables" message.
For example code that readers will learn from, it would be better to pass the API key as an explicit parameter:
def sandbox_env(api_key: str, config_dir: str) -> Dict[str, str]:This makes the dependency explicit and makes the function independently testable.
| ) | ||
| parser.add_argument( | ||
| "--timeout", | ||
| type=int, |
There was a problem hiding this comment.
Validation gap: positive_int() is only applied to the environment-variable fallback default (line 184), not to CLI-provided values. Since type=int is used here, passing --timeout -5 or --timeout 0 on the command line bypasses the validation entirely. Consider using type=positive_int (with the env-var fallback logic moved into the default) so CLI values are also validated.
# Move positive_int to type so it validates both CLI and env-default
parser.add_argument(
"--timeout",
type=positive_int,
default=os.environ.get("CUBE_SANDBOX_TIMEOUT", "600"),
help="Sandbox timeout in seconds.",
)And adjust positive_int to accept a default differently, or handle the string parsing within its own logic.
| ENV DISABLE_AUTOUPDATER=1 \ | ||
| CODEBUDDY_CONFIG_DIR=/workspace/.codebuddy | ||
|
|
||
| RUN apt-get update \ |
There was a problem hiding this comment.
Docker cache efficiency: Everything is chained into one RUN layer, so bumping the CodeBuddy version (line 23) forces re-execution of the entire chain: apt-get update, installing system packages, downloading the NodeSource GPG key, Node.js, etc. (~60-120s of wasted rebuild time during iteration).
Consider splitting into three layers to preserve caching for the stable parts:
- System packages + cleanup (rarely changes)
- NodeSource repo key + Node.js install + cleanup (changes with Node version)
npm install -g+codebuddy --version+npm cache clean(changes with every CodeBuddy bump)
| > /etc/apt/sources.list.d/nodesource.list \ | ||
| && apt-get update \ | ||
| && apt-get install -y --no-install-recommends nodejs \ | ||
| && npm install -g @tencent-ai/codebuddy-code@2.114.2 \ |
There was a problem hiding this comment.
Image size: npm install -g installs devDependencies by default, which can add 50-200+ MB of unnecessary packages to the sandbox template image. Consider --omit=dev:
RUN npm install -g --omit=dev @tencent-ai/codebuddy-code@2.114.2 \
&& codebuddy --version \
&& npm cache clean --force(If codebuddy --version fails without devDependencies, that's a CLI packaging issue worth flagging to the team.)
| printf '%s\n' "${create_output}" | ||
|
|
||
| if [[ "${WATCH_JOB:-0}" == "1" ]]; then | ||
| job_id="$(printf '%s\n' "${create_output}" | sed -n 's/.*job_id[=: ]*\([^ ]*\).*/\1/p' | tail -n 1)" |
There was a problem hiding this comment.
Fragile parsing: The sed pattern assumes job_id appears in a specific format (job_id=<value> or job_id: <value>). If cubemastercli tpl create-from-image output format changes, this silently produces an empty string. The fallback on lines 44-49 handles the failure gracefully (dumps raw output), but the pattern could be tightened by matching a known prefix/suffix specific to cubemastercli's actual output, or by asking cubemastercli to have a --json / --format json flag.
| if not args.template: | ||
| raise SystemExit("Missing template: set CUBE_TEMPLATE_ID or pass --template") | ||
|
|
||
| from e2b_code_interpreter import Sandbox |
There was a problem hiding this comment.
Lazy import: Importing Sandbox inside main() means that running --help or any other CLI operation will fail with an ImportError if e2b-code-interpreter is not installed. A top-level import (optionally guarded with a try/except and a helpful message) is the standard Python convention and worth demonstrating in example code that others learn from.
| """ | ||
|
|
||
|
|
||
| def sandbox_env(api_key: str, config_dir: str) -> Dict[str, str]: |
There was a problem hiding this comment.
Security: Proxy env var forwarding leaks host credentials into sandbox
sandbox_env() propagates HTTP_PROXY, HTTPS_PROXY, and NO_PROXY from the host environment into every sandbox instance. If a proxy URL contains embedded credentials (e.g., http://user:pass@corp-proxy:8080), those credentials are readable by any process inside the sandbox including the CodeBuddy LLM agent. Consider stripping userinfo from proxy URLs before forwarding, or at minimum document this behavior prominently.
| permission_mode=args.permission_mode, | ||
| ) | ||
| sandbox.files.write(SCRIPT_PATH, script) | ||
| sandbox.commands.run(f"chmod +x {shlex.quote(SCRIPT_PATH)}") |
There was a problem hiding this comment.
Performance: chmod +x is unnecessary here
The script is executed via bash /tmp/run-codebuddy.sh (line 240), which does not require the execute bit. This chmod call is a wasted network round-trip to the CubeAPI. Removing it saves ~50–200ms per run.
| from e2b_code_interpreter import Sandbox | ||
|
|
||
| print(f"[cube] template: {args.template}") | ||
| print(f"[cube] api url: {os.environ['E2B_API_URL']}") |
There was a problem hiding this comment.
Code quality: require_env return value partially ignored
require_env() returns a Dict[str, str] of validated values, but line 218 reads os.environ['E2B_API_URL'] directly instead of using the returned dict. This bypasses the function's abstraction and makes testing harder. Use required_env['E2B_API_URL'] instead for consistency.
| return env | ||
|
|
||
|
|
||
| def write_demo_workspace(sandbox) -> None: |
There was a problem hiding this comment.
Performance: Three network round-trips for trivial workspace setup
mkdir -p (1 RPC) + two files.write() calls (2 RPCs) = 3 network round-trips to the CubeAPI. These could be combined into a single shell heredoc script executed via sandbox.commands.run(), saving ~60–70% of this step's latency.
|
|
||
| def build_codebuddy_script( | ||
| prompt: str, | ||
| output_format: str, |
There was a problem hiding this comment.
Code quality: positive_int couples validation to argparse
positive_int raises argparse.ArgumentTypeError, but env_positive_int (line 73) calls it outside argparse and must catch-and-reraise as SystemExit. Consider extracting a pure validate_positive_int(value) -> int that raises ValueError, then wrap it with a thin argparse adapter. This keeps env_positive_int clean and makes validation reusable.
| return env | ||
|
|
||
|
|
||
| def write_demo_workspace(sandbox) -> None: |
There was a problem hiding this comment.
Code quality: Missing type annotations on core data-flow functions
write_demo_workspace, print_result (line 118), and verify_pause_resume (line 142) omit type annotations for their primary parameters while the rest of the module is fully annotated. Consider annotating sandbox as Sandbox and defining a Protocol for the result type to get IDE/type-checker assistance.
| )" | ||
| printf '%s\n' "${create_output}" | ||
|
|
||
| if [[ "${WATCH_JOB:-0}" == "1" ]]; then |
There was a problem hiding this comment.
Fragile job_id extraction via regex on unstructured CLI output
The sed command parses human-readable output from cubemastercli tpl create-from-image. If the CLI output format changes (localization, new fields, spacing), the extraction silently fails. If cubemastercli supports --format json, prefer jq -r '.job_id' for robust parsing.
| exit 1 | ||
| fi | ||
|
|
||
| docker build --platform "${DOCKER_PLATFORM:-linux/amd64}" -t "${IMAGE_NAME}" "${SCRIPT_DIR}/template" |
There was a problem hiding this comment.
Performance: docker build missing --pull flag
The base image tag 2026.16 is a rolling weekly tag. Without --pull, Docker uses a locally cached copy even if a newer manifest exists. For iterative local builds, the base image can silently fall behind. Add --pull to always fetch the latest manifest.
| Path.cwd() / ".env", | ||
| ] | ||
|
|
||
| seen_paths = set() |
There was a problem hiding this comment.
Minor: Unnecessary deduplication set for two candidate paths
The seen_paths set deduplicates two .env paths, but the function returns on the first match anyway. The set adds complexity without functional benefit — a simple loop suffices.
| """ | ||
|
|
||
|
|
||
| def strip_url_userinfo(value: str) -> str: |
There was a problem hiding this comment.
Security-critical: strip_url_userinfo() has no test coverage
This function handles credential stripping from proxy URLs before injecting them into sandboxes. Despite being security-critical, none of its branches are tested in isolation. Consider adding tests for:
- URL with userinfo (
http://user:pass@proxy:8080) - URL without
@(http://proxy:8080) - URL without scheme (
user:pass@proxy:8080) - URL with
@only in credentials (http://user:p@ss@proxy:8080)
The test_sandbox_env_strips_proxy_credentials_when_forwarding_is_enabled test validates the integration path through sandbox_env(), but not the utility in isolation.
| TRUTHY_ENV_VALUES = {"1", "true", "yes", "on"} | ||
|
|
||
|
|
||
| def require_env(keys: Iterable[str]) -> Dict[str, str]: |
There was a problem hiding this comment.
Iterator consumption bug in require_env() 🐛
The keys: Iterable[str] parameter is iterated twice (once for building missing, once for building the return dict). If keys is a one-shot iterator or generator, the second loop silently produces an empty dict — no missing keys reported, no env vars returned.
Fix by iterating once:
def require_env(keys: Iterable[str]) -> Dict[str, str]:
result = {}
missing = []
for key in keys:
value = os.environ.get(key)
if not value:
missing.append(key)
else:
result[key] = value
if missing:
raise SystemExit(...)
return resultCurrent callers pass list literals so this doesn't trigger yet, but the type annotation advertises Iterable.
| return parsed | ||
|
|
||
|
|
||
| def env_default(key: str, default: str) -> str: |
There was a problem hiding this comment.
env_default() treats empty string as "not set"
return os.environ.get(key) or default — if someone explicitly sets CUBE_SANDBOX_TIMEOUT="" (e.g., in .env), this silently falls through to the default "600" rather than propagating the empty value. Consider using os.environ.get(key, default) if you only want to fall back when the key is absent.
| python3-pip \ | ||
| ripgrep \ | ||
| && mkdir -p /etc/apt/keyrings \ | ||
| && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ |
There was a problem hiding this comment.
GPG key fingerprint should be verified
The NodeSource GPG key is piped directly into the keyring without fingerprint verification. An attacker compromising Nodesource's infrastructure or performing a transit hijack could serve a different key and sign malicious packages. Consider:
&& gpg --show-keys /etc/apt/keyrings/nodesource.gpg 2>/dev/null \
| grep -q "9FD3 B784 BC1C 6FC3 1A8A 0A1C 1655 A0AB 6857 3800" \
|| (echo "ERROR: Nodesource key fingerprint mismatch" && exit 1)Verify the current fingerprint at https://github.com/nodesource/distributions before pinning.
| return env | ||
|
|
||
|
|
||
| def write_demo_workspace(sandbox) -> None: |
There was a problem hiding this comment.
write_demo_workspace() makes 3 sequential RPC calls
Each sandbox.commands.run() and sandbox.files.write() crosses a network boundary to the sandbox MicroVM. On high-latency links this adds ~150–600ms. Consider batching into a single command:
def write_demo_workspace(sandbox) -> None:
script = (
"mkdir -p /tmp/codebuddy-demo\n"
"cat > /tmp/codebuddy-demo/hello.py <<'EOF'\n"
"print('hello from Cube Sandbox + CodeBuddy')\n"
"EOF\n"
"cat > /tmp/codebuddy-demo/README.md <<'EOF'\n"
"# CodeBuddy Cube Sandbox Demo\n\n"
"…\n"
"EOF\n"
)
sandbox.commands.run(script)Also consider adding error handling — if a file write fails, the script proceeds with a broken workspace.
| && npm cache clean --force \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| WORKDIR /workspace |
There was a problem hiding this comment.
Consider running as non-root user
The container runs CodeBuddy (an autonomous code-generation agent with Bash/Write/Edit access) as root. Any vulnerability or prompt injection gives root-level access inside the sandbox. While the MicroVM provides isolation, defense-in-depth suggests:
RUN groupadd -r codebuddy && useradd -r -g codebuddy -d /workspace -s /bin/bash codebuddy \
&& chown codebuddy:codebuddy /workspace
USER codebuddyDocument that this is a hardening recommendation — verify compatibility with cube-entrypoint.sh requirements.
| """ | ||
|
|
||
|
|
||
| def strip_url_userinfo(value: str) -> str: |
There was a problem hiding this comment.
Security-critical: strip_url_userinfo() has no test coverage
This function handles credential stripping from proxy URLs before injecting them into sandboxes. Despite being security-critical, none of its branches are tested. Consider adding tests for:
- URL with userinfo (
http://user:pass@proxy:8080) - URL without
@(http://proxy:8080) - URL without scheme (
user:pass@proxy:8080) - URL with
@only in credentials (http://user@host:p@ss@proxy:8080)
The test_sandbox_env_strips_proxy_credentials_when_forwarding_is_enabled test only validates the integration path through sandbox_env(), not the utility function in isolation.
| TRUTHY_ENV_VALUES = {"1", "true", "yes", "on"} | ||
|
|
||
|
|
||
| def require_env(keys: Iterable[str]) -> Dict[str, str]: |
There was a problem hiding this comment.
Iterator consumption bug in require_env() 🐛
The keys: Iterable[str] parameter is iterated twice (once for building missing, once for building the return dict). If keys is a one-shot iterator (e.g., iter(["E2B_API_URL", "E2B_API_KEY"]) or a generator), the second loop silently produces an empty dict — no missing keys reported, no env vars returned.
Fix: iterate once and build both lists:
def require_env(keys: Iterable[str]) -> Dict[str, str]:
result = {}
missing = []
for key in keys:
value = os.environ.get(key)
if not value:
missing.append(key)
else:
result[key] = value
if missing:
raise SystemExit(...)
return resultThe current callers pass list literals so this doesn't trigger today, but the type annotation advertises Iterable.
| return parsed | ||
|
|
||
|
|
||
| def env_default(key: str, default: str) -> str: |
There was a problem hiding this comment.
env_default() treats empty string as "not set"
return os.environ.get(key) or default — If someone explicitly sets CUBE_SANDBOX_TIMEOUT="" (e.g., in .env), this silently falls through to the default "600" rather than propagating the empty value or raising an error. Consider os.environ.get(key, default) if the intent is to only fall back when the key is absent, or strip + check if you want to reject empty values.
| python3-pip \ | ||
| ripgrep \ | ||
| && mkdir -p /etc/apt/keyrings \ | ||
| && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \ |
There was a problem hiding this comment.
GPG key fingerprint should be verified for supply chain security
The NodeSource GPG key is piped directly into the keyring without fingerprint verification. An attacker compromising Nodesource distribution infrastructure or performing a DNS/transit hijack could serve a different key and sign malicious packages. Consider verifying the key fingerprint after import:
&& gpg --show-keys /etc/apt/keyrings/nodesource.gpg 2>/dev/null \
| grep -q "9FD3 B784 BC1C 6FC3 1A8A 0A1C 1655 A0AB 6857 3800" \
|| (echo "ERROR: Nodesource key fingerprint mismatch" && exit 1)Current key fingerprint for NodeSource: 9FD3 B784 BC1C 6FC3 1A8A 0A1C 1655 A0AB 6857 3800 (verify before pinning).
| return env | ||
|
|
||
|
|
||
| def write_demo_workspace(sandbox) -> None: |
There was a problem hiding this comment.
write_demo_workspace() makes 3 sequential sandbox RPC calls
Each sandbox.commands.run() and sandbox.files.write() call crosses a network boundary. On high-latency links this adds 150–600ms. Consider batching into a single shell command:
def write_demo_workspace(sandbox) -> None:
script = (
"mkdir -p /tmp/codebuddy-demo\n"
"cat > /tmp/codebuddy-demo/hello.py <<'EOF'\n"
"print('hello from Cube Sandbox + CodeBuddy')\n"
"EOF\n"
"cat > /tmp/codebuddy-demo/README.md <<'EOF'\n"
"# CodeBuddy Cube Sandbox Demo\n\n"
"…\n"
"EOF\n"
)
sandbox.commands.run(script)Also, consider adding error handling — if a file write fails, the script proceeds with a broken workspace.
| && npm cache clean --force \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| WORKDIR /workspace |
There was a problem hiding this comment.
Consider running as non-root user
The container runs CodeBuddy (an autonomous code-generation agent with Bash, Write, and Edit tool access) as root. Any vulnerability or prompt injection enables root-level access inside the sandbox. While the MicroVM provides isolation, defense-in-depth suggests adding a non-root user:
RUN groupadd -r codebuddy && useradd -r -g codebuddy -d /workspace -s /bin/bash codebuddy \
&& chown codebuddy:codebuddy /workspace
USER codebuddyDocument that this is a sandbox hardening recommendation (the entrypoint may have root requirements — verify compatibility).
|
Refs #644 |
Refs #644
Summary
docs/guide/integrationsanddocs/zh/guide/integrations.examples/codebuddy-integration..env.example, Python runner, and bilingual README.Sandbox.create(envs=...), egress requirements, troubleshooting, and pause/resume state preservation.Validation
python3 -m py_compile examples/codebuddy-integration/run_codebuddy.py examples/codebuddy-integration/env_utils.py examples/codebuddy-integration/test_run_codebuddy.pycd examples/codebuddy-integration && python3 -m unittest test_run_codebuddy.pybash -n examples/codebuddy-integration/build-template.shgit diff --checkcd docs && npm run docs:build2.103.3--permission-mode bypassPermissionssuccessfully read a temp README and ranpython3 hello.pywithout modifying filescubesandbox-codebuddy:verifywith--platform linux/amd64codebuddy --version:2.114.2cube-entrypointstartedenvdon port49983codebuddy --version:2.114.2node --version:v22.23.1python3 --version:Python 3.10.12rg --version:ripgrep 13.0.0Not Yet Run
cubemastercli tpl create-from-imagepython run_codebuddy.pyagainst a live CubeSandbox deploymentpython run_codebuddy.py --pause-resumeagainst a live CubeSandbox deploymentThe available ECS can build and run the Docker image, but it does not currently have CubeSandbox deployed: no
cubemastercli, no CubeAPI on port3000, and no/dev/kvm/PVM runtime.