Trust boundaries
A box is divided into zones with different trust. Each arrow between zones is a place where something is checked, or where it is not. This page names every crossing so you can see what protects it.
The zones
Section titled “The zones”| Zone | What is in it | Trust |
|---|---|---|
| Outside | The internet, the model provider, paid APIs, outside services such as Jira | Untrusted. Reached on purpose, for specific jobs. |
| The relay | The relay machine, only when remote access is on. A hosted relay is not running during the preview, so this is a relay you run. It carries encrypted tunnel bytes and push messages. | Untrusted with content. It sees names, addresses, connect times and byte counts, never what is inside HTTPS or a push. |
| The GurtOS app | app.gurtos.com, an installable web app served from the public repository |
Trusted as much as its host. It holds each box's device token in the browser's storage. It reaches a box only at an https address, which today means one that a relay gives it. |
| The box's own screen | The portal on the box's display, and anything else that calls the API from the box itself with the box's own Host and Origin | Trusted like the owner |
| Your devices | Phones and browsers paired with a one-time code | Trusted to read, start tasks, undo and answer approvals. Owner's actions only for a device paired as the owner. Anyone without a token is refused. |
| The core, as root | The box service, the vault, every app's code, the action log, the task proxy, SpacetimeDB on 127.0.0.1 |
Fully trusted. Holds every key. |
| Each task | One Linux user, its own copy of a workspace, its worker (Droid or a test worker) | Untrusted. Assumed to be wrong or hostile. |
The core trusts apps completely: an app's tools, routes, triggers and log readers run inside the core as root.
Crossings
Section titled “Crossings”Into a task, and out of it
Section titled “Into a task, and out of it”| From | To | What crosses | Who checks | Logged as |
|---|---|---|---|---|
| Task | Its broker (Unix socket) | Steps, tool calls, questions, costs | The socket is owned by the task's user, mode 0600. The core checks the tool is in the task's tools, applies the budget and keys the call. At most 64 KB per step, question or cost, and 20 MB in all. |
task.step, tool.call, tool.result, approval.asked, cost |
| Task | The internet | Network connections | The firewall sends task users only to the proxy. The proxy checks the task's hosts, resolves the name once, never reaches link-local addresses (the cloud metadata service), and refuses the box's own ports. A name that resolves to the box or to a private network needs an exact host:port. At most 256 connections per task. |
Refusals as net.blocked. Allowed connections are not logged. |
| Droid task | The model provider | Model calls with a placeholder key | The proxy relays only Droid tasks, only to Factory's hosts or api.anthropic.com, and puts the vault's key in the authorization or x-api-key header only. With a Factory key, only the paths Droid calls; a model call may not ask the service to fetch for the task. |
Token counts and cost as cost. Refusals as net.blocked. |
| Task | The vault, the log, other tasks' files, the base files | Nothing | File permissions: all root-only or owned by another user | Not logged. The task gets a permission error. |
| Task | The API, SpacetimeDB, other local services | Nothing | The firewall rejects the packet; the proxy refuses the box's own ports | The kernel log counts firewall rejects (prefix box-block). Proxy refusals are net.blocked. |
| Task | Other tasks' processes | Nothing | ProtectProc=invisible, separate users |
Not logged |
Inside the core
Section titled “Inside the core”| From | To | What crosses | Who checks | Logged as |
|---|---|---|---|---|
| A tool or model job | The vault | One key at a time | The core returns only keys the tool or job lists in secrets |
Not logged |
| An app's route or trigger | The vault | One key at a time | The core returns only keys the app lists in secrets |
Not logged |
| A tool | Outside APIs | Whatever the tool sends | The tool's own code. Tools are outside the task's network limits. | The call and its result as tool.call and tool.result |
| An app's code | Anything on the box | Anything | Nothing. Apps run as root. | Only what the app writes |
From your devices and outside services
Section titled “From your devices and outside services”| From | To | What crosses | Who checks | Logged as |
|---|---|---|---|---|
| The box's own screen | The HTTP API (port 7700, or the Unix socket) | Everything | The request comes from the box itself with no proxy header, a Host of 127.0.0.1 or localhost, and no Origin but the box's own address |
task.created with its source, approval.answered with the by it sends, note for settings, setup, vault and apps |
| A paired device | The HTTP API | Reads, new tasks, answers, undo | A valid device token, as a header or ?access_token=. Without one, 401. It lets the device read, start tasks and answer like the box's screen, but never use the owner's controls (vault, settings, apps, folders, Keep). |
approval.answered with the device's name, whatever the request says |
| A device paired as the owner | The HTTP API | Also pairing, settings, setup, the vault, apps, the owner, folders, Keep | The token's device was paired as the owner from the box's own screen. Other devices get 403. |
note events, keep events. A vault key's name, never its value. |
| A paired device, through the relay | The HTTP API at https://<name>.<relay domain> |
The same as a paired device on the home network | TLS ends on the box. The request carries X-Forwarded-For, so a device token is required. |
The same as on the home network |
| The box (remote access on) | The relay | One outbound tunnel, and encrypted push messages | The tunnel is verified against the relay's pinned certificate authority and logs in with the box's secret. The relay allows each name to route only its own address. | Remote access state in Settings |
| Anyone else | The HTTP API | Only the health check, redeeming a pairing code, the portal's pages, Business work-account sign-in and the open routes of apps | Every other request gets 401. At most 5 wrong pairing codes a minute per address, and 1 MB per request body. |
Not logged |
| The box's own screen | SpacetimeDB (127.0.0.1:3000) |
Live tasks and approvals; answers through the answer reducer |
It listens on loopback, and the firewall never opens port 3000. Only the box writes tasks and approvals, and answer works only from a connection the box vouched for. |
approval.answered with the vouched name |
| Outside services | App routes /t/<app>/... |
Whatever the app accepts | A paired device's token, unless the app marks the route open (openRoutes) and the owner has not refused strangers for that app. Open routes must check their own secret: the Jira app's routes require a shared secret and check the approval belongs to the task; the Mana app's status route returns only a public summary, and its guest and policy routes only queue a request for the owner to approve. |
What the app writes, such as approval.answered by jira:<name> |
| An app's page in a browser | The HTTP API | Its own app's routes; reads of tasks and approvals; answers to its own app's requests | The page runs sandboxed (no same origin) with a window token from POST /api/apps/<id>/window. Anything else is 403. |
What the app writes, and approval.answered |
| Outside callers | Webhooks /hook/<app>/<name> |
A JSON payload that usually starts a task | A paired device's token, then the app's handler | task.created |
| The chat agent (ASI:One, an optional app) | The HTTP API | Reads, new tasks and answers, as a helper | It runs on the box as nobody and calls the API on 127.0.0.1. The API counts it as a helper on the box, not the box's own screen, so it never gets the owner's controls. Only paired chat owners can give it work. |
task.created with source asi1, answers by asi1 |
Into the system
Section titled “Into the system”| From | To | What crosses | Who checks | Logged as |
|---|---|---|---|---|
| An update file | The idle system slot | A whole system image | box-update verifies the signature against /etc/box-os/update.pub and the hash, then checks the image holds the box |
/data/update/last and the serial console |
| The new system | The default slot | Becoming the default | box-confirm.service waits up to 5 minutes for a healthy GET /health |
/data/update/last |
Reading the diagram
Section titled “Reading the diagram”- Every path from a task to anything else goes through the broker or the proxy. Both run in the core and both write to the log.
- The core is the one zone with the keys. Protecting the box means protecting root and choosing apps carefully.
- The API trusts the box itself and paired devices. Everyone else is refused. SpacetimeDB stays on
127.0.0.1. The API speaks plain HTTP, so reach it from other devices over a network you control. See the threat model.