Home / Demos / Vault app embed
A vault app, live inside this page
Below is an application running out of an encrypted vault, embedded in this page with nothing but a published read-only key. This walkthrough is the complete process that produced it — every command, the key that is deliberately public, and the one that never will be.
index.html is booted inside a sandboxed iframe with a window.sg bridge — the same shape SG/Vault's own vault-in-vault embedding uses. No copy of the app or its content exists on sgit.ai.The live embed
Click to open it. The status line inside the app will read content: sg.vfs.readText("content.json") — live from the vault, which is the bridge proving itself: the app asked the embed host for a vault file and got it.
How this vault was made, end to end
1 — Write the app. A vault app is one self-contained index.html — CSS and JS inlined, content in content.json, generative SVG instead of image files — plus an app.json that auto-launches it. It follows the authoring contract: no <link href>, <script src> or <img src> against vault paths, content read via sg.vfs.readText with an inlined fallback, and sg-app-ready posted when rendered.
demo-vault-gallery/ ├── index.html # the app — one file, contract-compliant ├── content.json # the editable content the app reads over the bridge ├── app.json # { "entry": "index.html", "present": true, "auto_open": true } └── README.md
2 — Make the folder a vault and push it.
$ sgit init --existing . Vault key: <passphrase>:4bshby5n ← saved to a password manager. Not printed here, not ever. $ sgit commit -m "Field Notes v1: app, content, README" && sgit push Push complete. Pushed 1 commit(s), 4 object(s) uploaded.
3 — Derive the read key. It is computed one-way from the vault key: it can decrypt everything in the vault and can never be turned back into write access.
$ sgit dev derive-keys '<vault-key>' read_key: 2848993a68c02a33… ← this one is safe to publish. See below.
4 — Publish the read key. Deliberately, in full, in this page's source:
vault id : 4bshby5n endpoint : https://dev.send.sgraph.ai read key : 2848993a68c02a33ea5582902c391901191e53680d35b36c0e76185d4107ad81
Anyone can take those three lines and read this vault — from the CLI (sgit clone --read-key 2848993a68c0… 4bshby5n), from their own page, from a script. That is the point: read access is a capability you can hand out, without an account, without the host mediating it, and without it ever becoming write access. A write attempt with this key is refused by construction — try it.
5 — Embed it. The host on this page is assets/vault-embed.js, ~170 lines: derive the ref id with HMAC-SHA256, fetch ciphertext over CORS, decrypt with Web Crypto, boot the app in an iframe with sandbox="allow-scripts" (opaque origin — the app gets no cookies, no storage, no reach into this page), and answer its sg.vfs calls over postMessage.
What the app can and cannot do in here
| Capability | In this embed | Why |
|---|---|---|
sg.vfs.readText / sg.vfs.read | works | served by the host from decrypted vault objects |
sg.loadCss / sg.loadJs | works | same read path, injected into the frame |
any mutation (sg.fs.*, sg.vault.*, …) | impossible | the page holds only a read key — there is no write capability to misuse, not even by a bug in the host |
| reach this page, cookies, storage, top navigation | blocked | sandbox="allow-scripts" and an opaque origin; the only channel is postMessage |
Embedding the full SG/Vault UI — the gap closed
The embed above is our minimal host: ~170 lines, enough bridge for a read-only app. The obvious next step was embedding the real SG/Vault interface — App-Mode chrome, or the vault browser with its FILES / SGIT / SETTINGS rail. When we first ran that experiment it worked in every respect but one: the credential. The loader documented a read-only format but the client rejected it, and the CLI's 64hex:vault_id shorthand was parsed as a passphrase, PBKDF2'd, and derived the wrong file ids (we measured abfd8ea0f1a2 where the vault's real ref id is 11ea50e81f4d — hence "Vault not found"). We filed both as asks.
They landed. The deployed loader now detects a read-key credential as format 6 — <64-hex read_key>:<vault_id>, matching what sgit clone already accepted — and, critically, checks it before the passphrase formats, which is exactly the ordering bug we hit. It also strips the sgit CLI's canonical key prefixes before detection, so a key pasted straight from new CLI output is understood — including sgit_rk1_, the read-only one, which is the form we publish. (Its write-credential sibling is the prefix our own build refuses to let onto this site at all: the release validator treats that string as a leak and fails the build, which is why you will not find it written out here.) Re-running the same experiment against the live build:
| Question | Answer | Evidence |
|---|---|---|
| Can the vault UI be iframed at all? | Yes | no X-Frame-Options, no frame-ancestors |
| Does App Mode work inside a cross-origin iframe? | Yes — fully | the complete HUD: toolbar, URL bar, read/write badges |
| Can it open with only the read key? | Yes — now | all three forms parse in the deployed loader (hex:id, sgit_rk1_hex:id, and the legacy id hex); App Mode booted this Field Notes app from the published read key alone, six SVG studies rendered, the app's own status line reading content.json over the bridge |
| Does the vault browser open read-only? | Yes | vault-shell with the FILES / SGIT / SETTINGS rail, the real decrypted tree (4 files, 10.6 KB), and an explicit R1 W0 + Read-only badge in the chrome |
| Is the SGit view reachable? | Yes — but still no deep-link | driving the nav reaches HISTORY / REFS / TREE / BRANCHES / STATUS / REPAIR with both real commits listed; no URL selects a view, so a host page cannot frame the SGit view in isolation. The one ask still open. |
So the credential gap is closed, and the consequence is the point of this whole page: a published read key is now enough to embed the official interface — no account, no token, no write capability anywhere in the chain. And the mechanism got better than the one we first tested: the UI now ships an embed protocol, so the host page hands the key over a validated postMessage handshake instead of a URL fragment. The key never appears in any URL, is never written to the frame's storage (it lives in the frame's memory for the session), the frame proves it is the right recipient before the key is sent, and the host gets structured vault-ready / vault-error events back instead of guessing from load timings.
Below is exactly that — both surfaces, opened with the same published key printed further up this page, loading on click because they pull the full UI from another origin. The area is deliberately wider than this text column, because the vault browser is a full working surface, and there is a full-screen button for vaults with a big UX.
What is still not possible — precisely. The two buttons above select a surface (App Mode or the vault browser). They cannot select a view inside the browser: the SGIT inspector and SETTINGS panels are one click away on the left rail, but the embed message carries only {key, mode, deepLink} where deepLink is a file path — there is no field that names a view, and view switching stays an in-page event. So "open straight onto the commit history" is still one small protocol field away, and the briefing now states exactly that ask: an optional view field on vault-open, applied after mount. Everything else on this page works today.
Honest scope
Two hosts now run on this page, and keeping both is deliberate. The minimal host is ~170 lines and shows the protocol with nothing hidden: derive, fetch, decrypt, answer sg.vfs over postMessage. The official host is the real product — full window.sg surface, permissions, HUD chrome, and now an embed handshake that keeps the key out of URLs and storage. Read the small one to understand the mechanism; use the big one to see what a product built on it feels like. Everything above is live and reproducible from the commands on this page.
| Shape | Evidence status | Copy or reference |
|---|---|---|
| Gallery (app + content) | LIVE — the embed above reads the real vault on every load | Reference — a push to the vault changes this page's embed with no rebuild |
Related
- Building vault apps — the authoring contract this app follows
- A live site whose host cannot read it — the same read path rendering markdown docs
- Sub-vaults — vault-in-vault, the pattern this embed mirrors
- The plan — two more demo vaults (a report, a two-agent inbox) follow this template