Trust Architecture
Status: alpha trust boundary implemented for trusted anchors, immutable ambient-hosted refusal and migration, the optional bounded hosted-lease lane, signed/encrypted fleet metadata, org documents, and transparency records. General browser-key authentication and peer-link ORL gossip remain staged/future. This chapter records why the access system is shaped the way it is and what each piece of infrastructure is allowed to do. The operational reference for the UI lives in Web Dashboard → Access; the daemon-to-daemon layer is in Peer Federation; the fleet-level operating model built on these mechanisms — which client for which daemon, custody per tier — is Trust Tiers.
The access system is built for a network of agentic networks: daemons
(agents) owned by people and organizations, where an owner can grant other
people and other daemons scoped IAM access to their machines, infrastructure,
and resources — with pleasant abstractions (passkeys, single-use route
linking, and route discovery) on top. The discovery client stays a plain
web browser. Root and trust administration require local presence or a browser
enrolled for independently reached direct mTLS by a trusted owner. An optional,
off-by-default fleet-name lane can borrow a short-lived daemon-local lease
bounded to a compiled non-root preset after confirmation on one of those
surfaces. The packaged macOS app contains a local mTLS bridge, but no Developer
ID-signed/notarized release has been published for this alpha. An
-unsigned-dev app artifact is not a distribution trust anchor.
The residual trust problem is code provenance
Break a hosted convenience service (connect.intendant.dev) into what it
could betray if compromised:
- Authentication — it is the WebAuthn relying party for accounts.
- Introductions — route discovery and daemon-presence metadata.
- Delivery — optional encrypted Web Push notifications and an optional ciphertext reachability relay. TURN remains a separate media component.
- Metadata — fleet lists, labels, claim records.
- Application code — the HTML/JS the browser runs.
Items 2–4 can be bounded cryptographically: trusted daemon-origin dashboard tunnels verify a daemon-signed binding (daemon public key + session grant + client nonce), push payloads are encrypted to the browser subscription, and metadata can be client-signed and client-encrypted. Fleet-record signatures are self-contained rather than anchored to an owner/device trust set, so they detect same-key alteration but do not stop a store from substituting a fresh self-signed record on another device. None of that metadata grants daemon authority. These mechanisms do not detrust item 5: Connect controls the page that asks for passkey assertions and PRF output, so malicious code at that endpoint could read or misuse account, fleet, or decrypted vault state made available to it. Item 1 is account authentication and route metadata only; a Connect account assertion does not authenticate to a daemon. Connect exposes no browser-control signaling: when a daemon advertises the optional lane, Connect can only navigate the account holder to that daemon’s fleet origin.
The optional reachability relay (docs/src/self-hosted-rendezvous.md) sits
squarely in the transport class (item 3) and does not enlarge Connect’s trust.
It routes a fleet name to a NAT’d daemon by peeking the TLS SNI and splicing
ciphertext both ways — it terminates no TLS, holds no certificate, and sees no
plaintext, so the browser’s handshake completes end-to-end against the daemon’s
own fleet certificate. The daemon tunnel splices that ciphertext into a
dedicated loopback-only gateway ingress, separate from the public listener.
Which listener accepted the connection is immutable transport provenance: the
gateway marks every such connection ReachabilityRelay before TLS or HTTP
parsing. In the all-off default, it serves only authority-free discovery bytes
under anonymous role:none and refuses every protected HTTP, MCP, signaling,
and WebSocket route. Exactly two per-daemon opt-ins admit anything more, each
gating its own credential class and neither moving the other:
- With hosted control on (
[connect] hosted_control_enabled), the browser-lane carve-out is a fresh proof by an active daemon-mintedhosted_leaseprincipal, or a one-use WebSocket ticket minted by such a proof; a second compiled classifier then limits the route, method, frame, concrete action, target, and outbound event projection. - With relay peer admission on (
[connect] relay_peer_admission— default off, boot-pinned, deliberately no environment override), the peer-lane carve-out is a TLS client certificate that resolves to an Approved, unexpired peer identity record: a daemon-side credential minted by the target’s own access CA through an owner-consented lane (pairing invite, doorbell approval, or org grant materialization) whose private key lives in daemon files — a class no browser keystore can hold or be scripted into presenting. Admission converts transport provenance only: the connection continues into the same transport-auth ladder every direct-lane peer request passes, the peer profile the owner granted remains the sole control-depth authority per operation and frame, and revocation or expiry of the record kills the next request. A browser-enrolled mTLS certificate resolves no peer record and keeps the refusal byte-identical, in both key states. The relay itself is unchanged — it still sees only ciphertext and admits nothing; the widening is entirely a daemon-side policy predicate.
Thus neither the loopback address of the tunnel’s last hop
nor a browser-controlled Host: localhost can enter the trusted-local lane.
Fleet SNI remains an independent provenance gate, not an mTLS bypass. The relay
ingress also rejects non-TLS bytes before the gateway’s raw ICE-TCP and
cleartext-MCP demux. The relay capability is bounded to withholding, delaying,
replaying, or delivering arbitrary ciphertext; lease minting and
compiled-preset scope remain daemon-local.
Item 5 is the one the web platform will not let us escape. The browser binds code identity to origin, origin trust to TLS+DNS, and the server behind an origin can change what it serves at any time, per user, silently:
- Service workers do not pin code — the worker script itself re-fetches from the origin, so the server always stays in control.
- IPFS and friends relocate the trust to whichever gateway resolves the content.
- Signed web bundles / Isolated Web Apps are effectively app installation under another name, and are not portably available.
- Passkeys prove user presence to the relying party. They protect the credential; they do not make the relying party honest, and they do nothing about the code the RP serves after login.
So within “pure browser, no extension” there is exactly one class of origin that can serve root-administration code without adding new trust: an origin whose compromise is already game over for you and whose route is independently authenticated. Your own daemon runs your agent with real authority — it is already inside your trusted computing base. Code served over its loopback or fingerprint-verified direct-mTLS route adds zero marginal trust. A WebPKI fleet name does not: the rendezvous controls that DNS namespace and can serve code at the same browser origin.
That observation fixes the design:
The rule: root-capable code is served by the resource owner, runs from a trusted local console, or ships as a native client whose release signature the user verifies (none has been published for this alpha); authority is only ever minted by the target daemon’s local IAM. A hosted claim, account assertion, route, or raw browser key carries discovery metadata and creates no principal or grant. Ambient hosted provenance is always capped at the zero-permission
role:none; no local grant, persisted edit, or generic IAM call can raise it. The optional hosted-control lane admits only an exact, expiringhosted_leaseprincipal minted after a trusted confirmation, and a compiled immutable floor excludes access/IAM, credentials, organization-root operations, approval resolution, and raising the lane’s own ceiling.
Anchor daemons
Every browser earns its trust once, through one ceremony, with one daemon the user controls — the anchor:
- The ceremony is the existing certificate enrollment
(
intendant access serve-certs, p12/mobileconfig) or an on-LAN bootstrap. It happens once per browser, not once per daemon. - The anchor — or any daemon of the user’s fleet reached over an independently verified route; the serving role is deliberately fungible — serves the dashboard and the Access admin surface. Root administration executes in an origin backed by hardware and route identity the user controls.
- The app generates a client identity key in the browser (WebCrypto P-256, non-extractable private key, stored in IndexedDB) for fleet-record signatures, attribution, and future identity work. In this alpha it is not a daemon login credential: local presence or an mTLS client certificate is the active browser authentication boundary.
- Reaching any other daemon (yours, or an organization’s) from a trusted client means: the rendezvous can supply route metadata, while the daemon-served client binds the independently reached channel to the target daemon’s key and authenticates through loopback or mTLS. A future verified packaged native release could do the same for its bundled local daemon. Browser-key fingerprints can be stored in local IAM, but no alpha human ingress consumes a browser-key signature as general authentication. Connect-served code has no ambient control ingress; the optional hosted lane consumes only proof by the tab key named in a separately approved, expiring lease.
- Passkeys authenticate the Connect account and derive keys for the encrypted fleet/vault envelopes. They do not make Connect-served code trustworthy and do not turn a claim into daemon authentication. The durable browser identity key is enrolled separately by an already-trusted daemon root.
A companion chapter, Credential Custody, applies the same discipline to the other secret class — the model provider credentials a daemon spends. Vault, lease, and relay components have shipped, but a Connect-origin account vault has no trusted delivery bridge to a daemon in this build; the custody chapter is the source for the exact boundary.
The hosted service keeps route discovery and presence, optional encrypted push
delivery, encrypted/signed fleet-metadata backup, account/route linking, a name
directory, the browser-code distribution surface (the install scripts are
release-pinned GitHub assets it at most redirects to), and an optional
ciphertext reachability relay. None is a daemon authority mint, but they are not
“zero trust”: Connect controls availability, account and route metadata, the
code it serves, and whether relayed bytes arrive. A daemon may publish a signed
capability hint so Connect offers fleet-origin navigation; lease creation and
approval still terminate at the daemon. The rendezvous component is
self-hostable (how),
and a daemon’s agent card states which rendezvous it uses
(rendezvous_base) — connect.intendant.dev is the default instance of an
open component, not a chokepoint.
The trust ledger
What each component can do to you if it turns malicious in the current alpha; rows explicitly marked future remain design constraints:
| Component | Worst case if malicious | Why it is bounded |
|---|---|---|
| Your anchor daemon | Full compromise | It already runs your agent; nothing new is delegated to it |
| Another daemon of yours | That machine’s authority | Each daemon is its own authority island |
| Org portal daemon (guest lane) | The guest’s org-scoped session | Blast radius = the org’s own resources; self-defeating |
| Connect route directory | Controls availability, account/route metadata, first-introduction labels, and the navigation target it presents | Daemons co-sign route links; claims and account assertions create no daemon IAM state; the optional button is a daemon-signed capability hint, not authority, and opens only the daemon’s public doorbell |
| Optional Web Push delivery | Denial of service; delivery timing analysis | Payloads are encrypted to each browser subscription; notifications carry no daemon authority |
| TURN relay | Denial of service; traffic analysis | Media remains separate from the Connect SNI relay; any deployment must use short-lived credentials and preserve authenticated, end-to-end encrypted daemon channels |
| Fleet metadata store | Denial of service; false labels/routes or a substituted self-signed record on a new device | Private fields are encrypted; the current browser detects same-key alteration; records carry no daemon authority. An owner/device signer trust set is not shipped yet. |
| Name directory | Handle confusion at first introduction | Key-first identity; handles are labels; org keys sign membership; append-only transparency log over all name bindings (STH pinned + consistency-verified by browsers; inclusion proofs on claims), optional DNS/GitHub attestation badges, invite-gated registration + reserved handles + dormant-handle reclamation |
| Fleet DNS zone + WebPKI (fleet-name route) | Controls the endpoint and origin-scoped code reached through the fleet name | With hosted control off, the route is discovery-only. With it on, unproved traffic is still anonymous role:none; protected routes require a short-lived daemon-minted lease bound to a tab key and exact compiled preset. The immutable floor and trusted confirmation cap same-origin authority. Shipped peer witnesses and an eligible signed-application witness shorten the detection window; the signed-application protocol accepts only a qualifying distribution anchor, never the current unsigned development artifact. CT is the slower fallback. No detection path mints authority. |
| Hosted Connect origin (directory lane) | Controls account/route/presence metadata, page-visible or unlocked state, navigation, and the installer-route redirect | Claims, passkeys, and account assertions grant nothing, and raw hosted provenance remains immutable role:none. The optional navigation hint is daemon-signed and leads only to the daemon’s lease doorbell. Served artifacts are transparency-logged (evidence, not prevention). The install scripts ship as release-pinned, transparency-logged GitHub assets Connect at most redirects to, with the redirect target pinned by intendant hosted-verify; the release channel itself remains a real software-supply-chain trust boundary, anchored on GitHub Releases and the log rather than on this origin. |
| Foreign browser origin / DNS rebinding | Drive a browser-held mTLS certificate or loopback root fallback cross-site | Every non-public HTTP route, direct dashboard signaling, and /ws reject foreign browser Origins before resolving transport authority; cross-/same-site Fetch Metadata closes navigation and subresource requests that omit Origin. Explicit authority-free shell/assets/discovery/signed-document doorbells run as anonymous role:none. Cleartext “own origin” is limited to localhost or a literal loopback address, so matching attacker-controlled Origin and Host values do not bypass the gate; non-loopback browser administration uses HTTPS/mTLS. |
Trust scales with the blast radius of the relationship: a global service that could betray everyone at once is allowed to hold approximately nothing; the party whose resources you are touching may hold exactly the trust that relationship already implies; you hold everything about yourself.
Organizations: two lanes
An organization is a root keypair plus a handle — not a row in a central database. Org membership and grants are documents signed by the org key; each org daemon verifies the chain and enforces its own local IAM, exactly as a personal daemon does.
Lane A — members bring their own agent. The consistent version of the network: Alice has her own daemon (laptop, VPS, anything), her browser trusts only it, and the org binds Alice’s identity — an mTLS identity for shipped alpha human access, or her daemon’s peer identity — into IAM grants on its machines with scoped roles. Alice touching org infra never requires Alice to trust org-served code, and the org never has to trust Alice’s infrastructure. The existing split between the user/client domain and the peer domain is preserved on purpose: an org can grant Alice-the-human (the client-key document shape is staged until a trusted direct authenticator consumes it) or Alice’s daemon (agent-to-agent peer profile with filesystem scoping), and those are different, separately auditable trust decisions.
Lane B — guests. A human with no daemon gets served the app by the org’s own portal daemon (orgs have real domains and can hold real TLS certificates; the ACME/private-IP pain that rules this out for personal daemons does not apply). The portal could betray its guest — but only with authority over the org’s own resources, which is a categorically smaller and self-defeating threat compared to a global origin. For the true cold start — fresh machine, no prior trust, nothing at stake — no browser-only design escapes trusting some server for the first load. We refuse to turn that observation into ambient authority: the global hosted origin is a directory lane only. It can link and locate a route, and conditionally navigate to a daemon’s fleet-origin lease doorbell, but it cannot create or approve a daemon-control session. An organization can offer browser control from its own resource-owner portal; global Connect itself cannot be opted into authority.
Organization grants: implementation and remaining work
Status: core implementation shipped. Grant expiry, org root keys, signed grant documents, per-daemon trust with a local cap, materialization, the presentation/issue/trust/revoke endpoints, direct document presentation/materialization, signed revocation lists + renewal, peer-subject documents, and issuer-key delegation are live. Peer-subject documents are usable now; human client-key documents can materialize but cannot authenticate an alpha session because shipped human ingress does not consume browser-key proofs. Revocation-list gossip over peer links remains. This section is the spec the code follows; the earlier “two lanes” section is the product narrative it serves.
Objects
Org identity. An organization is an Ed25519 root keypair plus a handle.
The root key lives on an org-designated daemon
(intendant org init <handle> →
<access-cert-dir>/org/<handle>/root.pk8, 0600), following the
existing daemon-identity custody pattern; it is exportable for offline
custody. The access-cert directory is <state-root>/access-certs on
macOS/Linux (default ~/.intendant/access-certs) and the OS data directory’s
intendant/access-certs on Windows. Day-to-day signing can move to delegated
issuer keys certified by the root; root-signed documents carry no chain, and
delegated documents carry the issuer certificate beside the signature it
explains.
Org grant document. A self-contained, signed statement a member presents to any daemon that trusts the org key:
{
"v": 1,
"kind": "org-grant",
"org": { "handle": "acme", "root_key": "<ed25519 b64u>" },
"subject": {
"client_key_fingerprint": "…", // or "peer_fingerprint": "…";
"label": "…" // exactly one subject fingerprint
},
"role_id": "role:session-reader", // or "peer:<profile>" for peer subjects
"targets": ["*"], // or explicit daemon ids
"grant_id": "<uuid>", // stable id, used by revocation
"issued_at_unix_ms": 0,
"expires_at_unix_ms": 0, // REQUIRED; hard cap 90d, default 30d
"chain": [{ "…": "issuer cert object" }], // present when an issuer key signed
"sig": "<ed25519 over the newline payload>"
}
The signing payload is newline-joined fields (the protocol style already
used by claim proofs and client-key offers), not canonical JSON. The
document is authorization, not authentication: it is useful only when an
active transport separately authenticates the bound subject. Peer certificate
subjects have that ingress today. Browser client-key subjects do not in this
alpha, so materializing one creates record state but no usable login path.
chain is omitted or empty when the root
signed the document and present with one issuer certificate when a
delegated issuer signed.
Subjects are exactly one of two cryptographic fingerprints: a browser client key for the human lane, or a peer daemon certificate fingerprint for the peer lane. Connect-account subjects are deliberately absent: they would make the org-grant path only as trustworthy as the rendezvous’s account assertion — a compromised hosted service could claim to be a granted member and collect the org’s authority on every daemon that trusts it. Peer certificates authenticate cryptographically end-to-end. Browser client keys remain staged subject vocabulary until a trusted browser-key authenticator is implemented; current human access uses loopback or explicit mTLS. Account subjects can be added later as an explicit, documented weakening if a real need appears.
Daemon-side org trust. iam.json gains
trusted_orgs: [{handle, root_key, max_role, status, added_at}]. Trusting
an org is a root-session action on each daemon — one click across the fleet
via the phase-4 fanout. max_role defaults to role:operator: an org can
never hand out more authority on your daemon than you allowed it, and
org-root requires an explicit local override (ceilings still apply on top,
by binding provenance, as today). Operator is the right default because
trusting an org is itself the consent moment, operator already excludes
access/settings/runtime administration, and a lower default would make the
org lane’s normal grants (terminal, files, sessions) fail confusingly. A
document whose role exceeds max_role is rejected at presentation rather
than silently downgraded, so issuers learn the cap immediately.
Verification and materialization
On presentation, the daemon verifies: signature against a trusted org
root or delegated issuer key → expiry → targets contains this daemon
(or *) → subject kind and role/profile namespace match → the document
fits under the trusted-org cap for its lane. A human-subject document
materializes an ordinary local IAM grant (source: "org:acme", the
document’s grant_id, expiry recorded) rather than evaluating documents
per-request; a peer-subject document materializes into the peer identity
store. Auditability and the existing evaluators come for free, and the
local owner can revoke or re-role the materialized authority like any
other; local IAM and local peer identity state always win.
Prerequisite schema work, valuable on its own: IamGrant gains
expires_at_unix_ms: Option<u64>, and enforcement treats an expired grant
as inactive (temporary grants for humans drop out of this immediately).
Presentation paths: an explicit POST /api/access/org-grants in the public
doorbell class (rate-limited and size-capped), and an org_grant ride-along field on a daemon-origin
dashboard-control offer so an org member’s first trusted direct-mTLS
connection to an org daemon can materialize the grant and proceed in one round
trip. Materialization runs before grant resolution, so the freshly written
grant resolves for the offer that carried it. Connect does not relay or present
this field in the default build: the service rejects hosted control calls and
the daemon drops legacy Connect offers before parsing any ride-along. A failed
direct presentation is non-fatal when another identity resolves; otherwise its
error is surfaced in the local answer.
“Public” here describes an authority-free courier door, not public daemon authentication. The caller identity receives no role and the HTTP request does not become a dashboard/control session. The daemon parses the bounded document, verifies its org signature and local trust/cap, and can materialize a grant only for the cryptographic subject named inside it. A peer subject must later authenticate with its peer mTLS key. A human browser-key subject is record-only in this alpha: peer offers can verify that key for attribution, but no ingress admits it as the authority-bearing IAM principal. The same distinction applies to public renewal and ORL read/apply: they verify or return signed documents, never authenticate the courier.
Browser-side storage (intendant_org_grants_v1, keyed by org handle) is a
record format, not a login or automatic alpha presentation promise. The default
Connect directory may display records already present in that origin, but it
does not present them to daemons. The retired browser-key offer code contains a
presentation field, yet no shipped human ingress authenticates that
key. Usable human access therefore requires an explicit trusted mTLS binding;
peer-subject documents remain usable through peer mTLS.
Materialization itself is idempotent-quiet and local-wins: an unchanged
presentation neither rewrites iam.json nor grows the audit log, and a
document whose materialized grant (or subject principal) was locally revoked
is refused rather than resurrected. The org’s escape hatch is a fresh document
(new grant_id); the owner’s is re-enabling the grant from a root session. This
same property is what lets the step-5 revocation list revoke by grant_id
durably.
Revocation
Layered, with the failure mode stated honestly:
- Short expiries + renewal are the primary mechanism. Documents are cheap to re-issue; the org daemon serves renewals to still-valid members.
- Org revocation list: the root signs
{org, seq, revoked_grant_ids[], revoked_subjects[], revoked_issuer_keys[]}; org daemons serve it publicly, browsers carry it today, and peer-link gossip remains later plumbing; daemons enforce monotonicseqand apply it by revoking materialized grants and peer identities. - Local override always works: any daemon root can revoke an org-materialized grant locally, ORL or not.
An unreachable revocation list plus a long expiry is a stale-authority window — hence the 90-day hard cap and the 30-day default.
ORL format and semantics. The list is cumulative and self-contained:
{
"v": 1,
"kind": "org-revocations",
"org": { "handle": "acme", "root_key": "<ed25519 b64u>" },
"seq": 4, // monotonic; consumers refuse stale
"revoked_grant_ids": ["…"], // document grant_ids, not local grant ids
"revoked_subjects": ["…"], // subject fingerprints — "member is out"
"revoked_issuer_keys": ["…"], // delegated issuer keys revoked wholesale
"issued_at_unix_ms": 0,
"sig": "<ed25519 over the newline payload>"
}
The signing payload is newline-joined like every other protocol here and
includes the grant-id list, subject-fingerprint list, issuer-key list, and
issue time. The org daemon persists the current list next to the root key
(org/<handle>/orl.json), bumps seq on every change, and serves it at
GET /api/access/orgs/<handle>/revocations (public — it is org-public
data; an empty seq-0 list is signed lazily on first read).
Subject entries use one canonical form at both ends: certificate fingerprints (peer daemons) fold to lowercase separator-free hex when the list is written and whenever it is compared — apply, materialization, and renewal all canonicalize both sides — so an uppercase or colon-separated spelling still revokes, including entries persisted by older builds. base64url client-key fingerprints are case-sensitive and only trimmed.
Delivery is carried, not discovered. A consumer daemon has no
address for “the org daemon” — there is no membership server to ask — so
the list travels the same way grant documents do: anyone may push it to
POST /api/access/orgs/revocations/apply (doorbell class:
unauthenticated, rate-limited, size-capped — the caller gets no authority;
the locally pinned org signature authorizes only application of the signed
revocation facts, and replaying an old list is refused by seq). Two couriers
exist today: the org admin’s browser publishes the list to the
rendezvous bulletin board (blind storage — signature-checked, with
monotonic writes). The board cannot forge a newer list and a daemon refuses a
sequence below its local high-water mark, but the board can withhold the latest
list or serve an older signed list to a fresh daemon that has no sequence
history. Every member’s browser fetches the board’s copy and hands it to each
daemon it visits.
Peer-link gossip can come later; with browsers as couriers, revocations
already reach any daemon a member still talks to.
Application. A daemon that trusts the org verifies the signature
against its trusted key for that handle, requires seq strictly greater
than the last applied (equal is an idempotent no-op), then persists the
lists and the new seq on its trusted_orgs entry and revokes every
materialized grant whose document grant_id, subject fingerprint, or
issuer key is listed. Persisting matters beyond the sweep:
materialization and renewal both check the stored lists, so a revoked
grant_id, subject, or issuer key is refused future presentation too —
combined with the no-resurrection rule above, an ORL revocation sticks
even against a member who still holds a validly signed document.
Renewal. A member (or anyone carrying the document) presents a
still-valid document to the org daemon —
POST /api/access/org-grants/renew, doorbell class — and receives a
freshly signed copy: same subject, role, targets, and the same
grant_id, with issued_at set to now and the original lifetime span
preserved (capped at 90 days). Keeping grant_id stable is deliberate:
ORL revocation by grant_id keeps working across renewals, and the
document’s identity is the grant, not the signature instance. The org
daemon refuses renewal for anything its own ORL lists (by grant_id or
subject) — expiry then retires the member within the document’s remaining
lifetime. Only the daemon holding the org root key can renew, and renewal
grants nothing a fresh issue would not; it exists so membership can be
kept short-lived without the issuer in the loop. The unauthenticated courier
is not logged in by this call and cannot exercise the renewed subject’s grant.
Organization-grant implementation status
- ✅ Grant expiry in the IAM schema, evaluator, and UI.
- ✅ Org identity +
intendant org init+trusted_orgs+ Access → Advanced → Organizations (trust / revoke / issue). - ✅ Document format, verification against trusted org keys,
materialization into local IAM, and paste-to-join UI. The public
presentation endpoint is
POST /api/access/org-grants(doorbell class: unauthenticated, rate-limited, 16 KiB cap — the signed document authorizes only subject-bound verification/materialization, never the courier’s session); trust/revoke/issue requireaccess.manage. - ⚠️ Human client-key presentation machinery is staged: browsers can store and
present a document from trusted daemon-origin code, and daemons can verify
and materialize it idempotently without resurrecting local revocations, but
no alpha ingress admits the document’s browser key as its controlling IAM
principal. It therefore creates record state, not a usable human login. The
hosted rendezvous path is disabled and cannot exercise a stored document.
Peer subjects are usable through peer mTLS. E2E:
scripts/validate-org-grants.cjs. - ✅ Revocation list + renewal: root-signed cumulative ORL maintained on
the org daemon (
orl.json, served publicly), carried to consumers via the public apply doorbell, enforced monotonically and persisted so listed grant ids/subjects are refused at materialization and renewal; renewal re-signs a still-valid document with the samegrant_idand its original lifetime span. UI: revoke-member / copy-list / apply-list / renew flows under Access → Advanced → Organizations. Peer-link gossip and periodic pull remain for later plumbing. - ✅ Peer-daemon subjects and issuer-key delegation, per the design
below and its sign-off decisions: fail-closed
max_peer_profile, explicit presentation only (no peer-doorbell ride-along in v1.1), and chain-only issuer certificates (deputies initialize a key, the root delegates, documents carry the certificate; revocation lists revoke issuers wholesale via recordedissued_via). Renewal stays root-only for now. E2E:scripts/validate-org-grants.cjsscenarios 4–5.
Peer subjects and issuer-key delegation
Peer-daemon subjects. An org grant whose subject is a peer daemon
materializes into the peer identity store
(access/access_policy.rs::PeerIdentityRecord), not IAM — daemons are
peers, never people, and the peer lane’s profile vocabulary is the right
authority language for them. Format: subject carries
peer_fingerprint instead of client_key_fingerprint (exactly one must
be present), and the signing payload’s subject-kind line — the literal
client_key today, deliberately baked into every existing signature —
becomes peer_daemon, so a signature can never be replayed across
subject kinds. role_id uses a peer:<profile> namespace (e.g.
peer:session-reader) so a peer document cannot be confused with a
human-role document even outside the payload.
Materialization upserts an approved PeerIdentityRecord bound to the
fingerprint. Two prerequisite schema steps, each valuable alone (the
same pattern as grant expiry in step 1): the record gains
expires_at_unix (org documents require expiry; enforcement treats an
expired record as revoked) and source (org:<handle> provenance, so
org revocation can sweep records it created and the UI can say where an
identity came from). The org’s cap for the peer lane is a separate
max_peer_profile on the trusted-org entry. It is empty by default, so
trusting an org grants no peer authority until the owner sets a peer cap.
The cap relation is operation-set containment, not a strict ladder:
file-oriented and session-oriented profiles can be siblings, and a
document fits only when its profile allows no operation outside the cap.
Over-cap documents are rejected, not downgraded, like the human lane.
Local rules carry over verbatim: locally revoked records are never
resurrected, re-presentation is idempotent-quiet, and ORL
revoked_subjects matches peer fingerprints exactly as it matches client
keys.
Issuer-key delegation. Day-to-day signing moves off the root: the root signs a delegation certificate
{
"v": 1, "kind": "org-issuer",
"org": { "handle": "acme", "root_key": "…" },
"issuer_key": "<ed25519 b64u>", "label": "…",
"issued_at_unix_ms": 0,
"expires_at_unix_ms": 0, // REQUIRED; suggested cap 365d
"max_role": "role:operator", // optional role:* or peer:* scope; "" allows both
"sig": "<root, newline payload>"
}
and a document may then carry chain: [<issuer-cert>] (the array
reserved since v1) with its own sig made by the issuer key.
Verification walks outside-in: the trusted root key validates the
cert, the cert must be unexpired, then the issuer key validates the
document. max_role is a scoped string despite the historic field name:
role:* scopes sign only human-subject documents, peer:* scopes sign
only peer-subject documents, and an empty scope allows both lanes. A
peer:* scope is enforced during verification by operation-set
containment; a role:* scope refuses peer documents during verification
and enforces human role permission containment during materialization,
where the receiving daemon’s local IAM catalog exists. Everything else —
org cap, targets, expiry, ORL — applies unchanged. One level only: an
issuer cannot mint issuers.
Revoking an issuer revokes everything it signed going forward: the ORL
gains a revoked_issuer_keys list, which adds a line to the ORL signing
payload. The v1 payload was extended before external compatibility was
promised. Any future incompatible change must use an explicit v: 2 with
dual-version acceptance. Materialized grants are swept by
matching the chain recorded at materialization time — which means the
materialization audit/grant record starts persisting the issuer key it
accepted.
Decisions (signed off 2026-07-02). (a) Explicit presentation only in
v1.1 — a peer-doorbell ride-along would bypass the peer approval queue
and needs its own design pass; (b) fail-closed: max_peer_profile is
empty by default and trusting an org grants no daemon-to-daemon
authority until the owner raises it — the two lanes are separate trust
decisions; (c) chain-only issuer certs — a published list adds no
verification value; documents stay self-contained.
Mechanisms
The pieces that implement the model, mapped to the codebase:
- Daemon-local IAM (
<access-cert-dir>/iam.json, with the platform location described above): principals (browser certificate, client key, metadata-only Connect account records, human user, peer daemon, exact hosted lease), grants (principal → role on this daemon), roles over the daemon permission catalog defined inaccess/iam.rs. Implemented; the source of all authority. Connect account records remain compatibility and audit vocabulary, not an authenticating binding. Hosted lease roles are compiled presets rather than persisted permission lists. - End-to-end tunnel binding: trusted direct dashboard-control offers are answered with a daemon-signed binding over (daemon public key, session grant, client nonce); the browser verifies before trusting the channel. Implemented for daemon-origin control. Fleet-SNI and hosted Connect signaling are rejected before a grant can be constructed. The retired Connect signaling format used the same binding, but the current service rejects those calls and current daemons refuse legacy events before opening a channel.
- Client identity keys (staged, not alpha authentication): browser-held
WebCrypto P-256 keys sign fleet records and may appear as peer-relay
attribution. Verification and IAM record vocabulary for key-signed offers
exist from the retired hosted design, but local
/ws, direct/connect/dashboard/offer, and the reserved future native-bridge code do not consume that proof. A stored key grant therefore grants no usable alpha ingress; active browser control authenticates with loopback presence or mTLS. - Ambient hosted refusal and explicit lease authority: a Connect account
assertion is route metadata and never authenticates to the daemon.
Daemon-stamped route provenance prevents a directly enrolled key from
shedding hosted policy merely because its stored origin looks trusted. Both
compatibility ceiling entries are normalized to
role:noneon every load; missing, empty, or hand-edited values also resolve torole:none. Connect’s retired browser offer/ICE/close calls remain refused at both ends, and no browser-key grant can authorize that path. Separately, the off-by-default hosted-control lane lets a fleet-origin tab request a bounded lease. Only a local console, direct-mTLS owner dashboard, or enrolled qualifying signed app can approve it. The daemon then mints an exacthosted_leaseprincipal; fresh request proofs and one-use WebSocket tickets exercise only the compiled preset. Generic IAM APIs cannot create, assign, or reactivate hosted principals, grants, or roles, and the lane’s dedicated ceiling ceremony cannot lift its immutable floor. The optional user-owned custom-domain lane is a separate TLS provenance class: exact SNI, Host, Origin, and WebAuthn rp_id agree on the configured name. Its passkey ceremony approves only the signed tab request and mints the same bounded lease; it creates no ambient IAM or root principal. The local IAM write boundary also refuses an active pure browser-key grant whose recorded origin is hosted, under the currently learned fleet zone, or an exact fleet name the daemon learned previously. Fleet-name provenance is retained infleet-origin-provenance.jsonbeside the access certificates so offline or Connect-disabled startup cannot reclassify an old service-named route as a direct anchor. When Connect is enabled, custom-name control also waits for the current registration’s fleet-zone observation before opening. On upgrade, an olderfleet-cert.pembackfills both canonical exact DNS SANs and their derived fleet zones before the gateway accepts requests. If that provenance is malformed or cannot be recovered completely, unknown DNS browser-key origins fail closed as fleet provenance until the local authority store is repaired. Ahuman_userlabel is not a bypass. A valid independently verified browser mTLS binding may carry that key as metadata. Legacy browser-key records are displayed as inactive bindings withenforced: falseandauthority: none, and cannot be reactivated through the grant lifecycle API. Upgrading only Connect cannot tear down a legacy P2P session that is already established; complete the migration by restarting upgraded daemons, closing old Connect tabs, and allowing the IAM migration to revoke legacyconnect-bootstrapgrants. - Legacy hosted-root and
--ownermigration: IAM schema v2 identifies principals whose client-key authentication origin is the retiredconnect-bootstrapsentinel, revokes every active grant on them, and recordsrevoke_legacy_connect_bootstrap. It separately revokes active browser-key grants whose recorded reason came from the retired CLI--ownerbootstrap and recordsrevoke_legacy_owner_browser_key_bootstrap. Both compatibility ceilings return torole:none; direct/mTLS root grants survive. Existing Connect account/route links are metadata outside IAM and remain linked. Remove--ownerfrom old service commands (the parser now rejects it), restart the upgraded daemon, close old Connect tabs, and runintendant access setuplocally to establish the generated owner mTLS credential. - Device enrollment (staged UI/state): the daemon retains a bounded pending
enrollment queue and decision/upsert scaffolding, but the default product has
no production writer for that queue. The alpha local/direct-mTLS transports do
not present browser-key authentication, and Connect/fleet events are refused
before enrollment, so the GET capability reports
status: staged,writer_available: false, and an empty queue during ordinary operation. Test fixtures can still exercise the decision plumbing. Remote alpha enrollment uses mTLS certificate setup instead; a claim never substitutes for trusted approval. - Encrypted fleet sync: the private fields of a synced fleet record
(daemon URLs) are sealed with an AES-GCM key derived from the account
passkey via the WebAuthn PRF extension — bytes the browser evaluates
locally and does not send in the protocol. Connect still controls that
browser’s JavaScript and can prompt for verification and exfiltrate the PRF
output or decrypted state while the page has it. Passkeys sync across the user’s
devices, so each device derives the same key; the hosted store holds
ciphertext (
enc_fields, signed as stored — fleet-record payload v3), and a device without the key still verifies the record and simply shows it locked. No PRF support degrades to plaintext-free sync of the public fields only. - Grant fanout: the anchor-served Access page applies one grant across
many daemons — an “Apply to” step in the grant flow calls each selected
daemon’s independently verified direct-mTLS IAM API (browser mTLS,
cross-origin), with
per-daemon results; every target authorizes independently and no central
grant store exists. Cross-origin access to the six fleet Access APIs is
gated by a per-daemon origin allowlist (itself, the macOS app scheme, its
outbound peer routes, its approved inbound identities) that both drives
the CORS echo and refuses state-changing requests from any other page —
closing the hole where a cert-installed browser could be steered by an
arbitrary website. This posture is daemon-wide: no authority-bearing API
response is wildcard-readable and foreign-origin
/api/*requests are refused./configis not public bootstrap: it requirespresence.read, accepts only the daemon’s own or a fleet-allowlisted Origin, and isCache-Control: no-storebecause its ICE material may contain credentials. Fleet-name SNI is rejected even when CORS would otherwise allow the caller. Only authority-free bytes such as the agent card,/connect/bootstrap,/connect/status, and the peer doorbell remain public. Authority- bearing/connect/dashboard/*signaling and/wsapply the same own/app- origin gate before resolving local or mTLS authority; cleartext own-origin access is loopback-only to resist DNS rebinding. - Signed fleet sync: fleet records that round-trip the hosted metadata store are signed by the pushing browser’s identity key (over host id, label, and route URLs) and verified on read; the Access UI badges each synced record as verified-by-this-browser, signed-by-another-device, unverified, or hosted-claim. The signing public key travels inside the record and there is no owner/device trust set yet. The current browser can therefore detect alteration under its own key, but a malicious store can replace a record with a newly generated, internally valid self-signed one on another device. Connect-served code can also wield that origin’s browser key while loaded. These signatures are integrity/attribution hints, not daemon authentication and not protection from a malicious hosted page or store. Private route fields are also encrypted across devices with the passkey-PRF-derived key described above; that protects stored ciphertext, not plaintext exposed to the currently running hosted page.
- MCP principal binding:
/mcprequests (supervised backends,intendant ctl, the dashboard’s tool RPC) enter the same evaluator as every other surface. Supervised agents authenticate with a session-scoped token derived from the daemon’s per-process secret, binding them toprincipal:agent-session:<id>; tokenless loopback callers bind toprincipal:local-process:loopback; browser pages must present the daemon’s own origin. Each tool call is checked against a per-tool permission map at call time, soagent_session/local_processgrants scope what a given supervised agent or local shell may reach. Defaults stay root-compatible on a single-user daemon; once any agent session has ever been scoped, the tokenless loopback default fails closed until alocal_processgrant states what bare local callers get, and a lapsed grant (expired or revoked) denies rather than restoring default trust. See MCP Server. - Org root keys: membership and role assertions signed by the org key; each daemon explicitly pins the org root it trusts and verifies signatures locally. Handles are labels, not a global authority directory. Connect’s append-only transparency log can make published metadata auditable, but it cannot add or replace an org root in daemon-local trust.
Prior art
The pattern is proven elsewhere; we are assembling, not inventing:
- Tailscale tailnet lock — coordination server distributes node keys but cannot mint them; signatures chain to user-held keys. Our rendezvous + client keys is the same demotion.
- Keybase — server as untrusted directory over user sigchains; key-first identity with names as labels.
- Matrix — the client trusts its homeserver; federation carries verifiable events between sovereign servers. Our anchor daemon is the homeserver role.
- Certificate Transparency — public evidence of certificate issuance and a useful route diagnostic, not a substitute for an authority anchor.
- SPKI/SDSI and petnames — authority bound to keys; human names are local, contextual labels.
Loopback trust vs. the runtime sandbox
Local presence makes loopback a root-capable surface — deliberate, and
documented above — but since the per-boot admission token shipped,
reachability alone is no longer the credential. Every daemon boot mints
a random loopback admission token and writes it to
<state root>/loopback-tokens/<port>.token (0600; one file per instance
port, so concurrent daemons on a home never clobber; rotates every boot;
removed with the instance’s other per-boot state on the next boot of that
port). Every owner-posture loopback surface — the dashboard HTTP API,
/ws, dashboard-control datachannel signaling, and the local_process
lane of /mcp — refuses tokenless loopback with a named error that says
exactly which file a local owner process reads (or that
INTENDANT_LOOPBACK_TOKEN overrides it). Authority-free bytes (the SPA
shell, /.well-known/agent-card.json) stay serveable; nothing that
mints owner authority is reachable without the token. The gate is
default-ON for every daemon posture — dev and QA builds, --no-tls
included — with no opt-out flag: a shared home’s effective security is
its weakest daemon’s posture, and the one live escape observed (a
session refused by an mTLS front door scavenging a permissive plain-HTTP
QA daemon on the same home) was exactly a weakest-daemon bypass.
The token turns “can reach 127.0.0.1” into “can read the owner’s state
root”. On every platform that ends tokenless drive-by — including
cross-uid drive-by, since TCP loopback is not uid-scoped and the
trusted-local lane previously admitted any local user — and ends the
scavenge-a-permissive-daemon bypass. Owner surfaces present the token
automatically: the boot prints a tokened dashboard URL to the
controlling terminal only (deliberately never through stdout/stderr,
which the daemon log tee mirrors into the sandbox-readable logs/
subtree), intendant ctl discovers it with zero friction
(INTENDANT_LOOPBACK_TOKEN env override, else the per-port file;
supervised sessions keep their injected, session-scoped mcp_token lane
instead and never self-escalate by reading the file), intendant ctl dashboard-url prints the owner URL for service-mode daemons, and the
dashboard stores the token from its opened URL, strips it from the
address bar, and attaches it to every same-origin request. The token
authenticates admission to the existing loopback posture — admitted
callers mint exactly the principals they always did (local_process,
the trusted-local dashboard lane); there is no new principal class and
no IAM vocabulary change. It is likewise independent of the /mcp
token ladder: mcp_token credentials keep working unchanged, and the
dedicated session-MCP listener’s single-rung session-token ladder is
untouched.
Transport interaction with the TLS posture (ratified 2026-07-20): a
TLS-enforcing daemon still refuses cleartext with 426 Upgrade Required, through one shared admission predicate whose only exceptions
are host-local and credentialed — the token-gated loopback /mcp lane
supervised backends bootstrap through, and (the ruling’s widening)
loopback requests presenting the per-boot admission token. That admits
exactly the CLI-class owner clients (ctl, rigs) whose traffic never
leaves the host and who today hold no TLS client identity; browsers
never qualify (browser-origin markers are excluded, so they keep the
426 and the printed https URL), remote surfaces are unchanged, and the
dedicated session-MCP listener stays exactly its own /mcp ladder —
the owner token never widens it. ctl-over-HTTPS remains deliberately
unbuilt even now that local key custody has shipped (the
credential-custody chapter’s “Local key custody” section): the ruled
posture keeps ctl loopback-token local, and any future HTTPS ctl lane
retrieves its custody-held client identity interactively — the
keychain prompt is the ceremony — rather than minting a silent second
authorized binary. That keeps this paragraph’s promise honest instead
of quietly widening the owner surface.
The honest security envelope, per platform:
- macOS — strong. The default (sandbox-on) runtime profile denies
network egress to the daemon’s own gateway port (a prompt-injected
shell cannot even reach the surface), and the Seatbelt credential
clause denies reads of
loopback-tokens/alongside the trust store — so the token really is scoped to owner processes.--no-sandboxlifts both with the rest of the confinement. - Linux and Windows — a real raise, not airtight. Neither sandbox can express a single-port deny (Landlock’s network rules are allowlist-only; restricted tokens do not filter loopback) nor a read deny under a granted tree, so a sandboxed shell that can read the home can read the token. The token still ends tokenless drive-by and the weakest-daemon scavenge on these platforms. The local key-custody migration has since shipped for macOS (opt-in, keychain-wrapped — see the credential-custody chapter), and by its own binding label it is bar-raising, not lane-sealing: airtight same-uid separation still waits on a signed hardened-runtime binary plus the uid-separation work, and Linux/Windows custody backends are unbuilt, so this limit stands accepted on all three platforms.
- Same-uid unsandboxed processes read the token by design. That is the unchanged owner surface: an owner-launched script or unsupervised agent on the same account has always been, and remains, the owner.
Multi-daemon homes get a same-home handoff: an owner-posture caller on
one daemon may ask GET /api/local-daemons/tokens for the per-instance
tokens that daemon’s own state root holds (files it could already
read), so the trusted dashboard opens same-home siblings with their own
tokened URLs. The route is credential-custody gated, answers
per-request without persisting anything, never crosses a home boundary,
and retires naturally when the fleet-identity arc supersedes it.
The same asymmetry applies to the
state-root secrets: every platform’s default grant set excludes the trust
store (access-certs/), leased auth, and the custody trail from runtime writes,
but a project root or operator-supplied extra_write_paths that overlaps those
locations can reopen them. Only macOS can additionally deny reads of the
credential locations at the OS-policy layer.
Alpha implementation status
The alpha keeps loopback and direct mTLS first-class while separating shipped authentication from staged identity vocabulary:
- Trusted anchors are shipped — local presence and direct mTLS are the
root-capable daemon-control and lease-confirmation entry points. The packaged macOS app has a
loopback mTLS bridge in source, but no Developer ID-signed/notarized release
has been published for this alpha. The release pipeline labels builds
-unsigned-devuntil signing credentials are provisioned; those artifacts are not distribution anchors. - Ambient hosted refusal, migration, and bounded leases are shipped — raw
hosted provenance is fixed at
role:none; retired Connect signaling is refused at both ends; the IAM migration revokes grants created by the retired Connect bootstrap and--ownerbrowser-key bootstrap. The separate hosted-control switch defaults off and admits only an approved, expiringhosted_leasethrough exact route/method/frame/action projections. - Client identity keys are metadata-only in this alpha — they sign fleet records and remain useful subject vocabulary. Peer offers can verify one for attribution, but no live alpha ingress admits it as the controlling IAM principal. Device enrollment UI/state is therefore staged, not a replacement for mTLS.
- Local grant fanout, signed and encrypted fleet sync, org-signed grant and revocation documents, self-hostable Connect, and transparency-log records are implemented — every daemon still makes its own trust and authorization decision.
- General browser-key login remains future work — it cannot reactivate the
retired Connect path or broaden the dedicated hosted-lease principal. The
signed-application witness/anchor protocol is implemented, but the current
-unsigned-devartifact is deliberately ineligible; operational signed-application confirmation still requires a qualifying signed/notarized distribution and platform-keystore enrollment.