Code sandbox
When an agent runs code, it does not run on the backend. Execution happens in an isolated, disposable environment with controlled egress, bound to one member and one conversation.
Isolation
Each run gets a locked-down, operator-created worker. The standard Kubernetes
deployment uses the cluster's normal container runtime; a platform operator can
attach a gVisor RuntimeClass for stronger syscall isolation. Network
egress is controlled rather than open: it is on by default
(SANDBOX_EGRESS_ENABLED = true) with the public_internet scope, so an
approved run can reach public HTTP/S addresses and nothing else. The
environment is disposable and scoped to the run.
Installing gVisor is not a Kaizen or Package Creator prerequisite. It must be installed and registered on eligible nodes by a cluster administrator only when the platform chooses that stronger posture. Staff who can install a Kamiwaza extension but cannot administer the cluster continue to use the normal operator-managed sandbox pool.
Three services cooperate:
| Service | Role |
|---|---|
| sandbox-controller | Allocates instances, maintains the warm pool, issues leases |
| sandbox-worker | Executes the code, single-tenant while leased |
| sandbox-egress-proxy | Mediates any outbound network access |
The worker has two jobs rather than one. Besides executing code it can host a browser session: one Chromium process rendering a live web page for a member's pane, inside the same boundary and on the same pool. That is a different workload on an existing service, not a fourth service, and it is described below.
The warm pool
Booting an environment per request would make every run_code call feel slow,
so Kaizen keeps a small, operator-configurable pool allocated.
| Setting | Default | Meaning |
|---|---|---|
SANDBOX_WARM_POOL_MIN | 3 | Instances kept warm |
SANDBOX_POOL_TARGET_IDLE | 1 | Idle target after burst |
SANDBOX_POOL_MAX_ALLOCATED | 32 | Hard ceiling |
SANDBOX_MAX_CONCURRENT_CREATES | 3 | Creation concurrency |
SANDBOX_BURST_IDLE_TTL_SECONDS | 300 | How long burst capacity lingers |
SANDBOX_ACQUIRE_WAIT_SECONDS | 30 | Lease wait before failing |
SANDBOX_ACQUIRE_QUEUE_MAX | 256 | Queue depth |
SANDBOX_MAX_CONCURRENT_PER_MEMBER | 2 | Per-member fairness |
A ready sandbox becomes single-tenant when leased. After use, Kaizen kills leftover processes and strictly wipes the workspace before returning it to the pool. A sandbox is discarded and replaced only when that reset cannot be proven clean — the reuse decision requires evidence, not absence of error.
Deployment-wide allocation and leases
Valkey is the sole allocation authority for controller-created workers. Warm, leased, resetting, creating, and retiring states; FIFO tickets; the hard pool ceiling; and per-member concurrency are deployment-wide, so routing a request to another API replica does not create a second pool or bypass fairness.
State transitions carry operation tokens and renewable ownership. Kaizen renews
controller leases every SANDBOX_LEASE_HEARTBEAT_SECONDS (60), while
SANDBOX_LEASE_TTL_SECONDS (900) bounds stale ownership. Under the platform
operator the controller claims workers through the platform instead, and each
claim's deadline is fixed when it is created: a renewal only confirms the
worker still exists, and a worker past its deadline is gone. Reset publishes a
worker as warm only after a successful wipe and matching operation token.
The worker does not validate Kaizen's operation token. Crash takeover therefore does not reassign an expired endpoint: Kaizen asks the controller to delete it, confirms it is absent, and only then frees capacity for a clean replacement. An ambiguous create remains allocated until deployment inventory proves the worker absent or Kaizen confirms its deletion.
Dynamic controller mode requires Valkey. If coordination or controller inventory is unavailable, admission fails closed; no API replica falls back to an independent in-memory pool. The reconciler retries worker image publication after a startup coordination outage before rebuilding the warm pool, so it cannot silently create workers from a stale release image.
Execution bounds
| Setting | Default |
|---|---|
SANDBOX_MAX_WALL_SECONDS | 30 |
SANDBOX_MAX_OUTPUT_BYTES | 1 MiB |
SANDBOX_MAX_ARTIFACT_BYTES | 10 MiB |
SANDBOX_WORKSPACE_MAX_BYTES | 8 MiB |
SANDBOX_HASHLINE_STATE_MAX_BYTES | 64 MiB |
SANDBOX_DEPENDENCY_INSTALL_TIMEOUT_SECONDS | 120 |
Browser sessions on the same workers
A browser session leases an ordinary sandbox worker and starts the current browser-media adapter, an isolated Xorg display with virtual input, headed Chromium, PulseAudio, and a loopback Playwright bridge inside that container. The adapter captures the desktop for members; Playwright gives the agent structured access to that exact Chromium instance. There is no second browser whose state can drift from the page members see.
The browser-media adapter is built from checksum-pinned source using the FIPS Go builder. Its bundled client is rebuilt from a hardened lockfile, patched to require relay-only ICE, and paired with an OpenH264 capture pipeline available in the hardened runtime image.
The bundled client uses the adapter's compatibility WebSocket protocol, which is enabled explicitly rather than through deprecated configuration drift. The pinned adapter release contains the required legacy authentication-bypass fix. Kaizen owns the load-bearing boundary: conversation authorization on every relay, private-peer termination, and filtering of media-adapter administrator operations. The adapter's origin-level session cookie is disabled to avoid collisions across concurrent browser-session path prefixes.
Three properties of that Chromium exist because of how the sandbox reaches the network, and each one was a member-visible hang before it was a rule.
The only route out is the CONNECT proxy, and the pod's sole UDP rule reaches TURN, so QUIC is disabled: a protocol with nowhere to go otherwise stalls every host that prefers it until Chromium abandons a blackholed handshake. Chromium's own vendor service calls are disabled too, and not for tidiness — they opened an HTTP/2 session to a vendor front end, a member's navigation to a host on that front end reused the pooled session, and the aborted session cancelled the navigation with no reason a member could read. Retrying did not help, because every attempt reused the same pool.
Hostnames are resolved absolutely, both in the worker's admission check and in
the egress proxy. The pods run with a Kubernetes search list and a high ndots,
so a public name was tried against the cluster suffixes first and paid a
five-second resolver timeout on each lookup, twice per navigation. Asking for
the name the caller actually meant is also the safer resolution, because it
removes any path by which a public host could resolve through
svc.cluster.local.
The captured screen is the driver's own pane in device pixels, clamped and rounded to even dimensions for the encoder. A fixed capture rescaled into whatever box the pane had is what made text soft; the screen mode is changed through the media adapter's API and Chromium's window follows it, so the desktop, the window, and the member's pixels stay the same size.
One pool, one allocator. Browser sessions and code runs use the same worker registry, ceilings, fairness, and queue. The browser lease has its own namespace so it does not evict the same member's code workspace. A browser holds a worker for its whole session, bounded by the 15-minute idle reap rather than the 30-second execution ceiling. Capacity must therefore follow concurrent browser sessions.
Two worker ports. Port 8091 remains the authenticated worker control API
and port 8080 is the browser-media HTTP and signaling adapter. The controller
creates both on the worker's ClusterIP Service. Neither is reachable from a
page, and two independent controls say so rather than one: the loopback
subtraction above sends a page's request for either port to the egress proxy,
which refuses it, and the platform sandbox NetworkPolicy admits ingress to a
sandbox pod only from this extension's own pods, so a sibling sandbox cannot
open them either. Port 8080 binds the pod address because Kaizen relays to it
from another pod; that reachability is what the NetworkPolicy bounds. The
NetworkPolicy is platform-owned rather than declared in this repository, so an
operator confirms it rather than inheriting it from Kaizen. Kaizen proxies port
8080 under /api/browser-sessions/{id}/viewport/ only after conversation
authorization, and what it proxies there is the adapter's static client and
nothing else: every path under the adapter's own api/ namespace is refused,
because the shipped client speaks the protocol over the socket and never calls
that API. Refusing the namespace rather than enumerating administrator paths is
deliberate - the enumeration missed api/room/screen, which reconfigures the
screen every viewer sees, and api/login, which with a forwarded token minted
an adapter credential for the rest. The check compares the path it read against
the path the adapter would route, so a request that only becomes one of those
after decoding or dot-segment resolution is refused too, without the guard
having to recognize the spelling. This matters because it is the only control
in front of those operations: the adapter runs with its own authentication
disabled, so every viewer's adapter session is an administrator session there.
Relay-only WebRTC. Dynamic sandbox pods never advertise public addresses or consume NodePorts. The member browser and egress-denied worker both connect to the operator's TURN pool. The platform sandbox NetworkPolicy must allow the exact plain-TCP TURN authority; no arbitrary destination or test-supplied endpoint is accepted. Kaizen issues the worker and member client the same short-lived TURN REST credential. Missing, malformed, or network-policy-blocked TURN configuration refuses the open.
Shared input and agent control. The media adapter's WebRTC data channel carries member pointer and keyboard input into the X display. Playwright owns that same Chromium over an inherited pipe with no debugger socket. Browser policy still admits the opening URL and the egress proxy remains the final network boundary; neither input path can bypass them.
Responsive quality. Browser panes keep a monitor-scaled width separate from Artifact-pane preferences. New sessions capture a 1600×1000 desktop, and the OpenH264 bitrate derives from current pixels and frame rate rather than an encoder default. The client preserves capture aspect while the pane resizes, avoiding distortion; widening a pane therefore uses available source detail.
Private text. The private actor keeps their media connection. Other viewers cannot start a relay, and already connected signaling relays are closed. The adapter then removes those peers and media connections, so privacy does not depend on hiding the iframe. The assistant's read and action paths are refused for the same interval.
Lifecycle. Opening sweeps idle sessions, leases a worker, mints egress and TURN credentials, and starts the runtime against a fresh Chromium profile. Closing terminates the media adapter, Chromium, Playwright, Xorg, PulseAudio, and Openbox, deletes the profile, then resets and releases the worker. A close that the worker does not confirm quarantines the lease instead of handing a possibly authenticated profile to another tenant.
Live browser-session state is held by the API process that opened it. A second API replica does not discover that in-memory state unless routing keeps the member on the same replica. This is unchanged from the existing sandbox lease-session boundary and remains the scaling limit to remove before active-active browser-session routing.
Workspace identity
A workspace is keyed by actor plus conversation — not by member alone, and not by conversation alone.
This is what makes shared chats safe. In a collaborative room each participant keeps a different workspace, so collaborators never share:
- files or durable workspace snapshots,
- installed packages and caches,
- environment values, processes, credentials, or provider grants,
- private stdout, stderr, or files that were not returned as retained outputs.
Workspaces persist across runs where SANDBOX_WORKSPACE_PERSIST is on, stored
in the object store.
Hashline authority is stored separately from the workspace tar under the same actor-and-conversation identity. It retains normalized full text, four-hex tag history, seen-line provenance, and bounded named registers. The model and untrusted code can observe a tag but cannot read, replace, or transfer the authority record. Worker release, replica changes, and stale-anchor recovery therefore do not turn a compact tag into a bearer capability.
read_workspace accepts only relative paths inside this sandbox workspace.
A rejected read or edit reports its path, file, or stale-anchor error without
treating its absent snapshot as a size overflow. Failed operations do not
advance the stored workspace or Hashline authority. A completed workspace operation whose snapshot
cannot be retained still fails the durability check.
The worker starts the generated process inside that actor-and-conversation
workspace. Agent-written code creates outputs with relative filenames such as
report.pptx; /work is an internal parent and is not a writable output path
for model code. Only validated files returned in the tool result's typed
download list become member-visible attachments. Editable canvas references
and run-code downloads remain different event fields, so a native file cannot
be opened accidentally as an HTML artifact.
A file-producing tool can declare the relative filenames it intends to create. The shared runtime projects those declarations as transient, non-downloadable generation progress and replaces them with the validated retained references at tool completion. The declaration grants no filesystem access and creates no database object on its own.
Persistent analysis kernels
run_code is ephemeral unless the model explicitly selects a persistent
Python or Node kernel. Persistent mode retains supported variables between
cells without pinning a worker in the normal scalable configuration.
After each cell, Python checkpoints individually pickleable values and Node
checkpoints the JSON-serializable state object into hidden files inside the
owner-scoped workspace. The ordinary bounded workspace snapshot then moves
those files to the object store and releases the worker lease. The next cell
may land on another clean worker, restore the snapshot, and continue. Open
process handles, sockets, threads, credentials, and provider state are never
checkpointed. If the complete workspace exceeds SANDBOX_WORKSPACE_MAX_BYTES,
the cell reports an error rather than claiming its new kernel state is durable.
With workspace persistence disabled, the existing single-worker affinity path keeps the checkpoint files in that worker's retained workspace directory while the actor-and-conversation lease lives. Every cell still runs a fresh, short- lived interpreter that restores the checkpoint on start — no interpreter process survives between cells in either mode, so only checkpointable values carry forward. This is a compatibility mode, not the horizontally scalable default.
Persistent cells remain egress-free and do not retain mounted resource file descriptors. Instead, code may call the read-only tool names advertised by the active provider snapshot. The trusted worker controller holds the opaque run capability and descriptor digests, sends each callback to the owning backend replica, and returns only the normal bounded tool payload. The code process never receives callback credentials. Write tools and recursive code execution are excluded.
Read-only projection of authorized resources
Linked chat documents and artifacts cross into the sandbox only as an exact, bounded, read-only projection.
In a private chat the projection covers every document authorized for the turn — files the member attached earlier in the conversation as well as any attached with the current message — so a follow-up question reaches the same originals the model is already reading. The composer clears its chips onto the message it sends, so a turn that asks a second question about an attached file carries no attachment of its own; the server resolves the conversation's attachment scope under the member rather than trusting the ids a request happened to repeat. Exact documents that an agent re-authorized from a retrieval result join the same projection for the current turn only. A maximum of 20 files and 8 MiB is projected, filenames are assigned by the server, and original bytes are pinned by digest. Private Library files are not silently projected into a shared room.
Every browser chat is a room, so a chat nobody else has joined takes the room
path too. A file picked from Documents — Use in Chat, Chat with
selected, or a Library pick in the composer — is bound to that chat as a
private retrieval reference rather than linked into the room, so the room's
own projection never carries it. When the turn's audience snapshot is exactly
the initiating member, every run also receives that member's selection, as a
private chat's attachments are offered on every call: code that explores the
environment before it reads the manifest still finds the file. A persistent
kernel mounts no resources, so it never loads the selection, and code there that
names the shared-resource contract is told so (resource_persistent_kernel)
rather than that no file is attached. Offering it on every call costs one
roster query, one visibility read per selected document, and an object-store
read of up to the 8 MiB bound for each ephemeral run in such a room — the same
cost a private chat's attachments carry. The selection is
re-resolved under the member,
appended after the room's verified files, held to the same 20-file and 8 MiB
bound as a whole, and a document already linked into the room is not
projected twice. The room's manifest digest is still checked against the
admitted manifest first; only the dispatch copy of the scope is re-bound to
the merged set the worker receives.
The admission snapshot alone does not decide this. Immediately before the
selection's bytes load, the projector re-reads the room's live audience — its
version and its active roster, including members who reach the chat through
a Project — in one query, and refuses with resource_audience_changed unless
the version still equals the admitted one and the initiating member is the
only active member. The moment anyone else can see the room — a direct
participant or a member of the chat's Project — the selection stays out.
A room whose admission snapshot already lists anyone besides the initiating member never considers the selection at all: the run proceeds on the room's linked files, or, when it has none, reports that no file is attached to the chat.
Otherwise, a selection that cannot be projected is withheld, not fatal,
whenever the run did not ask for files or the room has linked files of its
own. A run asks for files by naming the shared-resource contract
(KAIZEN_SHARED_RESOURCES_MANIFEST or KAIZEN_SHARED_RESOURCES_DIR). The
audience changing after
admission, a selected document still processing or failed, the selection on
its own exceeding the bound, or the room's files and the selection together
exceeding it all drop the selection and log a withheld event with the bounded
reason (run_code.private_selection_withheld before dispatch,
sandbox.private_selection_withheld for the combined bound), and the run
proceeds on the room's linked files under the admitted scope, exactly as it
would have without a selection. Only when the run asked for files and the
selection was its sole source does the member see the specific reason —
resource_audience_changed, resource_not_ready, resource_failed,
resource_over_count, resource_over_bytes, or resource_unavailable — so a
large folder selection never refuses a plain computation.
Two residual limits are stated plainly. A member who joins in the moments between that re-read and the worker finishing the run still sees whatever the run writes, because a run's outputs are linked to the room at its audience at link time. And outputs computed while the chat was private stay in the chat after it is shared, so a later participant can read and project them even though they can never project the original Library file.
Code discovers the projection through two environment variables.
KAIZEN_SHARED_RESOURCES_DIR is the read-only directory, and
KAIZEN_SHARED_RESOURCES_MANIFEST is a JSON array whose entries carry the
resolved path alongside the reference id, kind, immutable version, content
digest, server-assigned filename, media type, and size. Both variables are
absent when the turn has no authorized file. Opening the manifest entry's
path is the documented contract, and the run_code description states it,
so an agent never rebuilds a location from a display name the server may have
had to sanitize or de-duplicate.
Before code starts, the sandbox verifies the room's current audience version, resource-manifest digest, file paths, sizes, and content digests. Projected files live outside the participant's writable workspace and are never included in its saved snapshot.
The digest check compares the room, less the files the same run linked since, with the manifest the turn was admitted with. A link made during a run records that run's turn and member, and its journal event falls after the position the admission was read at; the sandbox scope carries both the admitting turn and that position.
- The run's own new links. A run's own earlier code output, tool result, or created artifact is recognized as its own: it does not stop the run, and it is left out of that run's projection, so the dispatch carries exactly the admitted manifest that the worker re-verifies. It is projected from the next turn.
- Files the run's code wrote. They are separately still in the run's writable workspace, which is kept or restored between runs within its snapshot bound, so later code in the same run can usually open them there.
- Revisions of admitted files. A new version of an artifact or document the
run was admitted with keeps its root and leaves the admitted version active,
so projecting the admitted set would hand code the previous version under the
same name. The run is refused instead. From the next turn both versions are
active and both are projected: each manifest entry carries its
version, and one of the two files of the same name is given a unique prefix, so code tells them apart by the manifest rather than by the file name. - Earlier attempts of the same input. A file an earlier attempt linked before this admission is part of the admitted manifest.
Anything else that changed stops the run.
The projection includes uploaded documents, editable member artifacts, and
native files retained from earlier code runs. A later conversion therefore
reads the exact stored .pptx, PDF, image, or other retained output instead of
trying to reconstruct binary data from model context. Binary references add
metadata only to the prompt; their bytes cross only this bounded sandbox data
plane.
If the actor, conversation, capability, manifest, or read-only projection cannot be proven, execution fails closed. Kaizen does not fall back to an owner workspace, a shared workspace, the host, or in-process execution.
Failing closed is not the same as being unavailable, and the member is told which one happened. A set that exceeds the count or byte bound, a file still being processed, a file whose processing failed, and a file the member can no longer read each return their own reason naming the affected file and the applicable limit. A room whose files changed after the turn started, and a file the run itself updated earlier in the turn, each return their own reason too, asking the member to try again. Only a genuine tier problem reports that code execution is unavailable. Kaizen never answers a raw-file request from the extracted text instead: an agent that cannot open the original says so.
Shared-turn outputs
Raw stdout, stderr, workspace snapshots, and files that were not returned as outputs remain private to the initiating participant. A validated file that the assistant creates for a shared turn is retained under that participant and linked into the exact room through the normal size, type, audit, and conversation-resource policy. The tool result, terminal delivery guard, journal linker, and chat card all use the same metadata-only reference.
The model receives bounded stdout, stderr, validation diagnostics, and retained file metadata so it can repair a failed generation intelligently. Member-facing tool rows receive a separate sanitized projection with the outcome, exit state, and retained references only; sandbox diagnostics are not exposed as UI copy.
Linking one retained output does not expose the rest of the workspace or the participant's broader artifact library.
Package installation
Baked Python stack
The worker image ships a curated, pinned Python data and document stack, so
everyday analysis and document work needs no registry at all — including on an
air-gapped installation, which has none. It is installed at image build time
from a hash-pinned lock (sidecar/sandbox/python-bundle/requirements.txt,
binary wheels only, --require-hashes --no-deps) for both linux/amd64 and
linux/arm64, and the build fails unless every pin is installed, every
promised import imports in an isolated interpreter, and the common round trips
(Excel through both writers, a Matplotlib PNG, Word, PowerPoint, a ReportLab
PDF read back with pypdf, a SciPy test) succeed on the FIPS base.
The baked set is pandas, NumPy, SciPy, Matplotlib, openpyxl, XlsxWriter,
python-docx, python-pptx, pypdf, Pillow, ReportLab, Requests, Beautiful Soup,
lxml, tabulate, and PyYAML, plus their own dependencies (31 distributions in
all). kaizen.sandbox.bundled_python is the table; dependency resolution
consults it before the broker, so an import or requested name the image already
provides is satisfied locally and no broker grant is minted for it. The worker
then skips any requirement whose exact distribution is already installed. A
request for a different pinned version (for example pandas==2.2.3) still
installs through the broker into the run's workspace, ahead of the baked copy.
humanize and folium are deliberately not baked: they keep exercising the
broker path. The license texts for every baked distribution ship in the image
under /usr/share/licenses/kaizen-sandbox-python/.
Broker
Anything not baked reaches a package registry through a broker rather than
the open internet: SANDBOX_PACKAGE_BROKER_URL fronts Python and JavaScript registry
paths with short-lived tokens (SANDBOX_PACKAGE_TOKEN_TTL_SECONDS, 300) and a
registry-index ceiling (SANDBOX_PACKAGE_MAX_INDEX_BYTES, 8 MiB) plus a
per-file size ceiling (SANDBOX_PACKAGE_MAX_FILE_BYTES, 100 MB).
The broker is served by the parent API on port 8000, matching Kamiwaza's
sandbox-to-parent NetworkPolicy contract. This keeps dependency installation
working without granting general network access or adding a broader egress
rule. Rewritten Python download links retain their wheel filename so pip can
evaluate compatibility while the actual allow-listed archive URL remains
hidden behind the token-scoped broker. PEP 658 metadata sidecars are allowed
only as the exact .metadata companion of an already approved wheel URL.
Python dependency inference distinguishes import names from installable
distribution names. In particular, import docx resolves to python-docx and
import pptx resolves to python-pptx (both are baked, so neither reaches the
broker unless a different version is pinned); the model still cannot provide a
raw registry URL or installer flag.
When the configured registry cannot be reached — the normal state of an
air-gapped installation that kept the public defaults — the broker bounds the
connection attempt to five seconds, answers 503 with
X-Kaizen-Package-Registry: unreachable, and refuses further requests to the
host that failed (a registry, or the download host it redirected to) for fifteen
seconds without dialling it again. That cooldown is held by
the broker only: the 503 deliberately carries no Retry-After, because pip
and npm honour it on a 503 and would sleep the whole cooldown before each
retry. The installers themselves are bounded too — pip runs with one retry and
a 60-second socket timeout, npm with one fetch retry backing off at most two
seconds. When an install fails, the worker asks the broker once whether the
registry itself is unreachable, because pip reports a refused index only as "no
matching distribution". The run fails within seconds with an error that says the
registry is unreachable and that baked packages need none, instead of stalling
for the two-minute install timeout. A failed install returns before the code
executes, and the worker says so in a structured not_run record on its
response (reason dependency_install_failed, the requested package names, and
whether the registry was unreachable or the install timed out). The parent
reads that record rather than stderr: it does not treat the run's missing
workspace snapshot as an over-bound snapshot (nothing ran, so the prior snapshot
stays authoritative), and the run_code result carries a structured
dependency_install_failed error naming the package and the cause, which is the
reason the member reads. A dependency request the worker rejects before installing
(reason dependency_request_invalid), a refused persistent-kernel contract, and
an unsupported language are recorded the same way. The parent honours the record
only beside a nonzero exit, so a malformed record cannot turn a dropped snapshot
into a reported success. With no registry configured at all
(SANDBOX_PACKAGE_REGISTRIES={}), no install is attempted and the run's error
names the baked stack. A pin of a baked package to a version other than the
baked one (for example pandas==2.2.3) fails before the code runs, naming the
version that is available, rather than silently running against the baked copy.
When a run also installs other packages, an exact pin of a baked package is
kept in that install so a transitive dependency cannot shadow it. See Air-gapped installations.
Before a generated .pdf, .docx, .pptx, or .xlsx enters the durable
artifact store, Kaizen verifies its native structure. A PDF must contain its PDF
header and terminal marker; an Office file must be a ZIP-based OOXML package
with the expected content-types manifest and main document part. Invalid
native-file output is removed from the artifact manifest, the run becomes an
error with bounded diagnostics, and no misleading download reference is
persisted. The final-answer pipeline additionally requires a durable attachment
with the requested extension or native content type before it permits a success
claim for an explicit PDF or Office-file request.
Native Pi package validation
Package Creator uses this same worker pool to validate locally authored native Pi proposals before admission. The application derives the validation profile; the model cannot select a Pi runtime version or provide a worker command. The sandbox image installs the exact production dependency graph from the Pi sidecar lock and reports the runtime version it actually observes.
Before execution, the backend checks package identity, dependencies, lifecycle
scripts, resource paths, and reviewed declarations. A skill-only package then
uses Pi's real skill loader without a fabricated Node test. An extension
package runs node:test first and reaches Pi's real extension loader only when
those tests pass. The worker then constructs a new validation tree from the
server-authoritative file set and reinstalls dependencies there, using only the
time left in the original validation deadline. Test-time edits therefore never
become loader input.
The Pi loader is split across two processes. A disposable child imports the authored extensions and reports a bounded observation. The trusted parent does not import authored code; it validates that observation, reads static resources, and writes the final result through an inherited descriptor owned by the parent. Malformed child output includes only a bounded stderr tail for diagnosis. The worker returns the resulting structured resource surface and diagnostics; the backend compares it with the reviewed Kaizen manifest.
Validation shares the deployment's standard or gVisor-selected worker posture. Unavailable capacity, failed tests, loader errors, unsupported Pi hooks, and surface drift all fail closed. Authored content never moves to the trusted backend or secretless package runner as a fallback.
Egress
Outbound access goes through the egress proxy with per-run signed tokens
(SANDBOX_EGRESS_TOKEN_TTL_SECONDS, 10 minutes by default) and one selected
mode. SANDBOX_EGRESS_ENABLED is on by default, and the default mode is
public_internet:
| Mode | Reachable destinations |
|---|---|
isolated | None |
public_internet | Public global addresses on allowed ports |
private_network | Only configured private CIDRs |
host_allowlist | Exact configured hosts, resolved and checked on every connection |
Loopback, link-local, multicast, unspecified, and reserved addresses remain denied in every mode. DNS answers are re-resolved and pinned by the proxy; a mixed allowed/disallowed answer is rejected. The signed token binds mode, hosts, CIDRs, ports, run, actor, expiry, and policy revision.
That decision has one implementation. The service that mints a grant, the proxy that re-checks it at the wire, and any worker that has to refuse a destination before dereferencing it all call the same module rather than keeping their own copy of the rule. Copies of a security predicate can only be compared after they diverge; one definition cannot diverge.
The mode comes from the deployment, and only from it
One resolution step turns operator configuration into the scope a run may use,
and every sandbox consumer reads it per run. A member request, a tool argument,
a model's output, and a sandbox process are all excluded from that step by
construction: run_code still accepts a provider hint, but the hint survives
only in host_allowlist mode, where it names registries an administrator
already declared. In the address-scoped modes the mode alone defines what a run
reaches, so nothing has to invent a provider alias to get the deployment's
ordinary network access.
Resolution is fail-closed in both directions. A configured value this release
does not define — a typo, a value from a newer release, a missing setting —
resolves to isolated rather than to the packaged public_internet default. A
mode the deployment cannot actually hold at the chokepoint, because the port
list is unusable or there is no proxy address or no field key to sign a grant
with, also resolves to isolated. The scope in force is therefore the
configured scope only while it is enforceable, and the difference is shown to
the operator rather than inferred.
Fresh configurations use a 10-minute token lifetime; an existing explicit value remains valid up to the 4-hour compatibility ceiling. Each issued token gets a unique execution identifier in addition to its actor and policy binding.
Grants are signed, and the proxy that checks them cannot issue them. Kaizen derives a signing key from the deployment's own field key, so nothing has to be configured or kept in step, and the proxy verifies with the public half it reads from Kaizen. Compromising the proxy therefore does not let anything mint a grant.
The path is still fail-closed on its prerequisites. With no field key, an
unreachable proxy, a proxy that has not yet read a verifying key, or isolated
selected, a run receives no credential and the worker network stays denied at
the deployment layer — being enabled is not the same as being open.
Every policy value is read when a run is dispatched, so changing the mode, hosts, ranges, or ports in Admin applies to the next run with no restart. Signing keys need no operator attention at all: they follow the field key, and the proxy picks up a change on its next verification.
The active public, private, or host-allowlist policy applies to a normal code run without requiring the model to invent a provider alias; a destination/provider hint may narrow but cannot broaden that policy.
Compression does not expand authority
Where a member enables Tool-result compression, Kaizen may shorten eligible
public run_code stdout and stderr after execution and after all
authorization and policy filtering have completed.
It changes only the model-bound projection. It does not open host-shell access, add network egress, read another workspace, or grant a tool. The worker keeps raw command output canonical, and a compression failure or timeout falls back to the original already-authorized output.
Related
- Sandbox pool sizing — operating the pool
- Isolation boundaries
- Artifacts
- Browser sessions — what a member sees
- Member API — the session routes and socket contract