Skip to content

Commit 300b20b

Browse files
authored
Merge pull request #51 from jack-yang-apodex/codex/service-sandbox-fail-closed
fix: fail closed for service sandbox isolation
2 parents 4fcba4d + 68b9200 commit 300b20b

5 files changed

Lines changed: 37 additions & 0 deletions

File tree

‎apodex/tests/test_deployment_config.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,12 @@ def test_default_compose_pulls_release_image_and_preserves_cli_state() -> None:
3939
assert agent["pull_policy"] == "always"
4040
assert agent["environment"]["APODEX_IN_CONTAINER"] == "1"
4141
assert agent["environment"]["SANDBOX_BACKEND"] == "container"
42+
assert agent["environment"]["FRONTIER_AGENT_REQUIRE_TOOL_USER"] == "1"
4243
assert "security_opt" not in agent
4344
assert ".:/project" in agent["volumes"]
45+
# Compose injects these files into the harness environment, but the
46+
# project bind must not expose their on-disk contents to tool commands.
47+
assert "/dev/null:/project/.env:ro" in agent["volumes"]
4448
assert agent["working_dir"] == "/project"
4549
assert "./.apodex/runs:/apodex-runs" in agent["volumes"]
4650
assert "frontier-agent-inputs:/inputs:ro" in agent["volumes"]
@@ -58,6 +62,18 @@ def test_default_compose_pulls_release_image_and_preserves_cli_state() -> None:
5862
)
5963
assert agent["environment"]["APODEX_WORKSPACE_LINK"] == "/workspace"
6064

65+
evaluator = compose["services"]["eval"]
66+
assert evaluator["environment"]["SANDBOX_BACKEND"] == "bwrap"
67+
assert evaluator["environment"]["SANDBOX_PROFILE"] == "service"
68+
69+
for overlay, filename in (
70+
("compose.sglang.yaml", ".env.sglang"),
71+
("compose.transformers.yaml", ".env.transformers"),
72+
):
73+
assert f"/dev/null:/project/{filename}:ro" in (
74+
_yaml(overlay)["services"]["agent"]["volumes"]
75+
)
76+
6177

6278
def test_development_compose_is_the_only_compose_file_that_builds() -> None:
6379
compose = _yaml("compose.yaml")

‎compose.sglang.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ services:
6565
max-file: "3"
6666

6767
agent:
68+
volumes:
69+
- /dev/null:/project/.env.sglang:ro
6870
depends_on:
6971
model:
7072
condition: service_healthy

‎compose.transformers.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ services:
4444
restart: unless-stopped
4545

4646
agent:
47+
volumes:
48+
- /dev/null:/project/.env.transformers:ro
4749
depends_on:
4850
model:
4951
condition: service_healthy

‎compose.yaml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ services:
99
APODEX_IN_CONTAINER: "1"
1010
HOME: /root
1111
SANDBOX_BACKEND: container
12+
# A missing agent-tool account would expose the harness environment to
13+
# model-authored commands through /proc. Service deployments must stop
14+
# instead of accepting the documented env-only degradation.
15+
FRONTIER_AGENT_REQUIRE_TOOL_USER: "1"
1216
APODEX_LOCAL_UTC_OFFSET: ${APODEX_LOCAL_UTC_OFFSET:-+0000}
1317
FRONTIER_AGENT_WORKSPACE_DIR: /workspace
1418
APODEX_SESSION_WORKSPACES_ROOT: /apodex-runs
@@ -31,6 +35,9 @@ services:
3135
APODEX_HOST_GID: ${APODEX_HOST_GID:-}
3236
volumes:
3337
- .:/project
38+
# The harness receives .env through env_file above. Hide the on-disk
39+
# copy from model commands, whose host-mapped UID can read /project.
40+
- /dev/null:/project/.env:ro
3441
- ./.apodex/runs:/apodex-runs
3542
- frontier-agent-inputs:/apodex-inputs
3643
- frontier-agent-inputs:/inputs:ro
@@ -51,6 +58,9 @@ services:
5158
# The eval runner can execute multiple isolated benchmark tasks in one
5259
# harness container, so it retains the nested bubblewrap backend.
5360
SANDBOX_BACKEND: bwrap
61+
# Multi-task workers need a private PID namespace and fresh procfs. The
62+
# service profile fails closed when the runtime cannot provide them.
63+
SANDBOX_PROFILE: service
5464
APODEX_HOST_UID: ${APODEX_HOST_UID:-}
5565
APODEX_HOST_GID: ${APODEX_HOST_GID:-}
5666
volumes:

‎docs/install/docker.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ Compose writes session records and deliverables to `.apodex/runs/<session-id>/`.
3434
Its named state volume is retained for legacy sessions. Attached inputs are
3535
copied into a separate volume that tools can only read. See
3636
[run artifacts and timestamps](../run-artifacts.md) for the on-disk layout.
37+
The agent receives `.env` through its process environment; Compose masks the
38+
on-disk file inside `/project` so model commands cannot read it. The SGLang and
39+
Transformers overrides also mask their respective env files. Keep any custom
40+
credential file outside the mounted project, since project files are available
41+
to the agent by design.
3742

3843
The convenience helper wraps the same thing:
3944

@@ -63,6 +68,7 @@ docker run --rm -it \
6368
--env-file .env \
6469
-e APODEX_IN_CONTAINER=1 \
6570
-e SANDBOX_BACKEND=container \
71+
-e FRONTIER_AGENT_REQUIRE_TOOL_USER=1 \
6672
-e FRONTIER_AGENT_WORKSPACE_DIR=/workspace \
6773
-e APODEX_RUNS_ROOT=/apodex-runs \
6874
-e APODEX_RUNS_ROOT_PINNED=1 \
@@ -71,6 +77,7 @@ docker run --rm -it \
7177
-e APODEX_INPUT_STAGING_ROOT=/apodex-inputs \
7278
-e FRONTIER_AGENT_INPUTS_ROOT=/inputs \
7379
-v "$(pwd):/workspace" \
80+
-v /dev/null:/workspace/.env:ro \
7481
-v "$(pwd)/.apodex/runs:/apodex-runs" \
7582
-v frontier-agent-inputs:/apodex-inputs \
7683
-v frontier-agent-inputs:/inputs:ro \

0 commit comments

Comments
 (0)