Egress
A sandbox can install what it needs, or it can run a proof. Never both at once. Egress happens in two phases, and the second one closes the registries.
The two phases
- build
- Provisioning. The sandbox may reach allowlisted package registries and nothing else. Every install is brokered and recorded, and the phase ends by writing a toolchain lock — every package, its version, its hash, its source — into the run manifest.
- sealed
- Execution. The registries are closed. Egress narrows to your application and the hosts your environment names. No installs are permitted; an attempt is a refused call and a ledger entry.
Closing the registries before the proof runs is the load-bearing part, and three things follow from it. The environment a verdict describes is the environment the manifest names, because nothing was installed after it was written. There is no exfiltration path through “downloading a tool” — a package registry is arbitrary content fetch, and for several ecosystems arbitrary content upload, wearing a legitimate hostname. And the proof runs against a frozen toolchain whose hash you can check.
How the allowlist is built
The sandbox cannot ask for a host. It can only be given one. The sealed-phase allowlist is derived from the environment contract, the external dependencies this run actually materialized, and the host grants in force — then hashed into the run manifest and enforced outside the sandbox.
- Hosts come from the provider's own descriptor, never from a string found in your code. A URL literal naming a known provider selects that provider's descriptor; a URL literal naming something unknown establishes nothing.
- How a dependency is fulfilled decides what it contributes. Live and provider-sandbox dependencies contribute their real hosts; a recorded dependency contributes the in-sandbox replay proxy, not the provider; a locally substituted one contributes nothing at all, because it is a service inside the twin.
- Hosts are exact and wildcards are refused.
*.stripe.comwould grant every host Stripe ever runs and every subdomain it ever loses control of. - IP literals are refused. A host with an unenumerable name is instantiated from metadata the provider returned, not from text parsed out of the repository.
- The allowlist is per side, and a difference between BASE and HEAD is reported. A pull request that adds a provider adds a host on HEAD only, and that shows up.
A host that is genuinely yours — a partner API with no public descriptor — needs a host grant: a versioned, expiring entry created by a repository admin and applied identically to both sides. A pull request cannot create one. The party who can open a route is not the party who writes the diff.
What a denial is
An outbound connection to a destination that is not on the map is not a connection failure. It is a recorded denial, with the hostname or address, the port, the protocol, the service that attempted it, how many times, and a typed reason.
What your application is told carries no Certus marker: HTTP-shaped traffic gets a plain error with no identifying header, and anything else is reset. It is never left to hang, because a hang burns wall clock and is indistinguishable from a slow provider. The point is the record, not the message.
A non-zero denial count is evidence, not necessarily a fault. It tells you your change reached for something the proof environment did not provide, which is often exactly what you want to know.
Reading it in the console
The Substrate view shows each side's egress phase, the hosts allowed in it, and the number of denials recorded. Compare the two sides: an allowlist that differs between BASE and HEAD is usually a new dependency, and it is worth knowing you added one.
When it says something unexpected
- A real dependency was denied
- Read the reason first. “Not in the map” or “no provider” means the environment contract did not identify the provider — the fix is there, not in the allowlist. “Policy forbids” means the dependency is deliberately being answered locally or from a recording.
- You want to add the host by hand
- First ask whether it needs a route at all. Moving the dependency to a local substitute — MinIO for S3, Mailpit for SMTP, a mock identity provider — removes the host, the credential, the redaction and the explanation in one step.
- It is a real provider Certus does not know
- It needs a registry entry and a fixture, which lands as a visible diff. Exactness deliberately costs a blocked run when a provider adds a host.
- An install was refused mid-proof
- Correct, always. The remedy is to re-provision in a fresh run, never to install during one.
- Denials to a host you have never heard of
- Worth reading closely. Something in the dependency tree is reaching outward during your proof.