Skip to content

Enable New App Builder#267

Draft
lukefoster11 wants to merge 10 commits into
masterfrom
r2
Draft

Enable New App Builder#267
lukefoster11 wants to merge 10 commits into
masterfrom
r2

Conversation

@lukefoster11

Copy link
Copy Markdown

No description provided.

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces the R2 agent-sandbox stack to the self-hosted Docker Compose deployment, adding js-executor, agent-sandbox-controller, agent-sandbox-proxy, r2-agent-worker, and a bundled MinIO service for blob storage, along with the seccomp and AppArmor profiles those services require.

  • New sandbox services: js-executor (nsjail-based, custom seccomp), agent-sandbox-controller/proxy (gVisor + pasta, custom gVisor seccomp), and r2-agent-worker (Temporal worker) are wired into new js-executor and agent-sandbox Docker networks.
  • Blob storage: A bundled MinIO instance is added as the default blob backend, with minio-init creating the required buckets on startup; install.sh generates the agent JWT keypair and writes MinIO/agent env vars into docker.env.
  • AppArmor override: A custom docker-default profile (appArmor/docker-default) and an idempotent install script (scripts/setup-docker-apparmor.sh) replace Docker's built-in profile to permit mount and pivot_root syscalls needed by gVisor and pasta inside sandbox containers.

Confidence Score: 3/5

The deployment introduces a bundled MinIO instance with publicly-known static credentials exposed on all interfaces, and a minio-init bucket-creation step that may silently fail on slow hosts — both of which would leave users with either an insecure or broken object-store setup out of the box.

The core sandbox wiring and seccomp/AppArmor work looks carefully done and well-documented. The main concern is compose.yaml: MinIO ships with hardcoded retool/retoolminio credentials that install.sh never randomizes (unlike postgres, JWT keys, and the encryption key), while ports 9000 and 9001 are published on all interfaces. The sleep 3 race in minio-init means bucket creation can silently fail on slow starts, breaking blob storage entirely. The r2-agent-worker startup-ordering gap is a secondary concern.

compose.yaml (MinIO service credentials and minio-init readiness logic) and install.sh (MinIO credential generation)

Security Review

  • Hardcoded MinIO credentials (retool/retoolminio) are embedded directly in compose.yaml and mirrored as static literals in the generated docker.env. Unlike every other secret in the install flow, these are never randomized. MinIO Console (port 9001) and the S3 API (port 9000) are published on all interfaces, making the admin surface reachable from outside Docker with well-known credentials.
  • Global AppArmor mount, relaxation: The custom docker-default profile adds an unrestricted mount, and pivot_root, rule that applies to every container on the host, not only agent-sandbox containers. A compromised container from any service gains mount capabilities that the upstream Docker profile explicitly denied.

Important Files Changed

Filename Overview
compose.yaml Adds js-executor, agent-sandbox-controller, agent-sandbox-proxy, r2-agent-worker, minio, and minio-init services; hardcoded MinIO credentials are not randomized; minio-init uses fragile sleep-based wait; r2-agent-worker missing depends_on for sandbox services
install.sh Adds AppArmor setup prompt, EC keypair generation for agent JWT, and writes MinIO/agent-sandbox env vars to docker.env; MinIO credentials are hardcoded literals (not randomized) inconsistent with the rest of the secrets
appArmor/docker-default New AppArmor profile replacing Docker's global docker-default; adds broad mount, and pivot_root, permissions needed for gVisor/pasta; applies to all containers on the host as an intentional design trade-off
scripts/setup-docker-apparmor.sh New idempotent script that installs the custom AppArmor profile and systemd drop-in; includes --check mode, preflight checks for AppArmor availability, and clean skip on non-AppArmor hosts
Dockerfile Adds agent-sandbox-service and js-executor-service as new multi-stage build targets; straightforward and consistent with existing code-executor pattern
gvisor-seccomp.json New seccomp profile extending Docker default with gVisor-required syscalls (mount, umount2, pivot_root, ptrace, clone/unshare/setns); well-commented with purpose for each addition
nsjail-seccomp.json New seccomp profile for nsjail-based js-executor; follows Docker default structure with capability-gated additions for clone, mount, pivot_root, and sethostname; no obvious missing or over-privileged syscalls
README.md Configuration steps renumbered and expanded with blob storage guidance for MinIO/R2/S3; changes are documentation-only
appArmor/README.md Adds thorough documentation explaining why the docker-default AppArmor override is necessary for agent-sandbox, including the exact symptom, the three deltas from upstream, and verification steps

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Client["Browser / API Client"]
    HTTPS["https-portal\n(nginx, :443/:80)"]
    API["api\n(MAIN_BACKEND + RR_GIT_SERVER\n:3000)"]
    CE["code-executor\n(nsjail seccomp)"]
    JE["js-executor\n(nsjail seccomp, NET_ADMIN)"]
    ASC["agent-sandbox-controller\n(Docker socket, gVisor seccomp)"]
    ASP["agent-sandbox-proxy\n(:3019)"]
    R2W["r2-agent-worker\n(Temporal: r2-agent queue)"]
    PG["postgres"]
    Temporal["temporal"]
    MinIO["minio\n(:9000/:9001)"]
    MinioInit["minio-init\n(creates buckets)"]
    Sandbox["sandbox containers\n(spawned by controller)"]

    Client --> HTTPS --> API
    API --> PG
    API --> JE
    API --> CE
    API --> MinIO
    R2W --> Temporal
    R2W --> ASC
    R2W --> JE
    R2W --> CE
    ASC -->|Docker socket| Sandbox
    ASC --> PG
    ASC --> MinIO
    ASP --> API
    Sandbox -->|HTTP proxy| ASP
    MinioInit --> MinIO
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    Client["Browser / API Client"]
    HTTPS["https-portal\n(nginx, :443/:80)"]
    API["api\n(MAIN_BACKEND + RR_GIT_SERVER\n:3000)"]
    CE["code-executor\n(nsjail seccomp)"]
    JE["js-executor\n(nsjail seccomp, NET_ADMIN)"]
    ASC["agent-sandbox-controller\n(Docker socket, gVisor seccomp)"]
    ASP["agent-sandbox-proxy\n(:3019)"]
    R2W["r2-agent-worker\n(Temporal: r2-agent queue)"]
    PG["postgres"]
    Temporal["temporal"]
    MinIO["minio\n(:9000/:9001)"]
    MinioInit["minio-init\n(creates buckets)"]
    Sandbox["sandbox containers\n(spawned by controller)"]

    Client --> HTTPS --> API
    API --> PG
    API --> JE
    API --> CE
    API --> MinIO
    R2W --> Temporal
    R2W --> ASC
    R2W --> JE
    R2W --> CE
    ASC -->|Docker socket| Sandbox
    ASC --> PG
    ASC --> MinIO
    ASP --> API
    Sandbox -->|HTTP proxy| ASP
    MinioInit --> MinIO
Loading

Comments Outside Diff (4)

  1. compose.yaml, line 333-351 (link)

    P1 security Hardcoded MinIO credentials exposed on all interfaces

    The minio service embeds MINIO_ROOT_USER=retool / MINIO_ROOT_PASSWORD=retoolminio directly in compose.yaml (not docker.env), so install.sh's randomization logic never applies to them. The same static strings are written into the generated docker.env as RR_DEFAULT_S3_ACCESS_KEY_ID=retool and RR_DEFAULT_S3_SECRET_ACCESS_KEY=retoolminio. Ports 9000 and 9001 are published on all interfaces, so any host reachable on the network exposes a MinIO Console with these publicly-known defaults. All other secrets generated by install.sh (postgres password, JWT keys, encryption key) are randomized — MinIO is the only service that ships with a static, guessable credential pair. Users who follow the setup instructions but keep the bundled MinIO without manually rotating the root password will have a fully accessible object-store admin console.

  2. compose.yaml, line 353-368 (link)

    P1 minio-init uses a fixed sleep 3 to wait for MinIO readiness

    The sleep 3 heuristic before mc alias set will fail silently on slow starts (e.g., first boot with no cached layers, resource-constrained hosts). If the MinIO API is not yet listening when mc alias set runs, the command exits non-zero and the required buckets are never created, causing the api service (which depends_on: minio) to start but fail on the first blob-storage operation. A more reliable approach is to loop until the health check endpoint responds before running mc commands.

  3. compose.yaml, line 314-328 (link)

    P2 r2-agent-worker depends_on omits agent-sandbox services

    The r2-agent-worker service connects to the agent-sandbox network and routes tasks to agent-sandbox-controller and agent-sandbox-proxy, but neither service appears in its depends_on list (only postgres is listed). On a fresh docker compose up, the agent-sandbox services may not have started before the worker begins processing tasks, causing early failures that may or may not retry correctly depending on the worker's reconnection logic.

  4. appArmor/docker-default, line 126-141 (link)

    P2 security Broad mount, applies to all containers, not just agent-sandbox

    Replacing the global docker-default profile means every container on the host — postgres, Temporal, the main backend, etc. — now inherits the unrestricted mount, and pivot_root, rules. The README justifies this as necessary because the profile name is hard-coded by the Docker daemon, but it is worth confirming that the team has accepted this as the intended trade-off: a container breakout from any service can now attempt bind-mounts or pivot_root operations that would have been blocked by the upstream profile.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "Merge pull request #265 from tryretool/l..." | Re-trigger Greptile

@lukefoster11 lukefoster11 changed the title R2 Enable New App Builder Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants