Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,8 @@ ref/
# pddlstream writes its FastDownward scratch files into the working directory.
temp/
statistics/

# Network isolation audit logs and downloaded test artifacts.
network_audit_results/

.apptainer-env-cache/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ python experiments/run_experiment.py approach=agentic \
replicate_seed=0 eval_seed="$EVAL_SEED"
```

With login-file authentication, Docker and Apptainer copy only `auth.json` into a throwaway Codex home. Host `config.toml`, `AGENTS.md`, skills, and session history are not mounted. Each fresh experiment starts with an empty sandbox-local session directory; only an automatic retry of that same experiment can resume it.
With login-file authentication, Docker copies only `auth.json` into a throwaway Codex home. Apptainer keeps authentication on the host in its inference broker and mounts no real credentials. Neither mounts host `config.toml`, `AGENTS.md`, skills, or session history. Each fresh experiment starts with an empty sandbox-local session directory; only an automatic retry of that same experiment can resume it.

#### OpenCode (multi-provider)

Expand Down Expand Up @@ -379,7 +379,7 @@ The agent runs inside a Docker container (`robocode-sandbox`) that provides full
| Network | `init-firewall.sh` whitelists API endpoints for the configured provider (Anthropic, OpenAI, Google, etc.), GitHub IPs, and telemetry; blocks everything else via iptables. Extra domains are passed via `ROBOCODE_FIREWALL_EXTRA_DOMAINS`. |
| Write hook | Claude backend: `PreToolUse` hook in `.claude/settings.json` double-checks Write/Edit paths stay inside `/sandbox`. Codex and OpenCode rely on the enclosing Docker filesystem boundary. |

The Apptainer backend (`container_backend=apptainer`, for HPC clusters with no Docker daemon) keeps the same filesystem isolation but has **no network firewall**: unprivileged Apptainer cannot grant `CAP_NET_ADMIN`, so `init-firewall.sh` is skipped and generated code runs with unrestricted network egress. Use Docker where the iptables allowlist matters.
The Apptainer backend (`container_backend=apptainer`, for HPC clusters without Docker) now runs Codex and Claude in a disconnected network namespace (`--userns --net --network none`). A host broker permits validated model inference, and a separate relay reaches only the experiment environment server. Agent processes cannot use general internet access, and provider credentials stay outside the container. See [implementation, test evidence, and limitations](docs/apptainer-network-isolation.md). Unsupported Apptainer backends and GenPlan fail closed.

### What the agent sees

Expand Down
37 changes: 18 additions & 19 deletions docker/Dockerfile.strict-blackbox
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
# Deliberately never copies project metadata, environment/KinDER/simulator code,
# or robotics/geometry packages. Generated programs use only Python's standard
# library plus pinned NumPy/SciPy; the frozen program is later checked on the host
# against exactly that allowlist (src/robocode/utils/strict_blackbox.py). A separate
# Python environment contains only the generic MCP-to-env-server render proxy.
# against exactly that allowlist (src/robocode/utils/strict_blackbox.py). Rendering
# uses the same interpreter, without installing any project or MCP package.
FROM node:22

ARG CLAUDE_CODE_VERSION=latest
Expand Down Expand Up @@ -40,6 +40,12 @@ RUN python3.11 -m venv /opt/robocode-strict \
&& /opt/robocode-strict/bin/pip install --no-cache-dir \
numpy==1.26.4 scipy==1.14.0

# Remove installer and base-image Python packages after the numerical wheels
# are installed. An agent can add any readable package directory to sys.path.
RUN /opt/robocode-strict/bin/python -m pip uninstall -y pip setuptools \
&& rm -rf /usr/lib/python3/dist-packages/* \
/usr/local/lib/python3.11/dist-packages/* /usr/share/python-wheels/*

RUN mkdir -p /usr/local/share/npm-global \
&& chown -R node:node /usr/local/share
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
Expand All @@ -48,24 +54,17 @@ RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} \
&& npm install -g opencode-ai@${OPENCODE_VERSION} \
&& npm install -g @openai/codex@${CODEX_VERSION}

# Keep MCP infrastructure out of the Python environment used by generated
# programs. This separate interpreter hosts only the generic blackbox render
# proxy; a .pth shares the strict environment's packages so render_policy
# can execute any approach that obeys the scoring import allowlist.
RUN python3.11 -m venv /opt/robocode-mcp \
&& /opt/robocode-mcp/bin/pip install --no-cache-dir "mcp==1.29.0" \
&& echo "/opt/robocode-strict/lib/python3.11/site-packages" \
> /opt/robocode-mcp/lib/python3.11/site-packages/strict-blackbox.pth
# node:22 also carries Python build/debug helpers outside site-packages. They
# are not used by the installed agent CLIs and must not become import backdoors.
RUN rm -rf /usr/local/lib/node_modules/npm/node_modules/node-gyp/gyp/pylib \
/usr/share/glib-2.0/codegen /usr/share/gcc/python \
/usr/share/doc/subversion/examples /usr/share/doc/libsvn1/examples \
/usr/share/python3/debpython

# Install only the generic, environment-independent blackbox MCP proxy. No
# environment, simulator, primitive, rendering, or approach source enters the
# image; actual pixels are produced by the host env server.
COPY src/robocode/__init__.py \
/opt/robocode-mcp/lib/python3.11/site-packages/robocode/__init__.py
COPY src/robocode/mcp/__init__.py src/robocode/mcp/server.py \
/opt/robocode-mcp/lib/python3.11/site-packages/robocode/mcp/
COPY src/robocode/utils/__init__.py src/robocode/utils/env_client.py \
/opt/robocode-mcp/lib/python3.11/site-packages/robocode/utils/
# No second interpreter or project package: virtualenvs are not access controls.
# The render protocol uses stdlib plus the same NumPy client as agent scripts.
COPY src/robocode/mcp/strict_server.py src/robocode/utils/env_client.py \
/opt/robocode-render/

COPY docker/init-firewall.sh /usr/local/bin/init-firewall.sh
COPY docker/strict-blackbox-entrypoint.sh /usr/local/bin/entrypoint.sh
Expand Down
6 changes: 3 additions & 3 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ if [ "$(id -u)" -eq 0 ]; then
HOME=/home/node USER=node LOGNAME=node \
"${run_as_node[@]}" uv sync --frozen --python python3.11 "${uv_extra_args[@]}"
else
# Unprivileged Apptainer runs preserve the host UID.
# Preserve non-root invocation behavior when firewall setup is explicitly skipped.
uv sync --frozen --python python3.11 "${uv_extra_args[@]}"
fi
cd /sandbox

# Skipped under unprivileged Apptainer, which cannot grant CAP_NET_ADMIN;
# ROBOCODE_SKIP_FIREWALL=1 is set by apptainer_sandbox.py.
# Docker firewall setup. Preserve the existing explicit skip override.
# Apptainer does not invoke this entrypoint; it uses a disconnected namespace.
if [ "${ROBOCODE_SKIP_FIREWALL:-0}" = "1" ]; then
echo "entrypoint: ROBOCODE_SKIP_FIREWALL=1, skipping firewall init" >&2
else
Expand Down
6 changes: 3 additions & 3 deletions docker/strict-blackbox-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
set -euo pipefail
IFS=$'\n\t'

# Skipped under unprivileged Apptainer, which cannot grant CAP_NET_ADMIN;
# ROBOCODE_SKIP_FIREWALL=1 is set by apptainer_sandbox.py.
# Docker firewall setup. Preserve the existing explicit skip override.
# Apptainer does not invoke this entrypoint; it uses a disconnected namespace.
if [ "${ROBOCODE_SKIP_FIREWALL:-0}" = "1" ]; then
echo "entrypoint: ROBOCODE_SKIP_FIREWALL=1, skipping firewall init" >&2
else
Expand All @@ -34,5 +34,5 @@ if [ "$(id -u)" -eq 0 ]; then
-- "$@"
fi

# Unprivileged Apptainer runs preserve the host UID.
# Preserve non-root invocation behavior when firewall setup is explicitly skipped.
exec "$@"
Loading
Loading