Volter World

Refusals

When a world refuses a request your app makes, the refusal names the side that refused and links the one change that fixes it. This page lists every refusal, keyed by the rule the refusal carries. Each refusal's message ends with a link to its entry here.

A request passes only when the app declares it and the world grants it, and a world answers a vendor's API with its twin. So a refusal names one missing side:

missingmeaningwhere the fix goes
declarationthe app (an image, a browser program) does not declare itthe app's declaration: its Dockerfile or compose file, or its program's hosts
grantthe world does not grant itthe world's .volter/world.json, usually runtime.network
transportthe way the client connects cannot carry what the world grantsthe client: send it as an HTTP request, or through the world's proxy
twinthe host is a twin's, and no twin in this world serves this paththe world's twins

A world's refusals are not a security boundary. The Node redirect and the world's proxy refuse cooperatively: a program that ignores them is not refused. A native image run's Seatbelt profile reproduces what Docker lets a container do, so an app that depends on your machine (its files, its other services) fails here the way it would in Docker. Run only trusted code natively; untrusted code runs in the browser tab.

Where a refusal shows

A Node process run in the world (volter world run, a service) gets an Error. Its message is the refusal's text, then the missing side and the fix link in parentheses. error.worldRefusal holds the record. An error from a socket also has code: 'ECONNREFUSED'.

Any other client behind the world's proxy (curl, Python, Go, a browser) gets 502 Bad Gateway:

  • the header X-Volter-World-Refusal: <missing>;
  • the refusal's text, with the missing side and the fix link;
  • the record as JSON on the next line. A browser gets a page with the record in <script type="application/json" id="volter-world-refusal">.

A host that does not answer is a 502 without the header; no rule refused it.

A native image run (macOS Seatbelt) refuses a connection with EPERM. When the run carries its image's declaration (VOLTER_WORLD_DECLARATION, set by the images CLI from the image's runtime binding), a Node process there gets the record for rule native-direct. For other programs, Seatbelt writes a kernel log line, and the address in it is masked:

Sandbox: <program>(<pid>) deny(1) network-outbound remote:*:<port>

The browser tab refuses with its own message, which carries no record yet:

<program> may not reach <host>: the tab's network is closed unless granted, by the World the tab is attached to or the page that embeds it. Grant <program> egress to <host> there to allow it.

Its fix is the same runtime.network grant as not-granted below.

The record

{ "kind": "volter-world-refusal", "version": 1, "capability": "network.egress", "target": "https://example.com/",
  "missing": "grant", "rule": "not-granted", "reason": "the World network policy does not allow GET https://example.com",
  "grantField": "runtime.network.egress", "see": "https://world.volter.ai/docs/reference/refusals#not-granted" }
fieldmeaning
capabilitywhat was asked for: network.egress (a destination outside the world), network.services (one of the world's own services), network.ownLoopback (a listener the run holds), network.host (anything else on the machine's network), files.read (a file the app reads), files.persist (a path the app keeps as its data)
targetthe URL, host:port or path refused
missingdeclaration, grant, transport or twin
rulethis page's entry
reasonthe refusal in words
grantFieldwhere missing is grant and one field grants it: that field of .volter/world.json
declaredBywhere the app's declaration is derived from a file: the capability, the file and its line
seethe link to the entry

Refusals seen in a world

These lines were recorded from one disposable world. It ran one Stripe twin, and its runtime.network granted only https://registry.npmjs.org (phase: build, that origin also in passthrough, publicReads: false). The world ran through volter-world run, on twin-world main at 7aadc14 with this page's change applied.

A Node fetch to a host the world does not list:

[twin-inject] blocked untwinned external fetch to example.com (the World does not grant it: runtime.network.egress; fix: https://world.volter.ai/docs/reference/refusals#not-granted)

The same request from curl, through the world's proxy:

x-volter-world-refusal: grant
example.com is outside this World: no twin serves it, and the World does not reach the internet for it. (the World network policy does not allow GET https://example.com: the World does not grant it: runtime.network.egress; fix: https://world.volter.ai/docs/reference/refusals#not-granted)
{"kind":"volter-world-refusal","version":1,"capability":"network.egress","target":"https://example.com/","missing":"grant","rule":"not-granted","reason":"the World network policy does not allow GET https://example.com","grantField":"runtime.network.egress","see":"https://world.volter.ai/docs/reference/refusals#not-granted"}

The same fetch in a sandbox (volter world up --sandbox), whose record names no grant field:

[twin-inject] blocked untwinned external fetch to example.com (the World does not grant it; fix: https://world.volter.ai/docs/reference/refusals#sealed)

A path of Stripe's host that the Stripe twin does not serve, from curl:

x-volter-world-refusal: twin
checkout.stripe.com is outside this World: no twin serves it, and the World does not reach the internet for it. (no twin in this World claims /unclaimed on checkout.stripe.com (stripe): no twin in this World serves it; fix: https://world.volter.ai/docs/reference/refusals#unclaimed-path)

A run carrying an image's declaration that lists only https://api.stripe.com for egress:

[twin-inject] the guest does not declare egress to https://example.com (the guest does not declare it; fix: https://world.volter.ai/docs/reference/refusals#undeclared)

Its record carries "declaredBy": {"capability": "network.egress", "file": "Dockerfile", "line": 1}.

Every refusal

not-granted

network.egress, missing grant. The world's runtime.network does not list the destination. Reads need egress; any method other than GET, HEAD or OPTIONS needs the destination in writes.

Fix: add the origin, the exact path or the subtree (ending in /) to runtime.network.egress, or to runtime.network.writes for a write. A world whose parent lists less is held to the parent's list.

sealed

network.egress, missing grant. The world is a sandbox (volter world up --sandbox). It reaches only its twins, so no grant lists anything for it.

Fix: give the destination a twin, or run the world without --sandbox and grant it in runtime.network.

policy-unreadable

network.egress, missing grant. The process's copy of the world's network policy (VOLTER_WORLD_NETWORK_POLICY) cannot be read, or belongs to another world. Every destination outside the world is refused.

Fix: run the process through the world (volter world run) rather than copying its environment by hand.

invalid

network.egress, missing grant. The request's address or path cannot be read unambiguously: a backslash, an encoded / or \, or a malformed escape. It is refused wherever the world lists any grant.

Fix: send the request to a plain URL.

unclaimed-path

network.egress, missing twin. The host belongs to a twin in this world (several vendors can share one host), but no twin here serves this path. The request is neither answered by the wrong twin nor sent to the real vendor.

Fix: add the twin that serves that API to the world, or set its *_TWIN_URL. If no twin serves it yet, the API needs one.

undeclared

network.egress, missing declaration. The app's declaration does not list the destination. A Dockerfile declares open egress, so this comes from a declaration that lists hosts: a browser program's hosts, or an image binding's capabilities.network.egress.

Fix: add the host to that list; declaredBy names the file and line.

raw-socket-twinned

network.egress, missing transport. The client opened its own socket to a host a twin serves, so the twin never saw the request.

Fix: point the SDK at the twin's endpoint variable the message names, which the world sets when it runs the app.

raw-socket-app

network.egress, missing transport. The client opened its own socket to a host the world routes to the application (volter-world app-url --host).

Fix: reach that host with an HTTP request, which the world routes.

inspected-transport

network.egress, missing transport. The destination's origin has a grant, so the world reads every request to it (its path and method are checked one request at a time), and this client connects in a way that cannot be read request by request: a raw socket, HTTP/2, a protocol upgrade, or an undici dispatcher of its own.

Fix: send it as an HTTP/1.1 request, or, in a phase: build world, list the exact origin in runtime.network.passthrough (its grant must be the whole origin).

guest-upgrade, guest-http2

network.egress, missing transport. A local guest's HTTP leaves through the world's guest listener, which carries plain HTTP requests only.

Fix: use HTTP/1.1 without an upgrade from the guest.

guest-inspected

network.egress, missing transport. A World Machine guest asked for a destination the world reads request by request. The world cannot read it without losing the address it checked for the guest.

Fix: grant the whole origin, or reach it from the world's host.

native-direct

network.egress, missing transport. A native image run reaches other hosts only through the world's proxy, so the world sees each request, and this client dialled the host directly.

Fix: send the client's traffic through the world's proxy: HTTPS_PROXY (for Node's own fetch, NODE_USE_ENV_PROXY=1).

service-undeclared

network.services, missing declaration. The destination is one of the world's own services, and the app's declaration does not name it.

Fix: name the service in the image's compose depends_on (capabilities.network.services).

own-loopback-ungranted

network.ownLoopback, missing grant. The run reached a listener it holds, and the world sets runtime.network.ownLoopback: false.

Fix: remove ownLoopback: false from runtime.network, here and in any parent world.

guest-private

network.host, missing grant. A guest asked for an address on the machine's own network that is neither one of the world's endpoints, nor a service it declares, nor its own declared listener. No world grants a guest the machine's network.

Fix: if it is the run's own listener (worker processes on ports no Dockerfile names), the image declares own loopback, which every container image does. If it is a service the app needs, make it a service of the world, and declare it.

host-undeclared

network.host, missing declaration. A process that is not confined from the machine's network, run with a declaration that does not include it, asked for an address on it.

Fix: declare it (compose network_mode: host), and see guest-private for what a world grants a guest.

persist-undeclared

files.persist, missing declaration. The app keeps a path as its data that its declaration does not name.

Fix: declare the path as a VOLUME. A declared path is writable for the run and does not outlive it.

read-undeclared

files.read, missing declaration. An image's process read a file that, in Docker, the container could not see: a file in your home folder, for example. A container sees its own filesystem, the world's provided files and its declared mounts. Natively it would have worked, and in Docker it would not, so the native run fails the same way.

Fix: if the app needs the file, mount it (a compose bind mount inside the world's folder) or copy it into the image.

mount-outside-world

files.read, missing grant. The image declares a bind mount whose host path is outside the world's folder. No world grants a host path it does not own.

Fix: move the mounted data into the world's folder, and mount it from there.

read-invalid

files.read, missing declaration. The path read is not absolute, so it names no file the decision can place.

Fix: read the file by its absolute path.

View Markdown source

On this page