
# 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](https://github.com/denoland/celld/blob/main/docs/security.md).

### 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](/docs/containers) 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_MS` is 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-env` or `SCAFO_CELLD_ENV`. Scafo adds no Celld policy defaults.
- Change `CELLD_TTL_MS` consistently 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](https://github.com/denoland/celld/blob/main/docs/security.md), [limitations](https://github.com/denoland/celld/blob/main/docs/limitations.md) and [Cloudflare compatibility](https://github.com/denoland/celld/blob/main/docs/cloudflare-compat.md) 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](https://github.com/denoland/celld/issues/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.
::

::read-more{to="/docs/operators"}
Keys, roles, scopes and the difference between `auth-invalid`, `auth-scope` and `trust-mismatch`.
::
