Trust model and limitations
A description of the boundary Scafo draws, the surfaces it exposes, and what it does not do. This is a description, not an audit.
#Trust model
One Scafo installation is one trusted workload domain. Repositories, dependencies, build inputs, deployers, runtime code and operational credential holders must trust one another.
Application, operator and OIDC scopes authorize actions. They do not isolate hostile workloads.
Important
Do not use one installation for arbitrary customer code, public build submission, or mutually untrusted applications. Use separate installations, buckets, and host or VM boundaries for separate trust domains.
#Protected surfaces
| Surface | Auth | Notes |
|---|---|---|
/.well-known/scafo/health | none | Intentionally public. |
/v1/ingress/routes* | ingress bearer token | Reveals every node's address for every host. |
/v1/storage/** | ingress bearer token | Carries private keys in both directions. |
| everything else | node, operator or workload token | Roles and application scopes limit what each may do. |
Both privileged surfaces live on the node's own listener. The ingress bearer token is all an ingress ever holds. The ingress enforces the rest of that boundary itself: it never proxies either prefix through the public platform API apex.
/.well-known/celld/health is the application runtime's health endpoint, not the Scafo node/API one.
Registry conditional writes prevent lost updates. Coordinator leases and fences protect stale placement decisions and other lease-gated work.
Node API and runtime listeners default to loopback. Wider binds require an explicit private-network or WireGuard transport decision.
#Limitations
#No OS sandboxing
Builds and runtime processes are not OS-sandboxed from the daemon or from each other. Worker egress can reach any address visible to its network namespace.
#Celld's operator API
Celld's internal operator API is mostly unauthenticated. Keep it on a private network or overlay; do not expose it publicly.
A private network does not keep it from the applications themselves. A Worker's outbound fetch starts on the node, so the code of one application can reach the operator API of every runtime process on that node — read its state, make it reload, shut it down. That is inside the trust model above, and it is the concrete reason the model is what it is: every application of an installation can act on every other one.
A node run with --network host shares the host's network namespace, so the same code also reaches whatever else listens on the host's loopback. Run such a host for Scafo alone, or treat everything on it as part of the same trust domain. See Celld's security guidance.
#Credentials reach the runtime
Celld and bucket credentials are administrative authority. Celld child processes receive fleet credentials. Scafo provides no OS credential boundary against trusted-workload compromise or native escape.
#The engine socket
A node that serves container classes is given the machine's container engine socket, and a node that can reach that socket is root-equivalent on that machine. Mounting it, and putting the node process in its group, is the operator's opt-in: Scafo never changes groups, mounts nothing and escalates nothing itself, and a node given no socket has no engine to reach. Such a node also runs in the host's network namespace, with what that means for Worker code above.
#Application variables are plain text
An application's variables live in its own wrangler.jsonc or wrangler.json, and Celld stores them as plain text in the bucket: anyone with bucket read access can read every variable of every application. Celld has no secret store. A value passed with scafo deploy --var is additionally visible in the process list and the CI log of the machine running the CLI. Keep defaults in the config's vars, pass the values that differ between applications from the CI's secret store as --var, mask them in CI logs, and treat bucket read access as access to every variable.
#The bucket is an availability boundary
The bucket is both the durable registry and the availability boundary. It must support conditional writes and ranged reads, and provide reliable, low-latency lease traffic. Scafo cannot make an unreliable object store highly available.
#Celld leases
Each runtime process renews its lease every third of CELLD_TTL_MS and retries a failed renewal inside the remaining lifetime. A process that loses its lease fences itself and exits with code 3; the node restarts it. Scafo preserves Celld's default lifetime.
#Deploy bounds are scheduling, not admission
Deploy admission bounds artifact bytes and entries, retained builder output, node and per-application concurrency and queue depth, staged bytes, and receive/queue/task deadlines. These are scheduling bounds, not OS resource admission. Apply host or container CPU, memory, process, filesystem and network limits.
#Supervision requirements
scafo sail closes deploy intake, cancels deploy work, joins builder cleanup, then drains runtimes on SIGINT/SIGTERM.
- On a bare host, use the installer's systemd unit or an equivalent cgroup-owning supervisor that terminates the daemon first, removes remaining builders on exit or timeout, and allows at least 50 seconds of shutdown grace. If
CELLD_SHUTDOWN_TOTAL_MSis larger, allow at least 10 additional seconds. - In containers, keep the direct Node entrypoint as PID 1 in a private PID namespace. Do not use host PID mode or a persistent wrapper.
Physical-host failure remains outside process-code guarantees.
#Operator responsibilities
Application authentication, public TLS, and ingress policy remain the operator's.
#Deploying safely
- Keep node, runtime-peer and operator traffic on a private network or encrypted overlay. Publish only intended application ingress endpoints.
- Protect bucket, node, ingress-token and deployment credentials as administrative secrets. Use a bucket scoped to one installation.
- Test object-store conditional writes, latency and failure behaviour before production use.
- Configure Celld runtime policy through
runtime.celldEnv,--celld-envorSCAFO_CELLD_ENV. Scafo adds no Celld policy defaults. - Change
CELLD_TTL_MSconsistently on every node; an unplanned takeover waits up to that lifetime. - Pin and review the Scafo build and the current Celld release. Review Celld's security, limitations and Cloudflare compatibility notes before upgrading.
#The Celld boundary
Scafo requires Celld 0.5.0 — scafo sail refuses an older binary with capability-missing — and creates one Celld fleet per application; the image and the Linux installer ship 0.6.2. One application is one Celld fleet, and a fleet takes one version at a time, except across a rolling pair upstream declared (0.5.0 ↔ 0.5.1, 0.6.0 ↔ 0.6.1, 0.6.1 ↔ 0.6.2), whose nodes may be replaced one at a time: a node neither starts an application's process nor accepts a deploy for it while another answering node serving that application, or crash-looping on it, reports a Celld release it may not share a fleet with (version-skew, 409).
| Celld owns | Scafo owns |
|---|---|
| Deployment adoption | Registry |
| Application health | Authorization |
| Cells | Placement |
| Runtime resource policy | Supervision, deploy intake, and the routing every ingress reads |
Celld exposes one current deployment per application fleet. Scafo records a monotonic deployment revision, not a selected version; it provides no per-deployment routing and no historical deployment selector. See Celld issue #181.
Scafo does not add a raw TCP bridge or transparent tunnel around missing Celld APIs.
Warning
Hostile workload isolation is not available. Do not infer it from scopes, deployment separation, or ingress routing.