Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion public/connect/schemas/connect-protocol.v1.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"grantScope": {
"type": "object",
"additionalProperties": false,
"required": ["contracts"],
"required": ["access", "contracts"],
"properties": {
"access": { "enum": ["contract", "full_collection"] },
"contracts": {
Expand Down
2 changes: 1 addition & 1 deletion src/data/conformance.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"generated_at": "2026-07-25",
"generated_at": "2026-07-26",
"spec_version": "0.3.0",
"implementations": [
{
Expand Down
12 changes: 6 additions & 6 deletions src/pages/sdk/manifest/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ const portableManifest = `{
</p>
<CodeBlock code={portableManifest} language="json" label="Portable manifest example" />
<p>
Portable manifests use key-bound short-code authorization, accept local
collections only, and keep their credentials in memory when opened from
{" "}<code>file://</code>. See the
Portable manifests use key-bound short-code authorization, can accept local
or hosted collections, and keep their credentials in memory when opened
from <code>file://</code>. See the
{" "}<a href="/sdk/portable-apps/">portable HTML application guide</a>.
</p>

Expand Down Expand Up @@ -155,15 +155,15 @@ const portableManifest = `{
</tr>
<tr>
<td><code>collection_kind: "hosted"</code></td>
<td>Offer only provider-backed collections; unavailable to portable files</td>
<td>Offer only provider-backed collections, including to portable files</td>
</tr>
</tbody>
</table>
<div class="callout">
<strong>Leave collection_kind out when either authority is acceptable.</strong>
<p>
The SDK selects direct or relay transport automatically within a local
grant.
The SDK selects direct, relay, or hosted transport automatically after
approval.
</p>
</div>

Expand Down
42 changes: 30 additions & 12 deletions src/pages/sdk/portable-apps/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const manifest = `{

const browserBundle = `<script
src="https://cdn.jsdelivr.net/npm/@mdbase/connect@0.1.0-beta.1/dist/browser/mdbase-connect.min.js"
integrity="sha384-6GTn5SRbBhjL6mSvjFUYGC+h7EV/Scj7NzxwbbmCFPWip8vc11F3EiZsOshUvLeP"
integrity="sha384-PD+lazz69NHRGmB9iNPltd6StRK0fJ5hockUI2jyoYwSu3ti0AnRQNrjWMXlHs2D"
crossorigin="anonymous"></script>`;

const application = `<button id="connect">Connect a collection</button>
Expand Down Expand Up @@ -95,18 +95,20 @@ const application = `<button id="connect">Connect a collection</button>
<p>
Connect identifies the normalized manifest by its SHA-256 digest. Changing
any declared field creates a different application identity and requires a
new approval. Portable applications currently authorize computer-owned
collections only. A manifest that requests
{" "}<code>collection_kind: "hosted"</code> is rejected.
new approval. Portable applications can authorize local or hosted
collections through the same connection API. Add
{" "}<code>collection_kind: "hosted"</code> when the app specifically needs
a durable provider-backed collection; omit it to offer every compatible
authority.
</p>

<h2 id="authorization">Authorize with a short code and installation key</h2>
<ol>
<li>The file registers its inline manifest and generates a P-256 relay key.</li>
<li>The file registers its inline manifest and generates an ephemeral P-256 key so a local collection remains available as a choice.</li>
<li>Connect returns a short-lived device code and an eight-character user code.</li>
<li>The SDK opens the Connect approval page in a popup and reports the code through <code>onDeviceCode</code>.</li>
<li>The signed-in user confirms the code, reviews the downloaded-file warning, chooses a local collection, and narrows the operations.</li>
<li>The SDK polls at the server-provided interval and accepts only the grant bound to its application key.</li>
<li>The signed-in user confirms the code, reviews the downloaded-file warning, chooses a compatible local or hosted collection, and narrows the operations.</li>
<li>The SDK polls at the server-provided interval and accepts only an exact opaque-origin grant: key-bound encrypted relay for local access, or a scoped provider capability for hosted access.</li>
</ol>
<p>
Device codes expire after ten minutes, are stored as hashes by the control
Expand All @@ -125,9 +127,11 @@ const application = `<button id="connect">Connect a collection</button>
<p>
Browsers serialize local-file origins as <code>null</code>. For a portable
manifest opened from <code>file://</code>, the SDK stores the authorization
token and non-extractable private key in process memory. Another downloaded
file cannot inherit them through <code>localStorage</code> or IndexedDB.
Reloading or reopening the file requires authorization again.
token and non-extractable private key in process memory. The key is
discarded after a hosted collection is selected. Another downloaded file
cannot inherit the refresh credential or hosted capability through
{" "}<code>localStorage</code> or IndexedDB. Reloading or reopening the file
requires authorization again.
</p>
<p>
An embedding shell can inject custom <code>storage</code> and
Expand Down Expand Up @@ -162,14 +166,28 @@ dist/browser/mdbase-connect.min.js.sha384`}

<h2 id="example">Connect from one HTML file</h2>
<CodeBlock code={application} language="html" label="portable-notes.html" />
<p>
Application code does not choose a transport. The returned
{" "}<code>MdbaseConnection</code> uses direct, relay, or hosted access as
required while <code>describe()</code>, <code>query()</code>,
{" "}<code>create()</code>, and the other operation methods stay the same.
Read <code>connection.route</code> only for diagnostics or status UI.
</p>

<h2 id="security">Keep the application page inside the grant boundary</h2>
<p>
The local connector is the final authorization boundary. It accepts
For local access, the connector is the final authorization boundary. It accepts
{" "}<code>Origin: null</code> only for an active portable grant with the exact
encrypted relay binding. Every operation authenticates the grant,
application, connector, collection, key ID, epoch, counter, request ID, and
ciphertext. CORS permission alone does not authorize an operation.
ciphertext.
</p>
<p>
For hosted access, the provider receives a short-lived bearer capability
limited to one replica, collection, grant, operation set, record scope,
expiry, and the exact <code>Origin: null</code> value. Refresh rotates both
the Connect credential and provider capability. CORS permission alone does
not authorize an operation.
</p>
<p>
Code already running inside an approved page can use that page's
Expand Down
11 changes: 6 additions & 5 deletions src/pages/sdk/routing/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,13 @@ const options = `const mdbase = new MdbaseConnect({
a trusted data processor.
</p>

<h2 id="portable">Portable files use local authority only</h2>
<h2 id="portable">Portable files use the same route-neutral connection</h2>
<p>
A downloaded file uses the same direct and encrypted-relay routes after its
short-code approval. Portable manifests cannot request a hosted collection.
This restriction keeps hosted renewal capabilities out of an opaque browser
origin until they have an equivalent sender constraint.
A downloaded file can use direct, encrypted-relay, or hosted-provider access
after its short-code approval. For hosted access, its short-lived capability
is bound to the exact grant, collection, scope, operations, expiry, and
opaque <code>Origin: null</code>, and remains in process memory. The
consuming operation API is identical across all three routes.
</p>

<h2 id="native">Native shells</h2>
Expand Down
11 changes: 7 additions & 4 deletions src/pages/sdk/security/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ import DocsLayout from "../../../components/DocsLayout.astro";
<h2 id="portable">Treat downloaded application code as authorized code</h2>
<p>
Every local file has the opaque browser origin <code>null</code>. Connect
does not trust that origin by itself. The connector accepts it only when
the request matches an active portable grant, the P-256 application key,
and the encrypted relay envelope.
does not trust that origin by itself. For a local grant, the connector also
requires the P-256 application key and encrypted relay envelope. For a
hosted grant, the provider requires a short-lived bearer capability scoped
to one grant, collection, operation set, record scope, expiry, and that
exact opaque origin. Both refresh credentials and provider capabilities
remain in process memory for <code>file://</code>.
</p>
<p>
Code already running in an approved page has that page's access. Keep
Expand Down Expand Up @@ -91,7 +94,7 @@ import DocsLayout from "../../../components/DocsLayout.astro";
<h2 id="checklist">Application release checklist</h2>
<ul>
<li>Validate the production manifest with default settings and omit <code>--allow-local</code>.</li>
<li>For portable files, test popup blocking, expiry, cancellation, reload, and a second unrelated local file.</li>
<li>For portable files, test popup blocking, expiry, cancellation, reload, a second unrelated local file, and both local and hosted collection routes.</li>
<li>Pin portable CDN assets to exact versions and verify their SRI values.</li>
<li>Test narrowing, pause, revocation, token rotation, and reauthorization.</li>
<li>Test direct-to-relay failover and verify one durable mutation outcome.</li>
Expand Down