Skip to content

Add Google Cloud Run sandbox integration#5055

Open
htahir1 wants to merge 10 commits into
developfrom
feature/cloudrun-sandbox
Open

Add Google Cloud Run sandbox integration#5055
htahir1 wants to merge 10 commits into
developfrom
feature/cloudrun-sandbox

Conversation

@htahir1

@htahir1 htahir1 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What this does

Adds a cloudrun sandbox flavor to the GCP integration, backed by Cloud Run sandboxes (public preview) — Google's new fast, strongly isolated environments for executing untrusted (e.g. LLM-generated) code.

Why a bridge?

Cloud Run sandboxes have no standalone REST API: the sandbox CLI that drives them only exists inside a Cloud Run service deployed with --sandbox-launcher. So this follows the bridge pattern established by the Cloudflare sandbox flavor (#4907): a small HTTP service deployed once to Cloud Run mediates between the ZenML client and the local sandbox CLI.

step code ──HTTPS + ID token──▶ bridge service (--sandbox-launcher) ──sandbox CLI──▶ sandboxes

The deployable bridge (sync stdlib HTTP server, ~450 lines, speaks the same wire protocol v1 as the Cloudflare bridge Worker) ships as examples/cloudrun_sandbox_bridge/ with a Dockerfile and one-command deploy instructions.

Key implementation decisions

  • Lives in the existing gcp integration (no new deps — httpx is core, google libs already required). Flavor name cloudrun, mirroring the vertex naming precedent.
  • Auth = Cloud Run IAM ID tokens, not API keys: derived from the standard GoogleCredentialsMixin chain (GCP service connector → SA key file → ADC), with per-credential-type ID-token minting and refresh. The bridge itself contains no auth code — Cloud Run verifies callers before requests reach it.
  • Per-exec env vars supported (native sandbox exec -e), lifting the Cloudflare flavor's NotImplementedError limitation.
  • Snapshots: create_snapshot() exports the filesystem overlay via sandbox tar to a configured GCS prefix; restore() boots a new sandbox from the tarball — parity with Modal, which Cloudflare lacks. Gated on snapshot_uri_prefix.
  • Full design rationale lives in the docs page (docs/book/component-guide/sandboxes/cloudrun.md), the module docstrings, and the bridge README.

Testing

  • 35 new unit tests (httpx.MockTransport, no real bridge/credentials): flavor metadata, config validation, SSE parsing, retry policy, process demux, session lifecycle, env merging, snapshot gating. All pass; base sandbox suite unaffected (124 passed total).
  • ruff, mypy, and pydoclint clean on the new files.
  • Live end-to-end against a deployed bridge not yet run (Cloud Run sandboxes are preview; the CLI surface is isolated in the example bridge). Reviewers should treat the bridge example as needing a manual smoke test before we advertise it.

Reviewer attention

  • The SSE demux/process machinery is deliberately near-identical to CloudflareSandboxProcess — a future refactor could hoist it into a shared bridge-client base once both flavors are in.
  • create_session threads the newer destroy_on_exit parameter from develop's BaseSandbox signature (the Cloudflare branch predates it).

🤖 Generated with Claude Code

Cloud Run sandboxes (public preview) provide fast, strongly isolated
environments for executing untrusted code, but expose no standalone REST
API: the sandbox CLI only exists inside a Cloud Run service deployed
with --sandbox-launcher. This adds a "cloudrun" sandbox flavor to the
GCP integration that talks to a small bridge service deployed on such a
service, following the bridge pattern established by the Cloudflare
sandbox flavor.

The client authenticates with Google-signed ID tokens (Cloud Run IAM
invoker flow) derived from the standard GCP credential chain (service
connector, SA key file, or ADC). Unlike the Cloudflare flavor it
supports per-exec env vars (native `sandbox exec -e`) and filesystem
snapshots to GCS via `sandbox tar`, giving create_snapshot()/restore()
parity with Modal.

The deployable bridge (sync stdlib HTTP server shelling out to the
sandbox CLI) ships as examples/cloudrun_sandbox_bridge/. Design notes
in CLOUDRUN_SANDBOX_SPEC.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@htahir1 htahir1 added enhancement New feature or request release-notes Release notes will be attached and used publicly for this PR. labels Jul 9, 2026
)
with _sandboxes_lock:
_sandboxes.pop(sandbox_id, None)
shutil.rmtree(_share_dir(sandbox_id), ignore_errors=True)
data = self._read_body()
staging_name = f"put-{uuid.uuid4().hex}"
staging_host = os.path.join(_share_dir(sandbox_id), staging_name)
with open(staging_host, "wb") as f:
f"{shlex.quote(dest)}",
)
finally:
os.unlink(staging_host)
f"{shlex.quote(f'{SHARE_MOUNT}/{staging_name}')}",
)
try:
size = os.path.getsize(staging_host)
self.send_header("Content-Type", "application/octet-stream")
self.send_header("Content-Length", str(size))
self.end_headers()
with open(staging_host, "rb") as f:
with open(staging_host, "rb") as f:
shutil.copyfileobj(f, self.wfile)
finally:
os.unlink(staging_host)
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Documentation Link Check Results

Absolute links check passed
Relative links check passed
Last checked: 2026-07-17 10:28:21 UTC

htahir1 and others added 3 commits July 9, 2026 11:33
A linked GCP service connector using the user-account, oauth2-token, or
external-account auth methods returns credentials with no ID-token
counterpart. Previously these fell through to the ambient-ADC fallback,
which would silently authenticate to the bridge as a different identity
than the connector the user configured. Raise an actionable error
instead, and keep the ADC fallback only for the no-connector path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reuse: retry backoff now uses exponential_backoff_delays (adds jitter,
avoiding retry lockstep across concurrent steps), utc_now, and the
existing GCP_PATH_PREFIX constant.

Efficiency: token refresh no longer holds its lock on the fast path or
rebuilds the google transport per refresh; finished processes are pruned
from long-lived sessions; newline-free exec output is flushed past 1 MiB
instead of growing the remainder unboundedly; plain bridge calls get a
real read timeout (only SSE streams disable it); the bridge example
reuses one GCS client, streams uploads to disk, and size-checks
downloads inside the sandbox before copying.

Simplification: single backoff site in the retry loop, snapshot no
longer round-trips an echoed URI, exit frames dispatch on event kind,
dead TYPE_CHECKING/dashboard-URL/regex code removed, duplicate 32 MiB
check collapsed into the upload path, and a Protocol now types the
ID-token credential contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The durable content lives in the docs page, module docstrings, and the
bridge README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@htahir1 htahir1 added the run-slow-ci Tag that is used to trigger the slow-ci label Jul 10, 2026
htahir1 and others added 6 commits July 10, 2026 09:01
The /tmp share-root default and 0.0.0.0 bind in the bridge are both
deliberate on Cloud Run (per-instance in-memory /tmp; containers must
listen on all interfaces behind IAM), so mark them nosec with the
reasoning. Test fixtures use /workspace instead of /tmp.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Critical/high:
- Bridge file staging now opens host-side descriptors with O_NOFOLLOW
  (and O_EXCL on upload) and validates a regular file via fstat, so
  untrusted sandbox code can't redirect a transfer to a host path by
  planting a symlink at the staged name.
- process.kill() now terminates the remote command: exec carries a
  client-generated exec id the bridge registers, and kill() calls a new
  DELETE /v1/sandbox/<id>/exec/<exec-id> endpoint instead of only closing
  the local SSE stream.
- SSE buffers are bounded by total bytes, not line count, and the
  newline-free flush path also respects the cap, so a flood of long or
  newline-free output can't OOM the caller.
- Sandbox ids are client-generated and bridge creation is idempotent, so
  a create whose response is lost is cleaned up (best-effort delete)
  instead of orphaning a sleep-infinity process.
- Fail loud when an explicit service_account_path (not only a connector)
  yields credentials that can't mint ID tokens, instead of silently
  falling back to ambient ADC.

Medium:
- Rebuild the bridge client (and its ID-token credentials) once the
  linked service connector has expired.
- Snapshot object names include a uuid so two snapshots within one second
  don't overwrite each other.
- Bridge uses the documented --env/--workdir CLI flags; README flags the
  preview CLI surface for pre-release verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The base SandboxSession contract documents, for all flavors, that a
relative `remote_path` in `upload_file`/`download_file` resolves against
the session's working directory — the same directory `exec()` uses as its
default cwd. The Cloud Run flavor diverged: `_exec` honored
`default_cwd`/`settings.cwd`, but file transfer anchored relative paths at
the sandbox root, so `exec("cat foo.txt")` and `upload_file(src, "foo.txt")`
disagreed about where "foo.txt" lived whenever a cwd was configured.

Resolve relative remote paths against the session's default cwd before the
existing sanitization runs, mirroring exec. Absolute paths are unchanged and
the `..`-escape guard still refuses paths that climb above the cwd.

Also tighten the per-exec env docstring (values are hidden from the sandboxed
process's command line but visible in the trusted bridge host's process list)
and document the missing per-task image support in the docs Limitations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bandit's B108 flags hardcoded /tmp paths; the literals here are example
remote sandbox paths, not real temp-file usage, so switch the example
to /srv/workspace instead of suppressing the check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request release-notes Release notes will be attached and used publicly for this PR. run-slow-ci Tag that is used to trigger the slow-ci

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants