12<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>dev.site - concepts</title>7<link rel="stylesheet" href="/vendor/pico.min.css">8<link rel="stylesheet" href="/site.css">9</head>10<body>1112<header class="container">13 <nav>14 <ul>15 <li>16 <a href="/" class="contrast brand" aria-label="dev.site home">17 <svg width="24" height="24" viewBox="2 2 20 20" aria-hidden="true"18 xmlns="http://www.w3.org/2000/svg">19 <path d="M16,15,12,7,8,15Zm-1,0L11.5,8"20 style="fill:none;stroke:var(--pico-primary);stroke-linecap:round;stroke-linejoin:round;stroke-width:2"></path>21 <circle cx="12" cy="12" r="9"22 style="fill:none;stroke:var(--devsite-logo-ring, rgb(0,0,0));stroke-linecap:round;stroke-linejoin:round;stroke-width:2"></circle>23 </svg>24 <strong>dev.site</strong>25 </a>26 </li>27 </ul>28 <ul><li><a href="/">Home</a></li></ul>29 </nav>30</header>3132<main class="container">33 <article>34 <hgroup>35 <h1>Important concepts</h1>36 </hgroup>37 <nav aria-label="On this page">38 <ul>39 <li><a href="#tickets">Tickets</a></li>40 <li><a href="#keys">Keys</a></li>41 <li><a href="#protocol">ALPN</a></li>42 </ul>43 </nav>44 </article>4546 <section id="tickets" aria-labelledby="tickets-title">47 <h2 id="tickets-title">Tickets</h2>48 <p>A ticket is a serializable object that packages the information required for an49 operation. dev.site passes tickets between the browser, CLI, control plane, and50 daemon. Each ticket type defines its own use count, lifetime, binding, storage, and51 revocation rules.</p>5253 <figure>54 <table class="concepts-table">55 <thead><tr><th>Ticket</th><th>Purpose</th><th>Issuer and consumer</th><th>Uses and lifetime</th><th>Binding, storage, and revocation</th></tr></thead>56 <tbody>57 <tr><td><code>dmt_…</code></td><td>Enroll one machine</td><td>Control plane issues it. The CLI and control plane use it.</td><td>One use. No automatic expiry.</td><td>It binds the enrolled endpoint after redemption. The control plane stores its hash. Revoke the machine entry.</td></tr>58 <tr><td><code>dsm_…</code></td><td>Authenticate one machine</td><td>The control plane issues it. The CLI, daemon, and control plane use it.</td><td>Many uses. No automatic expiry.</td><td>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.</td></tr>59 <tr><td><code>dst_…</code></td><td>Start one service connection</td><td>The control plane issues it for an authorised browser. The connector redeems it.</td><td>One use. Two minutes.</td><td>It names one viewer and service. The control plane stores its hash. Consume it, let it expire, or revoke access.</td></tr>60 <tr><td><code>dss_…</code></td><td>Request stream capabilities</td><td>The control plane issues it. The connector and control plane use it.</td><td>Many uses. Eight hours, or at most 15 minutes when delegated.</td><td>It binds to the client endpoint. The connector keeps it in memory and the server stores its hash. Delete, expire, or revoke it.</td></tr>61 <tr><td><code>dsp_…</code></td><td>Carry one delegated approval</td><td>The control-plane signing key issues it. A granting client and the control plane use it.</td><td>One grant per request id. Request and grant expiries limit it.</td><td>It binds the request id, service, issuer ticket, resource, endpoint, and expiries. The caller stores it. Revoke the issuer ticket or access.</td></tr>62 <tr><td>Stream ticket<br><small>signed capability</small></td><td>Open one service stream</td><td>The control-plane signing key issues it. The daemon uses it.</td><td>One use. Three minutes.</td><td>It binds both endpoints, viewer, resource, and permission. The connector holds it in memory. Its nonce prevents reuse.</td></tr>63 <tr><td>Browser session</td><td>Keep a user signed in</td><td>The control plane issues it after sign-in. The browser and control plane use it.</td><td>Many uses. Seven days.</td><td>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.</td></tr>64 </tbody>65 </table>66 </figure>6768 <details class="concept-detail">69 <summary>Machine enrollment ticket</summary>70 <p>A signed-in user creates a named <code>dmt_…</code> ticket. The CLI presents it with71 a public endpoint key and proof from the matching private key. The server atomically72 consumes the ticket, binds the machine entry to that endpoint, and returns a revocable73 <code>dsm_…</code> machine ticket. Machine scopes such as74 <code>service_grants:issue</code> originate on this enrollment ticket.</p>75 <p><code>dmt_…</code> currently has no automatic expiry. Create it when needed and use it76 promptly. A leak can enroll one endpoint until the ticket is used or revoked, but the77 ticket cannot be used again after successful enrollment.</p>78 </details>7980 <details class="concept-detail">81 <summary>Service connection tickets</summary>82 <p>An authorised browser creates a two-minute <code>dst_…</code> for one viewer and83 service. <code>devsite connect</code> generates an in-memory client endpoint and84 redeems that ticket for an eight-hour <code>dss_…</code> session bound to the client85 key. Each local TCP connection then exchanges the session for a fresh, three-minute,86 one-use stream ticket bound to both the client and daemon endpoints.</p>87 <pre><code>dst_… → dss_… → stream ticket → encrypted service stream</code></pre>88 <p>A <code>dss_…</code> is a reusable ticket. It can mint one stream ticket per TCP89 connection until it expires, is deleted, or loses current authorisation.</p>90 </details>9192 <details class="concept-detail">93 <summary>Delegated access grant</summary>94 <p>A requester creates a signed request containing an <code>agr_…</code> id, service95 keyword, temporary public endpoint, and expiry. A human or automated granting party96 resolves that keyword only among services its account may access. The server returns97 a <code>dsp_…</code> plan signed over the exact request, issuer credential, selected98 resource, endpoint, and expiries.</p>99 <p>The plan is an approval ticket. It cannot open a stream. It binds the request id,100 service, issuer ticket, resource, requester endpoint, and expiries. The control plane101 verifies the request proof again. Applying the ticket returns a requester-bound102 <code>dss_…</code> lasting at most 15 minutes. The request id permits one grant. The103 resulting ticket can serve multiple TCP connections during that window.</p>104 </details>105106 <details class="concept-detail">107 <summary>Ticket security and storage</summary>108 <p>Treat complete <code>dmt_…</code>, <code>dsm_…</code>, <code>dst_…</code>, and109 <code>dss_…</code> values, browser cookies, and live capabilities as secrets. The110 control plane stores hashes rather than plaintext bearer values. A111 <code>dsp_…</code> is decodable and cannot connect by itself, but should stay inside112 the approval workflow.</p>113 <p><code>acct_…</code>, <code>res_…</code>, <code>machine_…</code>, <code>agr_…</code>,114 handles, and endpoint ids are identifiers rather than tickets. Current access is115 rechecked when tickets are redeemed and capabilities are requested. Resource,116 sharing, session, and machine-ticket revocation narrow or terminate later use.</p>117 </details>118 </section>119120 <hr>121122 <section id="keys" aria-labelledby="keys-title">123 <h2 id="keys-title">Keys and Endpoint Identities</h2>124 <p>Ed25519 keys identify Iroh endpoints, prove that a request came from the endpoint it125 names, and sign control-plane authorization. A public endpoint id is safe to share.126 Its matching private key is what proves possession.</p>127128 <figure>129 <table class="concepts-table">130 <thead><tr><th>Key</th><th>Storage</th><th>Job</th></tr></thead>131 <tbody>132 <tr><td>Machine endpoint</td><td><code>devsite-endpoint.key</code></td><td>Persistent enrolled machine and daemon identity</td></tr>133 <tr><td>Machine public endpoint</td><td><code>devsite-endpoint.pub</code> and server registration</td><td>Daemon address and capability audience</td></tr>134 <tr><td>Ordinary connector</td><td>Process memory only</td><td>Binds a redeemed ticket and QUIC peer</td></tr>135 <tr><td>Delegated requester</td><td>A caller-selected file such as <code>requester.key</code></td><td>Signs the request and uses the resulting grant</td></tr>136 <tr><td>Control-plane signing key</td><td>Operator secret or <code>capability_signing.key</code></td><td>Signs approval plans and stream capabilities</td></tr>137 <tr><td>Pinned server public key</td><td>Private machine <code>config.json</code></td><td>Defines which control-plane signatures the daemon trusts</td></tr>138 </tbody>139 </table>140 </figure>141142 <details class="concept-detail">143 <summary>Persistent machine identity</summary>144 <p>The CLI creates <code>devsite-endpoint.key</code> in the resolved dev.site config145 directory and preserves it across restarts. On Unix, private files are created with146 mode <code>0600</code>. Its public half is written to147 <code>devsite-endpoint.pub</code>, registered with the control plane, and used as the148 daemon's Iroh endpoint id.</p>149 <p>Version 0.6.0 uses <code>devsite-endpoint.key</code> and150 <code>devsite-endpoint.pub</code>. It does not read or move151 <code>identity.key</code> or <code>identity.pub</code>. Start version 0.5.1 once152 before the upgrade if these legacy files remain. Version 0.5.1 checks only the153 dev.site config directory. It does not scan another directory or the filesystem.</p>154 </details>155156 <details class="concept-detail">157 <summary>Connector and requester keys</summary>158 <p>An ordinary connector's key exists only in memory, so copying its bound session to a159 different process is insufficient. Delegated access persists a temporary key at the160 requested <code>--key</code> path. The CLI creates it without overwriting an existing161 file and keeps it private on Unix. Share <code>request.json</code>, never162 <code>requester.key</code>. Delete the key after the request and granted connection163 finish.</p>164 </details>165166 <details class="concept-detail">167 <summary>Control-plane trust root</summary>168 <p>The server uses one signing key for <code>dsp_…</code> plans and one-stream169 capabilities. Production can provide it through <code>DEVSITE_SIGNING_KEY</code>. A170 local server otherwise keeps <code>capability_signing.key</code> in its state171 directory. Login pins only its public half. The daemon refuses to start without that172 pin and rejects artifacts signed by another key.</p>173 <p><code>devsite doctor</code> reports when the server's published public key differs174 from the pinned value. Losing the private signing key prevents existing daemons from175 trusting new capabilities. Compromise permits forged authorization. Rotation176 therefore requires an explicit trust migration rather than a silent file change.</p>177 </details>178179 <details class="concept-detail">180 <summary>Key compromise boundaries</summary>181 <p>A machine endpoint key cannot make authenticated API calls without its182 <code>dsm_…</code>. The bearer ticket cannot satisfy endpoint proofs without the183 key. Possession of both grants the enrolled machine's full authority and scopes, so184 revoke the named machine entry and enroll a new endpoint if either pair may be185 compromised. A temporary client key matters only while a matching session or186 capability remains live.</p>187 </details>188 </section>189190 <hr>191192 <section id="protocol" aria-labelledby="protocol-title">193 <h2 id="protocol-title">ALPN and the service wire protocol</h2>194 <p>ALPN means Application-Layer Protocol Negotiation. dev.site currently defines one195 service protocol: <code>devsite/tcp/1</code>. The client requests it and the daemon196 advertises it during the Iroh QUIC handshake, selecting the wire format before either197 side interprets an application frame.</p>198199 <pre><code>CLI ── HTTPS ──► dev.site control plane200 │ session validation + capability issuance201 │202 └── Iroh QUIC, ALPN devsite/tcp/1 ──► owner daemon ── TCP ──► loopback service203 encrypted service bytes</code></pre>204205 <p>The ALPN does not apply to the HTTPS API, OIDC, MCP adapter, or hosted application's206 own protocol. PostgreSQL, HTTP, SSH, Redis, and other TCP protocols are opaque bytes207 after dev.site authorizes the stream.</p>208209 <details class="concept-detail">210 <summary>Service wire sequence</summary>211 <ol>212 <li>The client opens a bidirectional stream on an authenticated QUIC connection.</li>213 <li>It sends a Postcard <code>Connect</code> request containing one signed capability.</li>214 <li>The daemon verifies signature, audience, authenticated client key, expiry,215 permission, resource, and one-use nonce.</li>216 <li>The daemon connects to the resource's fixed loopback target.</li>217 <li>It returns <code>Connected</code> or a deliberately coarse error.</li>218 <li>After <code>Connected</code>, the remainder is the service's raw TCP byte stream.</li>219 </ol>220 <p>Request and response frames start with a four-byte little-endian length followed by221 a Postcard payload. Length is checked before allocation and is capped at 4 MiB.222 Authorization failures collapse to <code>Denied</code> so a peer cannot distinguish a223 bad signature from an unknown or unshared resource.</p>224 </details>225226 <details class="concept-detail">227 <summary>ALPN security scope</summary>228 <p>Iroh QUIC encrypts the network and authenticates endpoint keys. ALPN separates and229 versions the application protocol. It does not grant service access: every stream230 still requires a server-signed capability addressed to the daemon and bound to the231 authenticated client. Relays carry encrypted QUIC traffic and do not terminate the232 dev.site application stream. The local daemon-to-service TCP hop uses whatever233 encryption the service protocol provides.</p>234 </details>235236 <details class="concept-detail">237 <summary>ALPN version compatibility</summary>238 <p>The <code>/1</code> suffix is the compatibility boundary. An incompatible framing or239 semantic change should use a new ALPN. Today the client requests one dev.site ALPN and240 the daemon advertises one. There is no protocol fallback or multi-version negotiation.241 <code>devsite doctor</code> compares the server's reported daemon protocol with the242 CLI's compiled value.</p>243 </details>244 </section>245</main>246247<footer class="container">248 <nav>249 <ul><li><a href="https://github.com/FelineStateMachine/devsite"250 target="_blank" rel="noopener">GitHub</a></li></ul>251 </nav>252</footer>253254</body>255</html>