@namzu/sandbox 13.0.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +309 -0
- package/README.md +151 -0
- package/dist/backends/firecracker/protocol.d.ts +22 -0
- package/dist/backends/firecracker/protocol.d.ts.map +1 -1
- package/dist/backends/firecracker/protocol.js.map +1 -1
- package/dist/backends/firecracker/transport.d.ts +104 -9
- package/dist/backends/firecracker/transport.d.ts.map +1 -1
- package/dist/backends/firecracker/transport.js +139 -13
- package/dist/backends/firecracker/transport.js.map +1 -1
- package/dist/backends/kubernetes/egress-policy.d.ts +219 -0
- package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -0
- package/dist/backends/kubernetes/egress-policy.js +314 -0
- package/dist/backends/kubernetes/egress-policy.js.map +1 -0
- package/dist/backends/kubernetes/index.d.ts +374 -0
- package/dist/backends/kubernetes/index.d.ts.map +1 -0
- package/dist/backends/kubernetes/index.js +671 -0
- package/dist/backends/kubernetes/index.js.map +1 -0
- package/dist/backends/kubernetes/k8s-client.d.ts +125 -0
- package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -0
- package/dist/backends/kubernetes/k8s-client.js +246 -0
- package/dist/backends/kubernetes/k8s-client.js.map +1 -0
- package/dist/backends/kubernetes/lease.d.ts +119 -0
- package/dist/backends/kubernetes/lease.d.ts.map +1 -0
- package/dist/backends/kubernetes/lease.js +151 -0
- package/dist/backends/kubernetes/lease.js.map +1 -0
- package/dist/backends/kubernetes/objects.d.ts +282 -0
- package/dist/backends/kubernetes/objects.d.ts.map +1 -0
- package/dist/backends/kubernetes/objects.js +156 -0
- package/dist/backends/kubernetes/objects.js.map +1 -0
- package/dist/backends/kubernetes/privilege-probe.d.ts +136 -0
- package/dist/backends/kubernetes/privilege-probe.d.ts.map +1 -0
- package/dist/backends/kubernetes/privilege-probe.js +185 -0
- package/dist/backends/kubernetes/privilege-probe.js.map +1 -0
- package/dist/backends/kubernetes/sandbox.d.ts +123 -0
- package/dist/backends/kubernetes/sandbox.d.ts.map +1 -0
- package/dist/backends/kubernetes/sandbox.js +299 -0
- package/dist/backends/kubernetes/sandbox.js.map +1 -0
- package/dist/backends/kubernetes/transport.d.ts +122 -0
- package/dist/backends/kubernetes/transport.d.ts.map +1 -0
- package/dist/backends/kubernetes/transport.js +197 -0
- package/dist/backends/kubernetes/transport.js.map +1 -0
- package/dist/backends/kubernetes/workspace.d.ts +381 -0
- package/dist/backends/kubernetes/workspace.d.ts.map +1 -0
- package/dist/backends/kubernetes/workspace.js +1064 -0
- package/dist/backends/kubernetes/workspace.js.map +1 -0
- package/dist/index.d.ts +132 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +102 -34
- package/dist/index.js.map +1 -1
- package/dist/testing/sandbox-conformance.d.ts +193 -0
- package/dist/testing/sandbox-conformance.d.ts.map +1 -0
- package/dist/testing/sandbox-conformance.js +465 -0
- package/dist/testing/sandbox-conformance.js.map +1 -0
- package/package.json +5 -4
- package/src/backends/firecracker/protocol.ts +27 -0
- package/src/backends/firecracker/transport.ts +199 -28
- package/src/backends/kubernetes/egress-policy.ts +437 -0
- package/src/backends/kubernetes/index.ts +1012 -0
- package/src/backends/kubernetes/k8s-client.ts +352 -0
- package/src/backends/kubernetes/lease.ts +198 -0
- package/src/backends/kubernetes/objects.ts +363 -0
- package/src/backends/kubernetes/privilege-probe.ts +261 -0
- package/src/backends/kubernetes/sandbox.ts +395 -0
- package/src/backends/kubernetes/transport.ts +286 -0
- package/src/backends/kubernetes/workspace.ts +1386 -0
- package/src/index.ts +257 -35
- package/src/testing/sandbox-conformance.ts +667 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,314 @@
|
|
|
1
1
|
# @namzu/sandbox
|
|
2
2
|
|
|
3
|
+
## 14.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cd43cfe: `SandboxProviderConfig` gains a real arm for `ACIStandbyPoolBackendConfig` (paired with the `ContainerSandboxLayout` it requires, same as the plain container arm). Previously the exported union only covered `ContainerBackendConfig`, `MicroVMBackendConfig` and `KubernetesBackendConfig`, so `createSandboxProvider({ backend: { tier: 'container', runtime: 'aci-standby-pool', … } })` did not type-check even though the backend was fully implemented and `pickBackend` already dispatched to it internally through two `as unknown as` casts. That call now type-checks with no cast.
|
|
8
|
+
|
|
9
|
+
**Minor, not patch:** this is a backward-compatible widening of an exported input type — every config that type-checked before still does, and the only change is that a config shape the runtime already accepted is now also accepted by the type checker. That is additive public surface (a new union arm a consumer's own type-level code can observe), not an implementation-only correction, so it does not qualify for patch under this repo's rule that patch is reserved for changes that leave the public surface untouched.
|
|
10
|
+
|
|
11
|
+
No runtime behavior changed: the ACI backend's construction, options and defaults are exactly what they were.
|
|
12
|
+
|
|
13
|
+
- f54f6f1: The microVM guest agent can now listen on a TCP port and require a per-instance
|
|
14
|
+
token. Both are opt-in through the environment and both are absent from every
|
|
15
|
+
shipped backend's configuration today, so an existing Firecracker deployment
|
|
16
|
+
behaves exactly as it did: with neither variable set, the agent authenticates
|
|
17
|
+
nothing and listens exactly where it listened before.
|
|
18
|
+
|
|
19
|
+
`NAMZU_AGENT_TCP_PORT` is a third listen mode, after `NAMZU_AGENT_UNIX_PATH`
|
|
20
|
+
and the inherited vsock descriptor and in that order, binding `0.0.0.0` for a
|
|
21
|
+
deployment that reaches the guest over a routed network rather than a
|
|
22
|
+
host-local socket. It fails closed: set with neither token variable it is
|
|
23
|
+
refused at startup, naming both, instead of binding an unauthenticated
|
|
24
|
+
listener. Framing, ops, execution leases, terminals, loopback TCP and
|
|
25
|
+
file IO are unchanged — only the listen address differs. A guest configured
|
|
26
|
+
with none of the three still refuses to start, and the message now names all
|
|
27
|
+
three.
|
|
28
|
+
|
|
29
|
+
`NAMZU_AGENT_BIND_TOKEN` makes every op except `healthz` present that exact
|
|
30
|
+
token in the request envelope, from the first frame of a connection; anything
|
|
31
|
+
else is answered `unauthorized` and the connection is closed before a handler
|
|
32
|
+
runs. Comparison is constant-time over a fixed-width digest, so neither the
|
|
33
|
+
value nor its length is learnable by probing. `NAMZU_AGENT_REQUIRE_TOKEN`
|
|
34
|
+
without a preset token is a fallback that binds to the first token seen and
|
|
35
|
+
refuses every other for the life of the process. `healthz` never requires a
|
|
36
|
+
token and never echoes one, so readiness probing needs no secret. An empty
|
|
37
|
+
`NAMZU_AGENT_BIND_TOKEN` is refused at startup in every mode — that is what a
|
|
38
|
+
downward-API injection looks like when it resolved to nothing.
|
|
39
|
+
|
|
40
|
+
A refused connection is destroyed rather than `end()`ed, so a refused peer can
|
|
41
|
+
no longer keep streaming into the agent's frame buffer over the readable half
|
|
42
|
+
that `end()` leaves open. Alongside it, bounds on what an unauthenticated peer
|
|
43
|
+
may spend before the gate — which cannot run until a whole frame is parsed,
|
|
44
|
+
because the credential rides inside the envelope.
|
|
45
|
+
`NAMZU_AGENT_MAX_FRAME_BYTES` (default 256 MiB) caps the length ANY frame
|
|
46
|
+
header may announce, on every listen mode, where the 8-hex prefix used to allow
|
|
47
|
+
4 GiB. `NAMZU_AGENT_REFUSAL_FLUSH_GRACE_MS` (default 1s) likewise applies
|
|
48
|
+
everywhere: it is how long a refusal frame may take to reach the wire before
|
|
49
|
+
the socket is destroyed regardless, so a peer that stops reading cannot hold a
|
|
50
|
+
refusal open. `NAMZU_AGENT_MAX_PREAUTH_FRAME_BYTES` (default 8 MiB),
|
|
51
|
+
`NAMZU_AGENT_MAX_PREAUTH_CONNECTIONS` (default 64),
|
|
52
|
+
`NAMZU_AGENT_MAX_PREAUTH_BUFFER_BYTES` (default 32 MiB),
|
|
53
|
+
`NAMZU_AGENT_PREAUTH_IDLE_TIMEOUT_MS` (default 10s) and
|
|
54
|
+
`NAMZU_AGENT_PREAUTH_DEADLINE_MS` (default 10s) apply only in the token
|
|
55
|
+
modes, so the Firecracker path sees none of them; together they bound what an
|
|
56
|
+
unauthenticated peer can make the agent hold to one number rather than to a
|
|
57
|
+
number per connection. One bound needs no variable and applies everywhere: a
|
|
58
|
+
frame header is exactly nine bytes, so a peer streaming bytes with no newline
|
|
59
|
+
in them is refused on the ninth rather than buffered against a newline that
|
|
60
|
+
never arrives. Note what the pre-auth cap
|
|
61
|
+
costs in a token mode: a `write-file` body shares the first frame with the
|
|
62
|
+
token, so that cap is the ceiling on the body — about 6 MiB of file content at
|
|
63
|
+
the default — and a body above it is refused `frame_too_large`, naming the
|
|
64
|
+
limit and the variable to raise.
|
|
65
|
+
|
|
66
|
+
Two of those pre-auth bounds are shaped by a slow loris rather than by a flood,
|
|
67
|
+
and an operator who tunes them should know which is which.
|
|
68
|
+
`NAMZU_AGENT_PREAUTH_IDLE_TIMEOUT_MS` is an idle timer that every byte resets,
|
|
69
|
+
so on its own it retires only a silent connection;
|
|
70
|
+
`NAMZU_AGENT_PREAUTH_DEADLINE_MS` runs from accept, is reset by nothing, and is
|
|
71
|
+
the bound a peer dripping one byte every few seconds actually meets. And a full
|
|
72
|
+
pre-auth pool evicts its **oldest** unauthenticated connection — answering it
|
|
73
|
+
`too_many_unauthenticated_connections` — rather than refusing the arrival, so a
|
|
74
|
+
poolful of squatters can no longer decide that nobody else, `healthz` included,
|
|
75
|
+
gets served. None of this stops a peer that can reach the port from causing
|
|
76
|
+
churn; the NetworkPolicy ingress rule in front of that port is the boundary,
|
|
77
|
+
and these bounds are defence in depth behind it.
|
|
78
|
+
|
|
79
|
+
The guest protocol version is deliberately NOT bumped: `token` is an optional,
|
|
80
|
+
additive envelope field, and the wire is otherwise byte-for-byte what it was.
|
|
81
|
+
Taking this release therefore requires no coupled rollout — no golden image has
|
|
82
|
+
to be rebuilt and no host has to be redeployed in step with it. A deployment
|
|
83
|
+
that wants the new modes turns them on in its own pod or image environment.
|
|
84
|
+
|
|
85
|
+
Read `NAMZU_AGENT_BIND_TOKEN` as an instance credential, not an isolation
|
|
86
|
+
boundary: the agent and the workload share a uid after deprivileging, so a
|
|
87
|
+
workload can read the agent's own environment out of `/proc`. It is
|
|
88
|
+
per-instance for that reason, and the network rule in front of the agent port
|
|
89
|
+
is the boundary it sits behind.
|
|
90
|
+
|
|
91
|
+
One behaviour changes for every existing deployment, Firecracker included, and
|
|
92
|
+
it is the reason to read this entry before upgrading. The `terminal` op used to
|
|
93
|
+
hand its shell the agent's whole environment; it now gets the same scrubbed
|
|
94
|
+
environment an `execute` child has always had, with every `NAMZU_AGENT_*` and
|
|
95
|
+
`NAMZU_SANDBOX_*` variable removed, and so does the `stty` resize helper behind
|
|
96
|
+
it. That closed a hole this release would otherwise have opened — the bind
|
|
97
|
+
token was visible in an interactive shell — and it means a terminal session no
|
|
98
|
+
longer sees the agent's own settings, such as `NAMZU_SANDBOX_WORKSPACE`. A
|
|
99
|
+
workload that needs a value in its terminal passes it in `env` on the
|
|
100
|
+
`openTerminal` call, which still wins over everything else, `TERM` included.
|
|
101
|
+
The bump stays `minor`: nothing exported changes shape, and the environment a
|
|
102
|
+
terminal is handed is guest-internal behaviour rather than a typed API, but it
|
|
103
|
+
is a change a terminal user can observe.
|
|
104
|
+
|
|
105
|
+
- 5604f73: A Kubernetes backend that claims VM-isolated sandboxes out of an [agent-sandbox](https://github.com/kubernetes-sigs/agent-sandbox) warm pool. New exported types `KubernetesBackendConfig` and `KubernetesClusterAccess`; `SandboxBackendConfig` and `SandboxProviderConfig` each gain an arm for it, so `createSandboxProvider({ backend: { tier: 'microvm', service: 'kubernetes', … } })` type-checks with no cast. Nothing existing changes shape.
|
|
106
|
+
|
|
107
|
+
**Take this upgrade for the new backend, not for a complete one.** This changeset covers acquire, readiness, address resolution and teardown; the execution surface, the acquire-time privilege probe and the lease arrive in the same release under their own changesets, and persistent workspaces (suspend/resume with a block-mode disk) and the cluster manifests follow in later ones. Every other backend is untouched.
|
|
108
|
+
|
|
109
|
+
What it does today, on a cluster running agent-sandbox v1.0.2 with a VM-isolating `RuntimeClass`:
|
|
110
|
+
|
|
111
|
+
- **Warm claim, or a direct Sandbox.** With `warmPoolName`, `create()` POSTs a `SandboxClaim` at that pool and the controller binds an already-running sandbox. Without it, it POSTs a `Sandbox` built from `sandboxTemplateName`'s pod template — necessitated rather than offered, since `SandboxClaim.spec.warmPoolRef` is required and a pool-less claim does not exist in the API.
|
|
112
|
+
- **The claim is pristine.** `spec.env` and `spec.volumeClaimTemplates` are never set, because a claim carrying either is forced to cold-start upstream instead of adopting a pool sandbox. It would still work; it would just stop being fast. Per-sandbox `env`, `memoryLimitMb`, `maxProcesses` and `egress` are therefore refused by name instead of accepted and dropped — set them on the `SandboxTemplate` the pool is built from.
|
|
113
|
+
- **The bound sandbox's own identity.** A pool sandbox keeps the name the pool generated for it, so the backend reads `status.sandbox` back rather than assuming the claim's name; the sandbox `id` is that cluster name, which makes an id in a log line a `kubectl get sandbox` argument.
|
|
114
|
+
- **Nothing left behind.** Every created object carries an absolute `shutdownTime` (default one hour, `claimTtlSeconds`) plus `shutdownPolicy: Delete`, so a host that dies mid-run costs one expiry rather than a leaked sandbox — `ttlSecondsAfterFinished` deliberately is not used, because its timer starts from a `Finished` condition a crashed host never reaches. Every failure on the create path deletes what it created on a separate short budget; an object already gone counts as released.
|
|
115
|
+
- **A per-instance agent credential.** The pod's own `metadata.uid`, read with one `GET` after readiness and delivered to the guest through the downward API. No claim mutation, so the warm path stays pristine.
|
|
116
|
+
|
|
117
|
+
Credentials arrive through `access`: `{ inCluster: true }` reads the projected ServiceAccount volume, and anything else supplies `{ server, ca?, getToken }`. There is no kubeconfig parsing in the package and no new dependency — `@namzu/sandbox` still declares no `dependencies` key.
|
|
118
|
+
|
|
119
|
+
- 437e3d3: The Kubernetes backend now ships the cluster-side half of itself: the guest image, its entrypoint, the `RuntimeClass`/`SandboxTemplate`/`SandboxWarmPool`/`NetworkPolicy`/RBAC manifests, and five scripts that measure the five acceptance criteria against a live cluster — all under `packages/sandbox/k8s/`, which is **not part of the published package** (the `files` array still packs only `dist` and `src`; `npm pack --dry-run` confirms it). None of that is a consumer-visible surface. What IS:
|
|
120
|
+
|
|
121
|
+
**The guest agent now exits cleanly on `SIGTERM` — bumped `minor`, not `patch`, for exactly this.** `k8s/entrypoint.sh` `exec`s straight into `agent/agent.cjs`, so in a real pod the agent is pid 1 of the container's own pid namespace, and Linux leaves a signal whose default action is "terminate" un-applied for pid 1 unless the process installs its own handler. With none registered, an operator deleting a sandbox watched it ride out the pod's full `terminationGracePeriodSeconds` before `SIGKILL` finally landed — every delete paid that tax, cluster-wide, whatever backend dialed the agent. The new handler closes the listener and calls `process.exit(0)` the moment `SIGTERM` arrives. It is not a graceful drain: an in-flight `exec` or an open terminal gets no grace window, the same "gone" a caller already has to handle from a pod the cluster removed out from under it. This is additive guest behavior on a file the Firecracker tier also runs in production — nothing about the vsock/unix paths changes, and `REMOTE_EXECUTION_PROTOCOL_VERSION`/`FIRECRACKER_AGENT_PROTOCOL_VERSION` are untouched — but it is a real, observable change to when a pod actually terminates, which is why this is `minor` rather than `patch`.
|
|
122
|
+
|
|
123
|
+
**Documented for the first time, no behavior change:** a confirmed `exec` cancellation on this backend resolves with the terminal signal/exit code the shared `RemoteExecutionController` observed — the same contract the Firecracker tier's `exec` already honors, now stated on `docs/sdk/kubernetes-sandbox.md` rather than left implicit.
|
|
124
|
+
|
|
125
|
+
Everything else in this change is infrastructure an operator applies by hand — see `packages/sandbox/k8s/README.md` for the apply order and how to run each acceptance script, and `docs/sdk/kubernetes-sandbox.md`'s new deployment section for what each one measures. The five acceptance numbers themselves are not yet in that table; they are gathered by running those scripts against a real Kata cluster, not by this change.
|
|
126
|
+
|
|
127
|
+
- 432db25: The Kubernetes backend's `KubernetesBackendConfig` gains an optional `egress` block: `{ policy: EgressPolicy; networkPolicyName?: string; engine?: 'core' | 'cilium' }` (`engine` defaults to `'core'`). New exported types `KubernetesEgressConfig` and `KubernetesEgressEngine`. Nothing existing changes shape — `egress` is additive and optional, and every Sandbox this backend produces now carries a `sandbox.namzu.ai/template` label it did not carry before, which is additive metadata rather than a behavior change for an existing caller.
|
|
128
|
+
|
|
129
|
+
**What it does.** `deny-all` and `allow-all` translate into a `NetworkPolicy` (egress rules that always leave the cluster's own DNS reachable, even under `deny-all`). `static` and `resolver` — hostname allowlists — are **refused at construction**, before any API call, naming the policy kind and what the cluster needs: core Kubernetes `NetworkPolicy` has no FQDN concept at all. Declaring `engine: 'cilium'` turns that refusal into an emission: a `CiliumNetworkPolicy` with a `toFQDNs` entry for every allowed host. This backend never emits `HTTP_PROXY`/`HTTPS_PROXY` as a substitute for an unenforceable policy — that is the container tier's still-open gap, not repeated here.
|
|
130
|
+
|
|
131
|
+
**Verify, never trust.** This backend never creates the `NetworkPolicy`/`CiliumNetworkPolicy` itself — like the docker backend's network, it is operator-applied. The first `create()` after construction (not `createSandboxProvider`, which still contacts nothing) `GET`s the object named by `networkPolicyName` (default `${sandboxTemplateName}-egress`) and refuses to proceed on a 404 or a shape mismatch, naming the field that is wrong. It runs once per backend and is not cached across a failure, so fixing the cluster and creating again retries it.
|
|
132
|
+
|
|
133
|
+
**Take this upgrade for the label, even without using `egress`.** Every Sandbox this backend creates directly now carries `sandbox.namzu.ai/template: <sandboxTemplateName>` on its pod — agent-sandbox's own controller-owned template label is written only on a Sandbox adopted out of a `SandboxWarmPool`, never on one this backend POSTs directly, so a `NetworkPolicy` an operator writes against a direct Sandbox should select by this new label. A `SandboxWarmPool`'s own `SandboxTemplate` needs the same label added to its `podTemplate.metadata.labels` for a pooled sandbox to match it too — this backend has no path to add it after the fact.
|
|
134
|
+
|
|
135
|
+
RBAC: when `config.egress` is set, the ServiceAccount also needs `get` on `networkpolicies` (`networking.k8s.io`), or `get` on `ciliumnetworkpolicies` (`cilium.io`) under `engine: 'cilium'`.
|
|
136
|
+
|
|
137
|
+
- a3ae83e: The Kubernetes backend now returns a working `Sandbox`, refuses to hand one back until the guest has proved it is deprivileged, and keeps a long run's pod from expiring underneath it.
|
|
138
|
+
|
|
139
|
+
**The execution surface exists.** `exec`, `writeFile`, `readFile`, `listFiles`, `openTerminal` and `openTcpConnection` no longer throw `KubernetesAgentTransportPendingError` — that error is gone, and so is the reason for it. `exec` runs through the same reserve-before-admission controller every other remote backend uses, so an `AbortSignal` terminates the guest process and the peer confirms the termination rather than the host abandoning the wait. `destroy()` kills and awaits every terminal it handed out before releasing the object, which is what makes offering `openTerminal` compliant at all; it is idempotent, and an object something else already reaped counts as released. Every call after it throws `KubernetesSandboxDestroyedError` naming the operation.
|
|
140
|
+
|
|
141
|
+
**`setNetworkPolicy`, `spawnDetached` and `walkFiles` are absent, deliberately.** Egress here is a `NetworkPolicy` on the pool's `SandboxTemplate` and there is no per-running-pod knob, the guest agent has no detached-spawn op, and bounded search is not in this batch. The SDK's contract says a backend that cannot honour an optional method must omit it rather than accept it and quietly do nothing; a test asserts each stays absent.
|
|
142
|
+
|
|
143
|
+
**Every acquire now proves the guest is deprivileged.** Before `create()` resolves, the backend reads `/proc/self/status` through the agent's `execute` op and refuses unless `CapInh`, `CapPrm`, `CapEff` and `CapBnd` are ALL zero and `NoNewPrivs` is 1. Checking `CapEff` alone would pass a container running as uid 0 with the full bounding set. A refusal destroys the instance and rejects, so no handle to an under-hardened sandbox escapes, and the error distinguishes "the probe could not run" (a minimal image with no `cat`) from "the process is privileged". **There is no configuration that turns this off.** If your image's entrypoint does not end with `exec setpriv --reuid --regid --clear-groups --inh-caps=-all --bounding-set=-all --no-new-privs -- node agent.cjs`, or equivalent, `create()` will now reject where it previously returned. The probe carries its own deadline — `min(readyTimeoutMs, 15s)` — so a pod whose agent has wedged (out of memory, an event loop the workload blocked) is refused on your acquire budget rather than held open for the execution controller's five-minute default; budget for a `create()` that can take `readyTimeoutMs` plus that again in the worst case. The probe is a check that the deprivileging happened, not a boundary against a guest that is already compromised — it asks the agent to report its own `/proc/self/status`. The boundary is still the VM and the `NetworkPolicy`.
|
|
144
|
+
|
|
145
|
+
**A handle renews its own lease.** The absolute `shutdownTime` this backend stamps on every object it creates bounds a leak; unrenewed it also bounded the RUN, so a session outliving `claimTtlSeconds` (default one hour) had its pod deleted mid-command. The handle now merge-PATCHes that expiry a full TTL forward every half TTL, jittered ±10%, and `destroy()` stops it. **This adds an RBAC requirement**: the ServiceAccount needs `patch` on `sandboxclaims` and `sandboxes` in the sandbox namespace, alongside the verbs it already needed. A renewal that fails is reported to the new optional `KubernetesBackendConfig.onLeaseRenewalError` and retried on the next tick; each PATCH is bounded on its own clock (a quarter of the interval, capped at 30 seconds), so an API server that accepts a renewal and never answers it is abandoned and retried rather than parking the loop and letting the lease expire in silence; and one that finds the object already deleted stops the loop and marks the handle gone, so later calls throw `KubernetesSandboxGoneError` instead of dialing a pod that no longer exists. A handle you drop without calling `destroy()` keeps renewing for as long as the process lives, so `destroy()` is now load-bearing for cleanup inside a long-lived host.
|
|
146
|
+
|
|
147
|
+
New on the public surface: `KubernetesPrivilegeProbeError` (with `reason`, `PrivilegeProbeFailure` and `ProcStatusPrivileges`), `KubernetesSandboxDestroyedError`, `KubernetesSandboxGoneError`, `KubernetesAgentUnauthorizedError`, `AgentPreauthFrameTooLargeError`, `TCP_PREAUTH_FRAME_LIMIT_BYTES`, and `KubernetesBackendConfig.onLeaseRenewalError`. Removed: `KubernetesAgentTransportPendingError`, which was never exported from the package entry point and could only ever be thrown by a method that now works.
|
|
148
|
+
|
|
149
|
+
One limit worth knowing before you write a large file: every request to the guest dials a fresh connection, so every request is that connection's first — not-yet-authenticated — frame and is bounded by the guest's pre-auth ceiling (8 MiB by default) on every call, not once. A `writeFile` whose base64 body would exceed it throws `AgentPreauthFrameTooLargeError` before dialing, naming the limit; in practice bodies above about 5.9 MiB raw do not fit. Chunking is not implemented — raise `NAMZU_AGENT_MAX_PREAUTH_FRAME_BYTES` in the deployment, or split the write.
|
|
150
|
+
|
|
151
|
+
Every other backend is untouched.
|
|
152
|
+
|
|
153
|
+
- 53c526c: `SandboxAgentHandle` (re-exported from the package's public entry point)
|
|
154
|
+
gains a fourth arm: `{ kind: 'tcp', host, port, token }`. `VsockAgentTransport`
|
|
155
|
+
(also public) gains a new `executeStreamed()` method, and its
|
|
156
|
+
`VsockTransportOptions` gain an optional `onDial` callback. All three
|
|
157
|
+
changes are additive and backward compatible — existing `unix`/`vsock`/`mtls`
|
|
158
|
+
handles, existing `VsockAgentTransport` callers, and the guest protocol are
|
|
159
|
+
untouched (`firecracker/__tests__/transport.test.ts` passes unmodified) — but
|
|
160
|
+
they are genuinely new surface in the compiled `.d.ts`, not yet constructible
|
|
161
|
+
by anything outside `packages/sandbox/src/backends/` until a later workstream
|
|
162
|
+
wires the kubernetes backend up to them.
|
|
163
|
+
|
|
164
|
+
Alongside this, a new (package-internal, not yet exported) `KubernetesAgentTransport`
|
|
165
|
+
in `src/backends/kubernetes/transport.ts` dials the guest agent (`agent/agent.cjs`)
|
|
166
|
+
directly over a routed pod network for the upcoming kubernetes backend.
|
|
167
|
+
|
|
168
|
+
The `tcp` dialer is a plain `net.connect({ host, port })` per call — no
|
|
169
|
+
routing preamble, no ack, no cached socket or IP — so a `host` that is a
|
|
170
|
+
Kubernetes Service FQDN is re-resolved on every request and a resumed
|
|
171
|
+
pod's new address costs nothing extra. The handle's optional `token`
|
|
172
|
+
rides in each request envelope (the credential field the guest agent
|
|
173
|
+
already accepts); a wrong token surfaces as a named
|
|
174
|
+
`KubernetesAgentUnauthorizedError` rather than a generic protocol error.
|
|
175
|
+
|
|
176
|
+
Because every `tcp` request dials a fresh connection, that connection's
|
|
177
|
+
first frame is also the one the guest agent has not authenticated yet,
|
|
178
|
+
so it is bound by the agent's pre-auth frame ceiling (8 MiB by default)
|
|
179
|
+
on every call, not just on first use. This transport now checks an
|
|
180
|
+
outgoing envelope's size against that ceiling BEFORE dialing and throws
|
|
181
|
+
a named `AgentPreauthFrameTooLargeError` naming the limit, instead of
|
|
182
|
+
opening a connection the agent would refuse anyway. Chunking a large
|
|
183
|
+
`write-file` body across multiple frames is a documented follow-up, not
|
|
184
|
+
implemented here.
|
|
185
|
+
|
|
186
|
+
`KubernetesAgentTransport` also accepts an optional `onTiming` callback
|
|
187
|
+
reporting a completed `exec()` call's dial/reserve/execute/drain
|
|
188
|
+
durations (never the token, command, or output), so the kubernetes
|
|
189
|
+
backend's sub-second warm-acquire target can be measured rather than
|
|
190
|
+
assumed.
|
|
191
|
+
|
|
192
|
+
- 50de52b: The Kubernetes backend can now keep a workspace: a sandbox with a block-mode disk that survives being suspended.
|
|
193
|
+
|
|
194
|
+
**New verb, not a new provider.** `createKubernetesWorkspace(config, options)` returns a `KubernetesWorkspace` — the SDK's `Sandbox`, plus `suspend()`, `resume()`, a `suspended` flag, and a `destroy()` that takes `deleteDisk`. It is separate from `createSandboxProvider` because a `SandboxProvider` promises an ephemeral sandbox per run and this promises the opposite; `warmPoolName` is ignored, since a workspace is always a `Sandbox` POSTed directly. `@namzu/sdk`'s `Sandbox` is untouched: no new `SandboxStatus` member, no `suspend?()`/`resume?()` on the shared contract, no `deleteDisk` on the shared `SandboxDestroyOptions`.
|
|
195
|
+
|
|
196
|
+
**`destroy()` keeps the disk.** The API has `operatingMode` and it has DELETE, and nothing in between, so there is no delete-compute-keep-disk verb to offer. `destroy()` and `destroy({ deleteDisk: false })` SUSPEND and leave the object standing; only `destroy({ deleteDisk: true })` DELETEs the Sandbox and cascades to its Pod, Service and PVC. The default is the non-destructive one because `destroy()` is what a `finally` block calls. No failure path ever deletes: a create or resume that fails after the object exists suspends it and rethrows — including a create that POSTed the object itself, because two processes can be coming up on one name at once and the one that got the `201` would otherwise delete the disk the other just adopted. The named cost: a failed create can leave one suspended `Sandbox` and its PVC standing, which nothing reaps and which the caller finds again under the same name.
|
|
197
|
+
|
|
198
|
+
**A workspace carries no lease.** Unlike a task sandbox it gets no `shutdownTime`, no `shutdownPolicy: Delete` and no renewal loop. An expiry on a workspace is a timer that deletes your files, and a renewal loop makes keeping them conditional on a host process staying up. The trade is explicit: **nothing reaps a workspace you abandon** — the PVC stands until someone calls `destroy({ deleteDisk: true })` or deletes the Sandbox by hand.
|
|
199
|
+
|
|
200
|
+
**The disk is fixed at creation and must be `volumeMode: Block` — every entry of it.** `Sandbox.spec.volumeClaimTemplates` is CEL-immutable and a `SandboxClaim` carrying one is forced to cold-start, so "claim a warm diskless sandbox and attach a disk later" is not expressible in this API; resizing is out of scope for the same reason. The `SandboxTemplate` a workspace is built from must declare at least one `volumeClaimTemplates` entry, every entry must be `Block`, and every entry must be claimed through a container's `volumeDevices` rather than `volumeMounts`. Anything else throws the new `KubernetesWorkspaceDiskError` before anything is created — each refused shape otherwise WORKS: no disk gives you a sandbox whose files vanish on the first suspend, and a `Filesystem` PVC under a VM-isolating RuntimeClass reaches the guest over a filesystem passthrough that pays a round trip per file operation, so a dependency-tree walk is several times slower and nothing fails.
|
|
201
|
+
|
|
202
|
+
**`config.egress` applies to a workspace too.** `createKubernetesWorkspace` runs the same two steps a provider `create()` runs: a `static`/`resolver` hostname allowlist with no FQDN-capable `engine` is refused synchronously, before any request, and the `NetworkPolicy` an operator applied is fetched and matched against the translation before anything is created. It is checked against the template the workspace is built from (`options.sandboxTemplateName`, falling back to `config.sandboxTemplateName`), because that name is the pod label the policy's `podSelector` matches — a deployment with a separate workspace template needs a policy object for it (`<workspace template>-egress` by default), and the task template's does not cover a workspace pod. Unlike the provider's once-per-backend check, this one runs on every call.
|
|
203
|
+
|
|
204
|
+
**A suspend waits for the pod, and only the pod.** `suspend()` resolves once the pod is gone or in a terminal phase — not on the Sandbox's `Suspended` condition, which upstream documents as lingering True after a resume, and not on a `deletionTimestamp`, which is set while the guest is still running and still writing to the disk. A pod that outlives `readyTimeoutMs` rejects with the new `KubernetesWorkspaceSuspendTimeoutError` rather than resolving early. A call already in flight when `suspend()` starts is not cancelled: it fails at the transport, not with the suspended error.
|
|
205
|
+
|
|
206
|
+
**A state is recorded when the cluster confirms it, never before.** Both verbs are idempotent by early-returning on a recorded state, so the moment that record is written decides what happens to a failure. `suspended` is written after the patch lands AND the pod is observed stopped; `deleted` after the DELETE resolves, or reports the object already gone. A request that fails leaves the state it found and rethrows, so you can retry: a refused suspend patch leaves the workspace running and still serving calls, a suspend whose pod outlived the wait admits no call but is not recorded as finished, and a failed DELETE does not answer your retry "already deleted" while the `Sandbox`, its pod and its PVC stand on the cluster with nothing left that would remove them. Concurrency is covered the other way round, with a single flight per verb: a second `suspend()` or `destroy()` arriving mid-transition awaits the one in progress instead of sending a second request into the window the deferred record opens, and `destroy()` with no options shares the suspend's flight because it is a suspend — the shared request running under the FIRST caller's `signal`, since that is what sharing one request means. `destroy()` is idempotent across the two shapes as well as within each: a plain `destroy()` on a workspace already removed by `destroy({ deleteDisk: true })` is a no-op in either admission order, because `destroy()` is what a `finally` block calls and what it asks for has happened; an explicit `suspend()` on a deleted workspace still throws.
|
|
207
|
+
|
|
208
|
+
**Nothing but `deleteDisk: true` deletes — including the path you never call.** When an execution's cancellation cannot be confirmed (a wedged agent, a partitioned pod, the `cancel-execution` window closing with no answer), the shared execution controller retires the pod that command was left in. On a task sandbox retiring means DELETEing the object, correctly — it is disposable and its disk is scratch. A workspace is retired by the same `operatingMode: Suspended` patch `suspend()` sends: the `exec()` still rejects, carrying `retirement: { accepted: true }` once that patch lands (`accepted: false`, with the error, when it does not), the workspace then reads `suspended: true` and admits nothing, and `resume()` brings up a fresh pod on the same disk. A `destroy({ deleteDisk: true })` whose DELETE fails leaves the same shape — session torn down, nothing admitted, `suspended: true` — because neither the delete nor a suspend reached the cluster, so both `resume()` and a retried delete are open to you.
|
|
209
|
+
|
|
210
|
+
**A resume rebuilds the address and the token.** A resumed pod keeps the sandbox's name and gets a new uid and a new IP, so `resume()` re-resolves the address, re-reads the bind token and rebuilds the transport, skipping any pod carrying a `deletionTimestamp` or in a terminal phase — while the outgoing pod terminates, a `GET` by name can still answer with it and a selector list can return it beside the new one. `Ready` is not a transition signal either — the controller leaves it standing across a resume the way it leaves `Suspended` standing — so the uid is POLLED under `readyTimeoutMs` until a live pod with a uid different from the one the last landed suspend patch retired is found, rather than read once from a status that has not caught up. That covers a resume issued straight after a suspend whose pod outlived its own wait, which arrives mid-drain with no live pod of that name to read at all. Every patch that lands is recorded the same way — an explicit `suspend()`, the retirement above, and the cleanup after a failed create or resume, which swallows its own failure and therefore records only when the request actually came back; a pod nobody asked the controller to remove has no replacement to wait for, and excluding it would time out a resume whose workspace was perfectly usable. What visibly moves depends on the Service: the token is always new, the pod IP always changes, and a `status.serviceFQDN` address does not, because the Service outlives the pod. The acquire-time privilege probe runs again on every resume. Between a suspend and a resume every call throws the new `KubernetesWorkspaceSuspendedError` and issues no dial, because the Service outlives the pod and a dial would hang on a connect timeout that names nothing. `status` reports `destroyed` while suspended — `SandboxStatus` has no suspended member — and `suspended` is what tells the recoverable state from the final one.
|
|
211
|
+
|
|
212
|
+
**Calling it twice reattaches — and what is adopted is checked.** The Sandbox is named `namzu-ws-<workspaceId>`, so a second process finds the same workspace; a create that collides adopts the existing object and resumes it if it was asleep. Because an adopt is handed an object this call did not build, the object is checked against the configuration before it is woken: it must carry a block disk, its pod template must carry `sandbox.namzu.ai/template` for the template this call builds from, and — when `runtimeClassName` is configured — it must already run under that class. A disagreement throws the new `KubernetesWorkspaceMismatchError` and nothing is patched, woken or dialed. The label check runs whether or not `config.egress` is set, since that label is what a policy's `podSelector` matches and adopting an object built from another template would hand back a pod the verified policy does not select; the RuntimeClass check is there because the privilege probe cannot see a missing VM boundary (`/proc/self/status` reads the same under Kata and under runc). The fix is to point the workspace at the template it was built from, or to delete the Sandbox — which takes its disk with it — and create it again.
|
|
213
|
+
|
|
214
|
+
A `workspaceId` that is not already a legal DNS-1123 label is refused rather than sanitised, because two ids that sanitise to one name would silently share one disk. It is a name and not a lock: two host processes can adopt one running workspace, and either one's `destroy()` suspends the pod the other is executing in.
|
|
215
|
+
|
|
216
|
+
**Fixed on the task path, in the same change:** a pool-less `create()` copied the `SandboxTemplate`'s `podTemplate` and dropped its `volumeClaimTemplates`, so a task template declaring a disk produced a healthy Sandbox with no disk and a container naming a volume that did not exist. Both paths now copy the template's `volumeClaimTemplates` verbatim. If you have been working around that by declaring no disk on a task template, nothing changes; if you declared one and wondered where it went, it now arrives.
|
|
217
|
+
|
|
218
|
+
New on the public surface: `createKubernetesWorkspace`, `KubernetesWorkspace`, `KubernetesWorkspaceOptions`, `KubernetesWorkspaceDestroyOptions`, `KubernetesWorkspaceTransitionOptions`, `KubernetesWorkspaceDiskError`, `KubernetesWorkspaceMismatchError`, `KubernetesWorkspaceSuspendTimeoutError`, `KubernetesWorkspaceSuspendedError`. RBAC is unchanged — a workspace uses verbs the task path already needed. Every other backend is untouched.
|
|
219
|
+
|
|
220
|
+
- a70c936: A `Sandbox` contract conformance suite: `defineSandboxConformance` (`packages/sandbox/src/testing/sandbox-conformance.ts`) asserts `exec`'s exit codes and streamed output, the `AbortSignal` contract (the process is genuinely terminated, never a resolved result that reads as an unaborted success), a `writeFile`/`readFile` round trip including binary content, `listFiles`, `openTerminal` ownership on `destroy()`, `openTcpConnection` to guest loopback and its refusal of a non-loopback host, destroy idempotence, and every call failing once destroyed. It takes its `describe`/`it`/`expect` and a factory producing a fresh `Sandbox` as arguments, the same shape `@namzu/sdk/testing`'s checkpoint-store and provider-driver suites already use, so `@namzu/sandbox` gains no test dependency from shipping it and a caller can run it against a recording harness.
|
|
221
|
+
|
|
222
|
+
**New exported surface, within the package only.** `@namzu/sandbox` has no `testing` subpath in its published `exports` map, and this change does not add one — that is a deliberate, separate decision. A caller inside this monorepo imports `defineSandboxConformance` by relative path (`src/testing/sandbox-conformance.js`), exactly as the two new test files below do. It is minor rather than patch because it is new, intentionally-public TypeScript surface a consumer with access to the package's source can import and depend on, even though nothing in `@namzu/sandbox`'s npm entry point changes shape.
|
|
223
|
+
|
|
224
|
+
**Proven against two backends, not one.** `packages/sandbox/src/backends/kubernetes/__tests__/conformance.test.ts` and `packages/sandbox/src/backends/firecracker/__tests__/conformance.test.ts` both run the identical suite — the kubernetes backend over a real `agent/agent.cjs` on a loopback TCP socket, Firecracker over the same agent on its existing unix-domain-socket fixture — which is what makes it a contract suite rather than one backend's tests wearing a new name. `packages/sandbox/src/testing/__tests__/conformance-fails-a-broken-sandbox.test.ts` is the suite's own negative test: three deliberately broken `Sandbox`s (resolves `exec` after abort, `destroy()` leaves a terminal running, `readFile` returns corrupted bytes) each fail it by name.
|
|
225
|
+
|
|
226
|
+
Nothing existing changes shape — every other export, every shipped backend's behavior, is untouched.
|
|
227
|
+
|
|
228
|
+
### Patch Changes
|
|
229
|
+
|
|
230
|
+
- 4b0e7ad: `defineSandboxConformance`'s `openTcpConnection` positive case now starts its echo listener INSIDE the guest, through `openTerminal`, instead of on the orchestrator/test process's own loopback. The old fixture only ever proved anything for a backend whose "guest" happened to share that loopback with the test process (a Firecracker unit test over a local socket, a fake-agent-in-process kubernetes test) — it could never pass against a real remote sandbox, which cannot dial the orchestrator's loopback at all. Confirmed in-cluster: this case now passes against a live kubernetes backend acquisition on a real kind cluster, where it previously failed with `connect ECONNREFUSED`.
|
|
231
|
+
|
|
232
|
+
Two new optional fields on `SandboxConformanceOptions` — `guestCanRunNode` and `guestListenerCommand` — let a backend whose guest cannot run a listener this way skip the case with a stated reason (its own title) rather than fail spuriously; both default to the existing behavior (node is assumed available, since every shipped backend's guest agent already runs on node), so no existing caller of `defineSandboxConformance` needs to change anything.
|
|
233
|
+
|
|
234
|
+
Patch, not minor: this module has no `testing` subpath in `@namzu/sandbox`'s own `exports` map (see the file's own doc comment) — a caller reaches it only by relative path within the monorepo, as `backends/kubernetes/__tests__/conformance.test.ts` and `backends/firecracker/__tests__/conformance.test.ts` already do — so this is not yet public surface, and the added options are additive and optional regardless.
|
|
235
|
+
|
|
236
|
+
Also corrects a self-contradictory doc comment on `Sandbox.openTerminal` (`@namzu/sdk`): it told an implementer to both "throw" and "omit" for a guest that cannot provide one. It now says only "omit", matching `Sandbox.openTcpConnection`'s own wording and this suite's documented skip-if-unavailable convention. Comment-only; no type or behavior changed.
|
|
237
|
+
|
|
238
|
+
- 13d01db: Adds an internal Kubernetes API client (`backends/kubernetes/k8s-client.ts`, not yet exported from the package entrypoint) for an in-progress Kubernetes/Kata sandbox backend. It speaks the API server with bare `fetch`, falling back to `node:https` only when a custom cluster CA is supplied, and bootstraps in-cluster credentials straight from the projected ServiceAccount volume — the same zero-dependency pattern the ACI and Firecracker backends already use. `@namzu/sandbox` still declares no `dependencies` key.
|
|
239
|
+
- 77adb50: Fix both the kubernetes/Firecracker guest agent (`agent/agent.cjs`) and the
|
|
240
|
+
container-tier HTTP worker (`worker/server.js`) reporting a cancelled
|
|
241
|
+
`exec()` as a clean, unaborted-looking success when the target process
|
|
242
|
+
ignores `SIGTERM` but happens to finish on its own before the cancel grace
|
|
243
|
+
window elapses. Both peers' `terminateAndConfirm` only escalated to
|
|
244
|
+
`SIGKILL` if the owned process group was still alive at the end of that
|
|
245
|
+
window, with nothing checking that the exit was actually caused by the
|
|
246
|
+
signal — so an ignoring process whose natural runtime was under the grace
|
|
247
|
+
period ran to completion untouched, in violation of
|
|
248
|
+
`SandboxExecOptions.signal`'s contract ("must terminate the owned process
|
|
249
|
+
... never silently ignore the signal and let the command run to
|
|
250
|
+
completion"). This is the same mechanism on both transports, found on the
|
|
251
|
+
guest agent first (issue #469's kind conformance run) and confirmed to
|
|
252
|
+
exist verbatim in the worker once looked for.
|
|
253
|
+
|
|
254
|
+
The default (previously `2000`ms on both) is now `250`ms for
|
|
255
|
+
`NAMZU_AGENT_CANCEL_GRACE_MS` (agent) and `NAMZU_SANDBOX_CANCEL_GRACE_MS`
|
|
256
|
+
(worker) — comfortably under the shared conformance suite's adversarial
|
|
257
|
+
fixture (a command that finishes on its own in ~400ms) while still enough
|
|
258
|
+
for a fast, well-behaved SIGTERM handler's cleanup. A deployment that
|
|
259
|
+
genuinely needs a longer window for cooperative shutdown sets either
|
|
260
|
+
variable explicitly; both were already, and remain, overridable. A new
|
|
261
|
+
regression test on each transport deliberately leaves its own grace
|
|
262
|
+
variable unset — the one thing every other suite on that transport
|
|
263
|
+
overrides — and fails against the old default, passes against the new one.
|
|
264
|
+
|
|
265
|
+
**Defect 2, found alongside the agent fix, is now MITIGATED for the
|
|
266
|
+
Kubernetes backend's shipped image, not merely documented:** in a real pod
|
|
267
|
+
the agent used to run as the container's PID 1 with no subreaper
|
|
268
|
+
(`packages/sandbox/k8s/entrypoint.sh` `exec`ed straight into it), so a
|
|
269
|
+
background job forked by a cancelled `sh -c` command (`... &`) reparented
|
|
270
|
+
to the agent on `SIGKILL` and was never reaped — Node's `child_process`
|
|
271
|
+
only `waitpid()`s the children it spawned itself — running that
|
|
272
|
+
cancellation out the full `RemoteExecutionController` cancel-confirm window
|
|
273
|
+
(8s by default) and tearing the sandbox down instead of confirming.
|
|
274
|
+
`k8s/entrypoint.sh` now execs into `tini` (installed in `k8s/Dockerfile`)
|
|
275
|
+
as the container's real PID 1 and subreaper, with the guest agent as its
|
|
276
|
+
child; `tini` reaps the orphan and forwards `SIGTERM` to the agent exactly
|
|
277
|
+
as before. **A host building its own image from `agent.cjs` directly rather
|
|
278
|
+
than from `k8s/Dockerfile` must still provide its own subreaper as PID 1**
|
|
279
|
+
— this fix lives in the shipped image, not in the agent itself, since
|
|
280
|
+
`agent.cjs` cannot know what pid it is. Root-caused with live evidence in
|
|
281
|
+
`research/k8s-sandbox/kind-e2e-results.md` ("Defect 2", `2026-09-16`) and
|
|
282
|
+
re-verified in-cluster against the new image in
|
|
283
|
+
`research/k8s-sandbox/abort-case-recheck-results.json`.
|
|
284
|
+
|
|
285
|
+
**Patch, not minor or major:** `agent/agent.cjs` and `worker/server.js` are
|
|
286
|
+
guest/worker files that run INSIDE a sandbox or container, never imported
|
|
287
|
+
by a consumer of the published `@namzu/sandbox` tarball (`npm pack
|
|
288
|
+
--dry-run` packs only `dist` and `src`); the k8s image's `Dockerfile` and
|
|
289
|
+
`entrypoint.sh` are likewise deployment artifacts, not the package's own
|
|
290
|
+
`exports`. Both defaults that changed are guest-/worker-internal timings
|
|
291
|
+
with no public type or exported symbol affected, and both changes narrow
|
|
292
|
+
when a cancellation escalates to `SIGKILL` and add a real subreaper —
|
|
293
|
+
strictly tightening what `SandboxExecOptions.signal`'s contract already
|
|
294
|
+
promised, never loosening it, so no caller-visible behavior a consumer
|
|
295
|
+
could have depended on gets worse.
|
|
296
|
+
|
|
297
|
+
- Updated dependencies [68e535b]
|
|
298
|
+
- Updated dependencies [a9e4b19]
|
|
299
|
+
- Updated dependencies [a54dc71]
|
|
300
|
+
- Updated dependencies [86a3818]
|
|
301
|
+
- Updated dependencies [03630cd]
|
|
302
|
+
- Updated dependencies [f33c62b]
|
|
303
|
+
- Updated dependencies [a8df193]
|
|
304
|
+
- Updated dependencies [8bfe291]
|
|
305
|
+
- Updated dependencies [6ae4072]
|
|
306
|
+
- Updated dependencies [dd8702d]
|
|
307
|
+
- Updated dependencies [92ab1d9]
|
|
308
|
+
- Updated dependencies [e6d6d1e]
|
|
309
|
+
- Updated dependencies [7ca8c7d]
|
|
310
|
+
- @namzu/sdk@40.0.0
|
|
311
|
+
|
|
3
312
|
## 13.0.0
|
|
4
313
|
|
|
5
314
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -84,6 +84,24 @@ Concurrent destroy and automatic-retirement calls share one checked teardown;
|
|
|
84
84
|
Docker removal is never reported as accepted after a non-zero or aborted
|
|
85
85
|
`docker rm -f`.
|
|
86
86
|
|
|
87
|
+
**The SIGTERM → SIGKILL grace window.** Both peers implement the same
|
|
88
|
+
mechanism, deliberately kept textually parallel so a future reader sees they
|
|
89
|
+
are one design, not two: `SIGTERM` the owned process group, wait for it to go
|
|
90
|
+
quiet, and escalate to `SIGKILL` only if it is still alive at the end of a
|
|
91
|
+
grace window — with nothing checking that a quiet group went quiet BECAUSE of
|
|
92
|
+
the signal rather than by finishing on its own. A command that ignores
|
|
93
|
+
`SIGTERM` but happens to complete within the window therefore runs to
|
|
94
|
+
completion untouched and reports back as a clean, unaborted-looking success.
|
|
95
|
+
The container worker's `NAMZU_SANDBOX_CANCEL_GRACE_MS` and the Firecracker/
|
|
96
|
+
kubernetes guest agent's `NAMZU_AGENT_CANCEL_GRACE_MS` both default to `250`
|
|
97
|
+
(previously `2000` on both — issue #469's kind conformance run caught this on
|
|
98
|
+
the agent transport first; the identical worker-side race was fixed in the
|
|
99
|
+
same change once found). 250ms is comfortably under the shared conformance
|
|
100
|
+
suite's adversarial fixture (a command that finishes on its own in ~400ms)
|
|
101
|
+
while still enough for a fast, well-behaved `SIGTERM` handler's own cleanup;
|
|
102
|
+
a deployment that genuinely needs a longer cooperative-shutdown window sets
|
|
103
|
+
either variable explicitly.
|
|
104
|
+
|
|
87
105
|
Every worker and microVM guest publishes its wire-protocol version in the
|
|
88
106
|
readiness response. The host admits only the exact version implemented by its
|
|
89
107
|
release; missing, older, and newer versions fail before a sandbox handle or
|
|
@@ -101,6 +119,139 @@ previous host and golden-image pair available together for rollback. Rolling
|
|
|
101
119
|
back only one side is intentionally rejected at readiness, so a mismatched
|
|
102
120
|
guest never accepts work under an unverified wire contract.
|
|
103
121
|
|
|
122
|
+
## Guest agent transports and the per-instance token
|
|
123
|
+
|
|
124
|
+
The microVM guest agent picks its listen socket from the environment, in a
|
|
125
|
+
fixed order: `NAMZU_AGENT_UNIX_PATH` for a unix-domain socket, then an
|
|
126
|
+
inherited descriptor for the vsock bridge named by `NAMZU_AGENT_VSOCK_PORT`,
|
|
127
|
+
then `NAMZU_AGENT_TCP_PORT` for a TCP listener on `0.0.0.0`. The third mode is
|
|
128
|
+
for a deployment that reaches the guest over a routed network — one sandbox per
|
|
129
|
+
pod on a container orchestrator — instead of over a host-local socket. Framing,
|
|
130
|
+
ops, execution leases, terminals, loopback TCP and file IO are identical on all
|
|
131
|
+
three; only the listen address differs. A guest whose environment configures
|
|
132
|
+
none of them still refuses to start, naming all three. `NAMZU_AGENT_TCP_PORT=0`
|
|
133
|
+
binds an ephemeral port, which is what the suites use; a deployment names a
|
|
134
|
+
fixed port, because nothing in front of the guest can be configured to reach a
|
|
135
|
+
port that is only chosen at startup.
|
|
136
|
+
|
|
137
|
+
A routed listener is reachable by whatever the network admits, so that
|
|
138
|
+
deployment also gives the agent a per-instance credential. With
|
|
139
|
+
`NAMZU_AGENT_BIND_TOKEN` set, every op except `healthz` must present exactly
|
|
140
|
+
that token in its request envelope, from the first frame of the connection; a
|
|
141
|
+
missing, empty or different token is answered `unauthorized` and the connection
|
|
142
|
+
is closed before any handler runs. The token is compared in constant time
|
|
143
|
+
against a fixed-width digest, so neither its value nor its length is learnable
|
|
144
|
+
by probing. `NAMZU_AGENT_REQUIRE_TOKEN` is the fallback for a deployment that
|
|
145
|
+
cannot inject a token: the agent binds to the first token it is shown and
|
|
146
|
+
refuses every other one for the life of the process. With neither variable set
|
|
147
|
+
the agent authenticates nothing, which is what the host-local vsock and unix
|
|
148
|
+
transports have always done and what they keep doing. `healthz` never requires
|
|
149
|
+
a token and never echoes one, so readiness probing needs no secret and leaks
|
|
150
|
+
nothing beyond liveness and the protocol version.
|
|
151
|
+
|
|
152
|
+
The TCP mode fails closed. `NAMZU_AGENT_TCP_PORT` set with neither
|
|
153
|
+
`NAMZU_AGENT_BIND_TOKEN` nor `NAMZU_AGENT_REQUIRE_TOKEN` is refused at startup,
|
|
154
|
+
naming both, rather than binding an unauthenticated listener on `0.0.0.0`; the
|
|
155
|
+
unix and inherited-descriptor modes keep requiring no token, because their
|
|
156
|
+
control channel is host↔guest only. `NAMZU_AGENT_BIND_TOKEN` set to the empty
|
|
157
|
+
string is refused at startup in **every** mode: that is the shape a
|
|
158
|
+
downward-API injection takes when it resolved to nothing, and honouring it
|
|
159
|
+
would open precisely the hole the variable was set to close.
|
|
160
|
+
|
|
161
|
+
Because the credential rides inside the request envelope, the gate cannot run
|
|
162
|
+
until a whole frame has been parsed — so what an unauthenticated peer may spend
|
|
163
|
+
in that window is bounded rather than trusted, and in the token modes only:
|
|
164
|
+
|
|
165
|
+
| Variable | Default | What it bounds |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `NAMZU_AGENT_MAX_FRAME_BYTES` | 256 MiB | The largest length any frame header may announce, on every listen mode. The 8-hex prefix otherwise permits 4 GiB, which the reader used to honour. Sized for the largest frame the host legitimately writes: a `write-file` carries the whole base64 body in one envelope. |
|
|
168
|
+
| `NAMZU_AGENT_MAX_PREAUTH_FRAME_BYTES` | 8 MiB | The same ceiling for a connection that has not yet presented the token, clamped to the one above. Token modes only. It is also the `write-file` ceiling on a token path — see below. |
|
|
169
|
+
| `NAMZU_AGENT_MAX_PREAUTH_CONNECTIONS` | 64 | How many connections may be unauthenticated at once. Token modes only. Every bound above is per connection, so without this one they could be paid again on the next connection. A full pool evicts its **oldest** unauthenticated member and serves the arrival — see below for why that direction. |
|
|
170
|
+
| `NAMZU_AGENT_MAX_PREAUTH_BUFFER_BYTES` | 32 MiB | What all unauthenticated connections may buffer **between them**, never less than one pre-auth frame. Token modes only. The count above bounds sockets; this bounds the heap behind them, and the heap is what runs out first. |
|
|
171
|
+
| `NAMZU_AGENT_PREAUTH_IDLE_TIMEOUT_MS` | 10000 | How long a connection may stay unauthenticated while **quiet**. Every byte received resets it, so it retires the connection that says nothing, not the one that says too little. Token modes only, cleared the moment a connection authenticates. |
|
|
172
|
+
| `NAMZU_AGENT_PREAUTH_DEADLINE_MS` | 10000 | How long a connection may stay unauthenticated **at all**, measured from accept and reset by nothing. Token modes only, cleared the moment a connection authenticates, so no long-lived terminal, `tcp-connect` or streaming `execute` is ever measured against it. |
|
|
173
|
+
| `NAMZU_AGENT_REFUSAL_FLUSH_GRACE_MS` | 1000 | How long a refusal frame may take to reach the wire before the socket is destroyed anyway. Every listen mode. A backstop against a peer that has stopped reading, not a budget anything normally spends. |
|
|
174
|
+
|
|
175
|
+
So the most an unauthenticated peer can make the agent hold is
|
|
176
|
+
`NAMZU_AGENT_MAX_PREAUTH_BUFFER_BYTES`, spread over at most
|
|
177
|
+
`NAMZU_AGENT_MAX_PREAUTH_CONNECTIONS` sockets, and both are tunable against the
|
|
178
|
+
pod's memory limit. Resident memory settles somewhat above that while the
|
|
179
|
+
allocator catches up; what it does not do is keep climbing. Note what a shared
|
|
180
|
+
budget means when it is exhausted: the connection refused is whichever one asks
|
|
181
|
+
next, which may be a legitimate caller rather than the peer holding the budget.
|
|
182
|
+
That is the trade a global bound makes — a refused request is recoverable, an
|
|
183
|
+
exhausted pod is not.
|
|
184
|
+
|
|
185
|
+
A header above either ceiling is answered `frame_too_large`, naming the
|
|
186
|
+
announced length, the limit, and the variable that governs it, because a caller
|
|
187
|
+
told only a number cannot tell which of the two ceilings it hit. A fourth bound
|
|
188
|
+
needs no variable: a frame header is exactly nine bytes, eight hex digits and a
|
|
189
|
+
newline, so a peer streaming bytes that contain no newline at all is refused on
|
|
190
|
+
the ninth of them rather than buffered against a newline that is never coming.
|
|
191
|
+
A refused connection — for any of these, or for `unauthorized` — is
|
|
192
|
+
**destroyed**, not `end()`ed: ending a socket closes only its writable half, so
|
|
193
|
+
a refused peer used to be able to keep streaming into the agent's frame buffer
|
|
194
|
+
for as long as it liked.
|
|
195
|
+
|
|
196
|
+
Two of those bounds exist because the others do not answer a slow loris, and in
|
|
197
|
+
one earlier shape made each other worse. An idle timeout is reset by every
|
|
198
|
+
byte, so a peer that trickles one byte every few seconds stays unauthenticated
|
|
199
|
+
for as long as it cares to; `NAMZU_AGENT_PREAUTH_DEADLINE_MS` is what bounds
|
|
200
|
+
it, because it runs from accept and nothing resets it. And a full pool that
|
|
201
|
+
refused the **newest** connection handed exactly those peers the power to
|
|
202
|
+
decide who else got served: enough of them locked out every later caller,
|
|
203
|
+
including the credential-exempt `healthz` probe that a readiness check cannot
|
|
204
|
+
do without. So a full pool evicts its oldest unauthenticated member instead,
|
|
205
|
+
answers it `too_many_unauthenticated_connections`, and serves the arrival — the
|
|
206
|
+
oldest unauthenticated connection being, by construction, the one that has had
|
|
207
|
+
the longest to present a token and has not.
|
|
208
|
+
|
|
209
|
+
What none of this does is stop a peer that can reach the port from causing
|
|
210
|
+
churn. It can still open connections, hold slots until the deadline, and make
|
|
211
|
+
the agent evict and re-accept; what it cannot do is hold a slot indefinitely or
|
|
212
|
+
starve a probe. That residual is deliberate, and it is the division of labour
|
|
213
|
+
this design rests on: the NetworkPolicy ingress rule in front of the agent port
|
|
214
|
+
is the boundary that decides who may reach it at all, and the token and these
|
|
215
|
+
bounds are defence in depth behind it, for a peer already inside that rule.
|
|
216
|
+
|
|
217
|
+
One consequence is worth naming, because it is the price of putting the
|
|
218
|
+
credential in the envelope rather than in a handshake: a `write-file` body
|
|
219
|
+
travels in the same first frame as the token, so in a token mode the pre-auth
|
|
220
|
+
cap **is** the ceiling on that body — about 6 MiB of file content at the
|
|
221
|
+
default, since the body travels base64-encoded. A deployment that writes larger
|
|
222
|
+
files raises `NAMZU_AGENT_MAX_PREAUTH_FRAME_BYTES` to suit, trading pre-auth
|
|
223
|
+
buffer budget for body size. Nothing on the Firecracker path is affected: no
|
|
224
|
+
token mode is active there, so no pre-auth cap applies and a `write-file` frame
|
|
225
|
+
of any size up to `NAMZU_AGENT_MAX_FRAME_BYTES` is accepted exactly as before.
|
|
226
|
+
|
|
227
|
+
None of this is a wire change. `token` is an optional envelope field, so the
|
|
228
|
+
guest protocol version is deliberately unchanged and no host and no golden
|
|
229
|
+
image has to roll together with this release.
|
|
230
|
+
|
|
231
|
+
What the token is not: a boundary against the sandbox's own workload. Once the
|
|
232
|
+
image entrypoint deprivileges, the agent and the workload share a uid, so a
|
|
233
|
+
workload process can read the agent's own `/proc/<pid>/environ`. It is
|
|
234
|
+
per-instance for exactly that reason — a workload that steals its own
|
|
235
|
+
instance's token gains nothing it does not already have inside that instance,
|
|
236
|
+
and there is no shared pool secret whose theft would reach the other instances.
|
|
237
|
+
The boundary that keeps other tenants out is the network rule in front of the
|
|
238
|
+
agent port; the token is defence in depth behind it. What the guest does
|
|
239
|
+
guarantee is narrower and exact: every process it starts to serve a request —
|
|
240
|
+
an `execute` command, a `terminal` shell, the resize helper behind that
|
|
241
|
+
terminal — is handed an environment stripped of every `NAMZU_AGENT_*` and
|
|
242
|
+
`NAMZU_SANDBOX_*` variable, so the token and the agent's own configuration
|
|
243
|
+
never enter the workload's environment through the environment it is given.
|
|
244
|
+
Reading them out of `/proc` is the exposure above, and it is why the token is
|
|
245
|
+
per-instance.
|
|
246
|
+
|
|
247
|
+
That scrub is a **behaviour change** for the `terminal` op, not only a new
|
|
248
|
+
guarantee. A terminal shell used to be handed the agent's whole `process.env`;
|
|
249
|
+
it now gets the scrubbed environment an `execute` child has always had, and so
|
|
250
|
+
does the `stty` resize helper behind it. A terminal session therefore no longer
|
|
251
|
+
sees the agent's own settings — `NAMZU_SANDBOX_WORKSPACE` among them. A
|
|
252
|
+
workload that needs a value in its terminal passes it in `env` on the
|
|
253
|
+
`openTerminal` call, which still wins over everything else, `TERM` included.
|
|
254
|
+
|
|
104
255
|
## Firecracker workspace channels
|
|
105
256
|
|
|
106
257
|
The Firecracker backend exposes two optional same-sandbox channels. Call
|
|
@@ -15,6 +15,28 @@
|
|
|
15
15
|
* these data events. HTTP uses endpoints; the framed guest uses dedicated ops.
|
|
16
16
|
*/
|
|
17
17
|
import type { SandboxExecOptions, SandboxExecResult } from '@namzu/sdk';
|
|
18
|
+
/**
|
|
19
|
+
* The per-instance credential a framed request may present, mixed into
|
|
20
|
+
* the request envelope alongside its `op`.
|
|
21
|
+
*
|
|
22
|
+
* Absent on the vsock and unix transports: that control channel is
|
|
23
|
+
* host↔guest only and never traverses guest egress, so the guest agent
|
|
24
|
+
* authenticates nothing there and this field is simply never written.
|
|
25
|
+
* A guest reached over a ROUTED network has no such boundary, so it is
|
|
26
|
+
* started with a per-instance token (`NAMZU_AGENT_BIND_TOKEN`, fed the
|
|
27
|
+
* pod's own identity by its orchestrator) and refuses every op but
|
|
28
|
+
* `healthz` that does not present exactly that value — from the first
|
|
29
|
+
* frame, since the agent dispatches a connection on its first frame and
|
|
30
|
+
* there is no handshake to defer the check to.
|
|
31
|
+
*
|
|
32
|
+
* The field is OPTIONAL and additive, so a host that never sets it
|
|
33
|
+
* speaks the same wire it always did and
|
|
34
|
+
* `FIRECRACKER_AGENT_PROTOCOL_VERSION` is deliberately unchanged: this
|
|
35
|
+
* needs no coupled golden-image and host rollout.
|
|
36
|
+
*/
|
|
37
|
+
export interface AgentRequestCredential {
|
|
38
|
+
readonly token?: string;
|
|
39
|
+
}
|
|
18
40
|
/**
|
|
19
41
|
* `/execute` request body. Identical field set to the HTTP worker's
|
|
20
42
|
* `handleExecute` body (`command`, `args`, `cwd`, `env`, `stdin`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../src/backends/firecracker/protocol.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../src/backends/firecracker/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../src/backends/firecracker/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../src/backends/firecracker/protocol.ts"],"names":[],"mappings":"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"}
|