Important concepts

Tickets

A ticket is a serializable object that packages the information required for an operation. dev.site passes tickets between the browser, CLI, control plane, and daemon. Each ticket type defines its own use count, lifetime, binding, storage, and revocation rules.

TicketPurposeIssuer and consumerUses and lifetimeBinding, storage, and revocation
dmt_…Enroll one machineControl plane issues it. The CLI and control plane use it.One use. No automatic expiry.It binds the enrolled endpoint after redemption. The control plane stores its hash. Revoke the machine entry.
dsm_…Authenticate one machineThe control plane issues it. The CLI, daemon, and control plane use it.Many uses. No automatic expiry.It is a bearer ticket. Enrollment and daemon registration also need endpoint proof. Private config stores it. The server stores its hash. Revoke the ticket.
dst_…Start one service connectionThe control plane issues it for an authorised browser. The connector redeems it.One use. Two minutes.It names one viewer and service. The control plane stores its hash. Consume it, let it expire, or revoke access.
dss_…Request stream capabilitiesThe control plane issues it. The connector and control plane use it.Many uses. Eight hours, or at most 15 minutes when delegated.It binds to the client endpoint. The connector keeps it in memory and the server stores its hash. Delete, expire, or revoke it.
dsp_…Carry one delegated approvalThe control-plane signing key issues it. A granting client and the control plane use it.One grant per request id. Request and grant expiries limit it.It binds the request id, service, issuer ticket, resource, endpoint, and expiries. The caller stores it. Revoke the issuer ticket or access.
Stream ticket
signed capability
Open one service streamThe control-plane signing key issues it. The daemon uses it.One use. Three minutes.It binds both endpoints, viewer, resource, and permission. The connector holds it in memory. Its nonce prevents reuse.
Browser sessionKeep a user signed inThe control plane issues it after sign-in. The browser and control plane use it.Many uses. Seven days.It is a bearer ticket with no endpoint-key binding. An HttpOnly cookie holds it. The server stores its hash. Log out or let it expire.
Machine enrollment ticket

A signed-in user creates a named dmt_… ticket. The CLI presents it with a public endpoint key and proof from the matching private key. The server atomically consumes the ticket, binds the machine entry to that endpoint, and returns a revocable dsm_… machine ticket. Machine scopes such as service_grants:issue originate on this enrollment ticket.

dmt_… currently has no automatic expiry. Create it when needed and use it promptly. A leak can enroll one endpoint until the ticket is used or revoked, but the ticket cannot be used again after successful enrollment.

Service connection tickets

An authorised browser creates a two-minute dst_… for one viewer and service. devsite connect generates an in-memory client endpoint and redeems that ticket for an eight-hour dss_… session bound to the client key. Each local TCP connection then exchanges the session for a fresh, three-minute, one-use stream ticket bound to both the client and daemon endpoints.

dst_… → dss_… → stream ticket → encrypted service stream

A dss_… is a reusable ticket. It can mint one stream ticket per TCP connection until it expires, is deleted, or loses current authorisation.

Delegated access grant

A requester creates a signed request containing an agr_… id, service keyword, temporary public endpoint, and expiry. A human or automated granting party resolves that keyword only among services its account may access. The server returns a dsp_… plan signed over the exact request, issuer credential, selected resource, endpoint, and expiries.

The plan is an approval ticket. It cannot open a stream. It binds the request id, service, issuer ticket, resource, requester endpoint, and expiries. The control plane verifies the request proof again. Applying the ticket returns a requester-bound dss_… lasting at most 15 minutes. The request id permits one grant. The resulting ticket can serve multiple TCP connections during that window.

Ticket security and storage

Treat complete dmt_…, dsm_…, dst_…, and dss_… values, browser cookies, and live capabilities as secrets. The control plane stores hashes rather than plaintext bearer values. A dsp_… is decodable and cannot connect by itself, but should stay inside the approval workflow.

acct_…, res_…, machine_…, agr_…, handles, and endpoint ids are identifiers rather than tickets. Current access is rechecked when tickets are redeemed and capabilities are requested. Resource, sharing, session, and machine-ticket revocation narrow or terminate later use.


Keys and Endpoint Identities

Ed25519 keys identify Iroh endpoints, prove that a request came from the endpoint it names, and sign control-plane authorization. A public endpoint id is safe to share. Its matching private key is what proves possession.

KeyStorageJob
Machine endpointdevsite-endpoint.keyPersistent enrolled machine and daemon identity
Machine public endpointdevsite-endpoint.pub and server registrationDaemon address and capability audience
Ordinary connectorProcess memory onlyBinds a redeemed ticket and QUIC peer
Delegated requesterA caller-selected file such as requester.keySigns the request and uses the resulting grant
Control-plane signing keyOperator secret or capability_signing.keySigns approval plans and stream capabilities
Pinned server public keyPrivate machine config.jsonDefines which control-plane signatures the daemon trusts
Persistent machine identity

The CLI creates devsite-endpoint.key in the resolved dev.site config directory and preserves it across restarts. On Unix, private files are created with mode 0600. Its public half is written to devsite-endpoint.pub, registered with the control plane, and used as the daemon's Iroh endpoint id.

Version 0.6.0 uses devsite-endpoint.key and devsite-endpoint.pub. It does not read or move identity.key or identity.pub. Start version 0.5.1 once before the upgrade if these legacy files remain. Version 0.5.1 checks only the dev.site config directory. It does not scan another directory or the filesystem.

Connector and requester keys

An ordinary connector's key exists only in memory, so copying its bound session to a different process is insufficient. Delegated access persists a temporary key at the requested --key path. The CLI creates it without overwriting an existing file and keeps it private on Unix. Share request.json, never requester.key. Delete the key after the request and granted connection finish.

Control-plane trust root

The server uses one signing key for dsp_… plans and one-stream capabilities. Production can provide it through DEVSITE_SIGNING_KEY. A local server otherwise keeps capability_signing.key in its state directory. Login pins only its public half. The daemon refuses to start without that pin and rejects artifacts signed by another key.

devsite doctor reports when the server's published public key differs from the pinned value. Losing the private signing key prevents existing daemons from trusting new capabilities. Compromise permits forged authorization. Rotation therefore requires an explicit trust migration rather than a silent file change.

Key compromise boundaries

A machine endpoint key cannot make authenticated API calls without its dsm_…. The bearer ticket cannot satisfy endpoint proofs without the key. Possession of both grants the enrolled machine's full authority and scopes, so revoke the named machine entry and enroll a new endpoint if either pair may be compromised. A temporary client key matters only while a matching session or capability remains live.


ALPN and the service wire protocol

ALPN means Application-Layer Protocol Negotiation. dev.site currently defines one service protocol: devsite/tcp/1. The client requests it and the daemon advertises it during the Iroh QUIC handshake, selecting the wire format before either side interprets an application frame.

CLI ── HTTPS ──► dev.site control plane
 │               session validation + capability issuance
 │
 └── Iroh QUIC, ALPN devsite/tcp/1 ──► owner daemon ── TCP ──► loopback service
               encrypted service bytes

The ALPN does not apply to the HTTPS API, OIDC, MCP adapter, or hosted application's own protocol. PostgreSQL, HTTP, SSH, Redis, and other TCP protocols are opaque bytes after dev.site authorizes the stream.

Service wire sequence
  1. The client opens a bidirectional stream on an authenticated QUIC connection.
  2. It sends a Postcard Connect request containing one signed capability.
  3. The daemon verifies signature, audience, authenticated client key, expiry, permission, resource, and one-use nonce.
  4. The daemon connects to the resource's fixed loopback target.
  5. It returns Connected or a deliberately coarse error.
  6. After Connected, the remainder is the service's raw TCP byte stream.

Request and response frames start with a four-byte little-endian length followed by a Postcard payload. Length is checked before allocation and is capped at 4 MiB. Authorization failures collapse to Denied so a peer cannot distinguish a bad signature from an unknown or unshared resource.

ALPN security scope

Iroh QUIC encrypts the network and authenticates endpoint keys. ALPN separates and versions the application protocol. It does not grant service access: every stream still requires a server-signed capability addressed to the daemon and bound to the authenticated client. Relays carry encrypted QUIC traffic and do not terminate the dev.site application stream. The local daemon-to-service TCP hop uses whatever encryption the service protocol provides.

ALPN version compatibility

The /1 suffix is the compatibility boundary. An incompatible framing or semantic change should use a new ALPN. Today the client requests one dev.site ALPN and the daemon advertises one. There is no protocol fallback or multi-version negotiation. devsite doctor compares the server's reported daemon protocol with the CLI's compiled value.