{"_id":"@cyberdione/aegis-vault-web","_rev":"3-c928c9370fd8fc2c54cc148311ad2b13","name":"@cyberdione/aegis-vault-web","dist-tags":{"latest":"0.4.1-staging.g1183f6f17d4d","staging":"0.4.1-staging.g1183f6f17d4d"},"versions":{"0.4.1-staging.g1183f6f17d4d":{"name":"@cyberdione/aegis-vault-web","version":"0.4.1-staging.g1183f6f17d4d","keywords":["vault","argon2","ed25519","webauthn","browser","indexeddb","hsm"],"license":"Apache-2.0","_id":"@cyberdione/aegis-vault-web@0.4.1-staging.g1183f6f17d4d","maintainers":[{"name":"birdetta","email":"erica@windisch.us"}],"homepage":"https://github.com/cyberdione/aegis-vault#readme","bugs":{"url":"https://github.com/cyberdione/aegis-vault/issues"},"dist":{"shasum":"71a4d38acbeeb4449768de7d58ee73e5a880b0d4","tarball":"https://registry.npmjs.org/@cyberdione/aegis-vault-web/-/aegis-vault-web-0.4.1-staging.g1183f6f17d4d.tgz","fileCount":52,"integrity":"sha512-BHiZ0gKtsgY6YtOsjpzpUSFf5+iH8Rv1lBh5HDG+faRmRX7M/XkXkgvila7QQZ0lltl8sYgf0Bkz68ZV30NIww==","signatures":[{"sig":"MEMCHzB5d01i1DZNPmgVKe+BuascgVYWmrXjpu+6t9bBo4ACIGtFDOQ0/vAKIwT/4NdVGCBv629QEJzVujmZ2mxtsUfq","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":423179},"main":"./dist/index.js","type":"module","_from":"file:/home/birdetta/projects/cyberdione/.fleet-coord/artifacts/aegis-vault-web-0.4.1-staging.g1183f6f17d4d/cyberdione-aegis-vault-web-0.4.1-staging.g1183f6f17d4d.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./pkg/*":"./pkg/*","./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"},"./types":{"types":"./dist/types.d.ts","import":"./dist/types.js"},"./widget":{"types":"./dist/widget/index.d.ts","import":"./dist/widget/index.js"},"./webauthn":{"types":"./dist/webauthn.d.ts","import":"./dist/webauthn.js"},"./integrity.json":"./dist/integrity.json"},"scripts":{"build":"npm run build:wasm && npm run build:ts && npm run integrity","clean":"rm -rf dist pkg","smoke":"node scripts/smoke-pack.mjs","prepack":"node scripts/assert-prebuilt.mjs","build:ts":"tsc -p tsconfig.json","integrity":"node scripts/integrity.mjs","build:wasm":"bash scripts/build-wasm.sh","prepublishOnly":"npm run build"},"_npmUser":{"name":"birdetta","email":"erica@windisch.us"},"_resolved":"/home/birdetta/projects/cyberdione/.fleet-coord/artifacts/aegis-vault-web-0.4.1-staging.g1183f6f17d4d/cyberdione-aegis-vault-web-0.4.1-staging.g1183f6f17d4d.tgz","_integrity":"sha512-BHiZ0gKtsgY6YtOsjpzpUSFf5+iH8Rv1lBh5HDG+faRmRX7M/XkXkgvila7QQZ0lltl8sYgf0Bkz68ZV30NIww==","repository":{"url":"git+https://github.com/cyberdione/aegis-vault.git","type":"git"},"_npmVersion":"10.9.7","description":"Encrypted browser identity vault — passphrase + WebAuthn-PRF, AES-256-GCM at rest, HSM-shaped per-purpose Ed25519 signing slots. Vanilla core + Web Component widget + optional framework adapters.","directories":{},"_nodeVersion":"22.22.2","publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"react":"^18.2.0","typescript":"^5.4.0","@types/react":"^18.2.0"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aegis-vault-web_0.4.1-staging.g1183f6f17d4d_1785386450800_0.24548470659677046","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-07-30T04:40:50.521Z","modified":"2026-07-30T04:42:53.719Z","0.4.1-staging.g1183f6f17d4d":"2026-07-30T04:40:50.932Z"},"bugs":{"url":"https://github.com/cyberdione/aegis-vault/issues"},"license":"Apache-2.0","homepage":"https://github.com/cyberdione/aegis-vault#readme","keywords":["vault","argon2","ed25519","webauthn","browser","indexeddb","hsm"],"repository":{"url":"git+https://github.com/cyberdione/aegis-vault.git","type":"git"},"description":"Encrypted browser identity vault — passphrase + WebAuthn-PRF, AES-256-GCM at rest, HSM-shaped per-purpose Ed25519 signing slots. Vanilla core + Web Component widget + optional framework adapters.","maintainers":[{"name":"birdetta","email":"erica@windisch.us"}],"readme":"# aegis-vault\n\n> Encrypted browser identity vault. Passphrase + WebAuthn-PRF, AES-256-GCM at rest, HSM-shaped per-purpose Ed25519 signing slots.\n\nA browser-side secret store designed to replace plaintext `localStorage` for OAuth refresh tokens, host configurations, and identity material — and to replace per-session random keypairs with a stable browser identity that persists across reloads. The Ed25519 signing seed lives in WebAssembly linear memory and is **never returned to JavaScript** as raw bytes; consumers obtain opaque `IdentityHandle`s and ask the vault to sign on their behalf.\n\nThe library is a **vanilla TypeScript / ECMAScript library**. It ships with an optional vanilla Web Component widget (`<aegis-vault-modal>`) for the default UI, and an optional thin React adapter for React consumers. Neither is required — you can always drop down to the headless core API and build your own UI.\n\n**Status:** v0.2, API not stable until 1.0.\n\n## Why\n\nModern browser apps that talk to authenticated backend services typically store:\n\n- **OAuth refresh tokens** — long-lived bearer credentials\n- **Per-host config** — endpoint URLs, cert hashes, OAuth client IDs\n- **Per-session signing keys** — frequently regenerated random Ed25519 seeds, one per service\n\nIn `localStorage`, all of these are readable by any browser extension with content-script access and any XSS payload that lands in the tab. The signing keys, when generated fresh per session, also prevent the server from recognizing the same browser across reloads — defeating audit, capability, and rate-limiting based on signer pubkey.\n\nAegis-vault fixes both:\n\n1. **Encryption at rest in IndexedDB**, AES-256-GCM with subkeys derived per page from a passphrase-protected master key (Argon2id + HKDF). Optional WebAuthn PRF as a hardware second factor.\n2. **Persistent identity slots**, derived deterministically per \"purpose\" string from the vault's root seed via HKDF. The hyprstream signing identity, the SSH signing identity, and the git-commit-signing identity are independent keypairs that all derive from the same vault.\n3. **HSM-shaped signing API**: the seed never leaves the wasm module. Consumers receive `IdentityHandle`s and call `handle.sign(canonical_bytes)`. There is no `seed()` or `export_seed()` accessor in the default profile.\n\n## Install\n\n```bash\nnpm install @cyberdione/aegis-vault-web\n```\n\nThe npm package ships **prebuilt** — compiled JavaScript (`dist/`) and the\nwasm-bindgen output (`pkg/`) are included in the tarball. Installing it does\n**not** compile Rust, run `wasm-pack`, `capnproto`, or `tsc`, and there is no\n`prepare`/install hook. Consumers never need a native toolchain.\n\n**Production (`latest`) does not exist yet.** There is no published `latest`\ndist-tag and no production release. A future production release requires a\nseparately reviewed new version + protected `vX.Y.Z` tag; until then, install a\nstaging prerelease for integration only.\n\n```jsonc\n// package.json — when a production release exists, pin exact, never a range:\n// \"@cyberdione/aegis-vault-web\": \"0.4.0\"\n// (no exact production version is published yet)\n```\n\n```bash\n# Pre-release / integration consumption. A maintainer manually dispatches the\n# publish workflow against a reviewed commit, so each staging version maps to\n# one exact source commit. Do not use in production.\nnpm install @cyberdione/aegis-vault-web@staging\n```\n\n| Dist-tag | Status | Source | Provenance | Use |\n| --- | --- | --- | --- | --- |\n| `latest` | **not published** | (future) protected `vX.Y.Z` tag | (future) OIDC | production (pin exact, when it exists) |\n| `staging` — bootstrap | one-time | locally authenticated publish of the reviewed prerelease | **none** (local build) | integration only |\n| `staging` — steady-state | ongoing | manual `workflow_dispatch` from a reviewed `main` commit | OIDC `--provenance` | integration only |\n\n> Git installs (`github:cyberdione/aegis-vault#…`) are **not** a supported\n> consumer path: the published tarball is prebuilt, so the registry package is\n> the distribution channel. For local development against a sibling clone, use\n> `npm link` after running `npm run build` locally (which *does* need the Rust\n> + capnproto toolchain — see [Contributing](#contributing)).\n\n### Verifying a release\n\n> **Provenance caveat (bootstrap).** npm Sigstore provenance can only be\n> produced from a supported CI environment with a configured trusted publisher.\n> The **first** staging publication is a locally authenticated bootstrap and\n> **intentionally has no provenance**; steady-state OIDC provenance\n> (`--provenance` from `publish.yml`) begins only after the package exists on\n> the registry AND the npm trusted publisher + `staging` GitHub environment are\n> configured. The byte-level `dist/integrity.json` manifest below covers every\n> release from the first bootstrap onward.\n\nEvery release carries integrity evidence; steady-state releases additionally\ncarry provenance:\n\n1. **npm provenance** *(steady-state only — see caveat above)* — once OIDC\n   Trusted Publishing is configured, publishes run from CI with `--provenance`\n   (no stored token), attesting each tarball to its source commit and workflow\n   run via Sigstore. Verify with:\n   ```bash\n   npm audit signatures\n   npm view @cyberdione/aegis-vault-web@<version> --json   # dist.provenance field\n   ```\n2. **`dist/integrity.json`** — a SHA-256 manifest of every shipped file **except\n   the manifest itself** (including `package.json` and `LICENSE`). `scripts/` is\n   deliberately not shipped, so run the verifier from the matching reviewed\n   source checkout against the installed/extracted package. It fails on any\n   missing, unexpected, or mismatched payload file:\n   ```bash\n   # from a checkout of the source at the version you installed:\n   git clone https://github.com/cyberdione/aegis-vault && cd aegis-vault\n   git checkout <commit-for-the-installed-staging-version>\n   node scripts/verify-integrity.mjs /path/to/consumer/node_modules/@cyberdione/aegis-vault-web\n   # — or, against a freshly unpacked tarball:\n   npm pack @cyberdione/aegis-vault-web@<version>   # or use a local tgz\n   tar -xzf <tarball> -C /tmp/pkg && node scripts/verify-integrity.mjs /tmp/pkg/package\n   ```\n\n## Quick start — vanilla widget (easiest)\n\nDrop the element into your HTML and listen for its events. Works in plain HTML, React, Vue, Svelte, Solid, or anything else.\n\n```html\n<script type=\"module\">\n  // Side-effect import: registers <aegis-vault-modal>\n  import '@cyberdione/aegis-vault-web/widget';\n  import { vault } from '@cyberdione/aegis-vault-web';\n\n  const modal = document.querySelector('aegis-vault-modal');\n  modal.addEventListener('aegis-vault-unlocked', async (ev) => {\n    // ev.detail: { persistent: boolean }\n    const id = await vault.identityOpen('my-app-v1');\n    console.log('signer pubkey:', id.pubkey);\n    const sig = await id.sign(new TextEncoder().encode('hello'));\n    await id.close();\n  });\n</script>\n\n<aegis-vault-modal></aegis-vault-modal>\n```\n\nSee [`docs/examples/vanilla.html`](docs/examples/vanilla.html) for a complete runnable example.\n\n## Quick start — vanilla headless (build your own UI)\n\nFor full control over the modal UX, skip the widget and talk to the core API directly:\n\n```ts\nimport { vault } from '@cyberdione/aegis-vault-web';\n\n// 1. Check if a vault exists; if not, create one.\nif (!(await vault.exists())) {\n  await vault.create('correct horse battery staple');\n} else {\n  await vault.unlock('correct horse battery staple');\n}\n\n// 2. Open an identity for a stable purpose.\nconst id = await vault.identityOpen('hyprstream-rpc-envelope-v1');\n\n// 3. The pubkey is stable across reloads for this passphrase + purpose.\nconst pubkey: Uint8Array = id.pubkey;  // cached, synchronous access\n\n// 4. Sign canonical bytes. Domain separation is enforced inside the vault.\nconst canonical = new Uint8Array([1, 2, 3, 4]);\nconst signature: Uint8Array = await id.sign(canonical);\n\n// 5. Persist OAuth tokens encrypted at rest.\nawait vault.pageSet('auth', 'refresh_token_host_a', '...');\nawait vault.pageSet('auth', 'client_id_host_a', '...');\n\n// 6. On reload + unlock, the same purpose returns the same key.\nawait id.close();\n```\n\nSee [`docs/examples/vanilla-headless.html`](docs/examples/vanilla-headless.html) for a complete runnable example that builds its own modal.\n\n## Quick start — React\n\nReact consumers can either use the widget directly inside JSX (React treats lowercase tags as custom elements):\n\n```tsx\nimport '@cyberdione/aegis-vault-web/widget';\nimport { vault } from '@cyberdione/aegis-vault-web';\n\nfunction App() {\n  return (\n    <>\n      <aegis-vault-modal />\n      <YourApp />\n    </>\n  );\n}\n```\n\nOr build a custom modal with the headless `useVault()` hook:\n\n```tsx\nimport { VaultProvider, useVault } from '@cyberdione/aegis-vault-web/react';\n\nfunction App() {\n  return (\n    <VaultProvider>\n      <VaultGate><AuthedApp /></VaultGate>\n    </VaultProvider>\n  );\n}\n\nfunction VaultGate({ children }) {\n  const { state, actions } = useVault();\n  if (state.locked) return <YourCustomModal onUnlock={actions.unlock} />;\n  return children;\n}\n```\n\nSee [`docs/examples/react-with-widget.tsx`](docs/examples/react-with-widget.tsx) and [`docs/examples/react-custom.tsx`](docs/examples/react-custom.tsx) for complete examples.\n\n## Async API contract\n\n**All cross-boundary vault methods are `async`**, even though the current in-process backend resolves synchronously underneath. This is a forward-compatibility contract: a future cross-origin iframe deployment ([Phase D](#phases)) will use the same interface but round-trip each call through `postMessage`. Keeping consumer code async-shaped from day one means the transport swap is invisible.\n\nIn practice: `pageGet`, `pageEntries`, `identityOpen`, `IdentityHandle.sign`, and `IdentityHandle.close` all return `Promise`s. The exceptions are `IdentityHandle.pubkey` (cached at open time, synchronous field access) and `lock()` (fire-and-forget local zeroize).\n\n## Persistence model: two booleans, no enum\n\nA vault is in one of three observable states, exposed as **two independent booleans**:\n\n| `locked` | `persistent` | Meaning |\n|---|---|---|\n| `true` | _ignored_ | No in-memory vault. UI should show an unlock modal. |\n| `false` | `true` | Vault loaded from (or created into) IndexedDB. `pageSet` writes survive reload. |\n| `false` | `false` | Ephemeral in-memory vault. `pageSet` writes are lost on reload. |\n\n```ts\nimport { vault } from '@cyberdione/aegis-vault-web';\n\nvault.subscribe(({ locked, persistent }) => {\n  console.log({ locked, persistent });\n});\n\nawait vault.create('passphrase');         // → { locked: false, persistent: true }\nawait vault.startEphemeral();             // → { locked: false, persistent: false }\nvault.lock();                             // → { locked: true,  persistent: false }\n```\n\nThere is no `mode` enum and no string state. Consumers that need to know whether their `pageSet` calls will persist should check `vault.persistent` before writing.\n\n## Naming: avoiding `anonymous`\n\nHyprstream (one of this vault's intended consumers) has a server-side `Subject` named `anonymous`, meaning \"this request carried no JWT identity claim.\" That is **not** the same thing as a vault that holds an in-memory ephemeral root seed without persisting it to disk:\n\n- A vault with `persistent: false` can still authenticate to hyprstream as `Subject: jane@example.com` (the user is OAuth-logged-in, the JWT is sent, the server logs `jane`).\n- A fully persistent vault with no JWT loaded appears as `Subject: anonymous` to hyprstream.\n\nTo prevent the two concepts from blurring in code review and incident response, **aegis-vault never uses the word `anonymous`** in any identifier, type name, comment, log line, or UI string. The Rust constructor for an in-memory vault is `Vault::ephemeral()`. The TS API method is `vault.startEphemeral()`. The widget button copy is \"Continue without saving.\" If you see `anonymous` in an aegis-vault PR, please reject it on naming grounds.\n\n## HSM-style identity slots\n\nThe vault is shaped like a hardware security module: keys live inside the vault, operations on those keys go through the vault, and key material is not exported.\n\n```ts\nconst id = await vault.identityOpen('hyprstream-rpc-envelope-v1');\n//                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n//                                    \"purpose\" — a stable string identifier.\n//                                    Each unique purpose derives an independent\n//                                    Ed25519 keypair via HKDF from the root seed.\n\nid.pubkey;            // 32 bytes — cached at open, synchronous field access\nawait id.sign(bytes); // 64 bytes — Ed25519 signature, domain-separated by purpose\nawait id.close();\n```\n\n### Per-purpose key derivation\n\nFor each `purpose` string, the vault computes:\n\n```\npurpose_seed = HKDF-SHA256(\n    ikm  = root_seed,\n    info = \"aegis-purpose-key-v1\\0\" || purpose\n)\nsigning_key  = Ed25519::from_seed(purpose_seed)\n```\n\nTwo different purposes (`\"hyprstream-rpc-envelope-v1\"` and `\"ssh-user-cert-v1\"`) produce cryptographically independent keypairs. Compromise of one purpose's signing oracle does not weaken the others. Privacy benefit: an observer of one server's signer pubkey cannot link the user to a different server's signer pubkey.\n\n### Domain-separated signing\n\nEvery `sign()` call is over a SHA-512 prehash that binds the purpose:\n\n```\nsigned_bytes = SHA-512(\n    \"aegis-purpose-v1\\0\"\n    || (purpose_length as u8)\n    || purpose\n    || canonical_bytes\n)\nsignature = Ed25519::sign(purpose_signing_key, signed_bytes)\n```\n\nA signature produced under purpose A will **not verify** against purpose B's pubkey, even for byte-identical `canonical_bytes`. This makes cross-protocol attacks (one purpose's signing oracle producing valid signatures for another purpose's protocol) structurally impossible.\n\n### Verifying signatures server-side\n\nA verifier needs to replicate the prehash and check against the per-purpose pubkey. Pseudocode:\n\n```python\ndef verify(pubkey, purpose, canonical_bytes, signature):\n    prehash = sha512(\n        b\"aegis-purpose-v1\\0\"\n        + bytes([len(purpose)])\n        + purpose.encode(\"utf-8\")\n        + canonical_bytes\n    )\n    return ed25519_verify(pubkey, prehash, signature)\n```\n\nThe `aegis-vault` Rust crate exposes a `verify_purpose` test helper for round-trip checks; production verifiers should implement the prehash construction directly to avoid taking on a vault dependency.\n\n## Pages: encrypted key/value storage\n\nPages are independently encrypted namespaces within the vault. Each page has its own AES-256-GCM subkey derived from the root seed via HKDF.\n\n```ts\nawait vault.pageSet('auth', 'refresh_token', 'abc...');\nawait vault.pageSet('auth', 'client_id', 'xyz...');\nawait vault.pageSet('hosts', 'host:host_42', JSON.stringify({...}));\n\nconst token = await vault.pageGet('auth', 'refresh_token');\nconst allAuth = await vault.pageEntries('auth');\n```\n\nAll page operations are async. See \"Async API contract\" above for why.\n\n### Well-known page names\n\nThe crate validates against a fixed list of page names so the on-disk `page_id` byte stays stable across consumers. v0.2 supports:\n\n| Page | Intended use |\n|---|---|\n| `hosts` | Host endpoints, cert hashes, OAuth URLs |\n| `auth` | OAuth refresh tokens, dynamic client IDs |\n| `llm` | LLM provider API keys |\n| `prefs` | App preferences and small config |\n\nAdding a new well-known page requires bumping the crate version and adding a `PageId` variant. Future versions may relax this if free-form page names prove safe.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────┐\n│  Your app (vanilla, React, Vue, Svelte, …)  │\n└──────────────┬──────────────────────────────┘\n               │  one of three paths\n   ┌───────────┼────────────────────────┐\n   │           │                        │\n   ▼           ▼                        ▼\n┌────────┐ ┌──────────────────┐  ┌─────────────┐\n│ widget │ │ headless TS API  │  │ react hooks │\n│ (vanilla │ │ (vanilla)        │  │ (~80 LOC    │\n│  Web     │ │ vault singleton  │  │  adapter)   │\n│  Comp)   │ │ VaultClient iface│  │             │\n└────┬───┘ └────────┬─────────┘  └─────┬───────┘\n     └──────────────┴──────────────────┘\n                    │\n                    ▼\n┌─────────────────────────────────────────────┐\n│  @cyberdione/aegis-vault-web (vanilla core) │\n│  - AegisVault (locked / persistent)         │\n│  - IndexedDB I/O                            │\n│  - BroadcastChannel tab sync                │\n│  - Async VaultClient contract               │\n└──────────────┬──────────────────────────────┘\n               │  wasm-bindgen\n┌──────────────▼──────────────────────────────┐\n│  aegis-vault (Rust crate)                   │\n│  - Argon2id, HKDF, AES-GCM, HMAC            │\n│  - Ed25519 per-purpose derivation           │\n│  - Page encrypt/decrypt                     │\n│  - Versioned blob format                    │\n│  - Zeroizing<...> for all secrets           │\n└─────────────────────────────────────────────┘\n```\n\nThe Rust crate is the only place that handles secrets. The TS core shuttles opaque ciphertext blobs between IDB and the wasm module but never sees plaintext seeds, page contents, or signatures-in-progress. The three consumption paths (widget, headless, React hooks) are thin layers over the vanilla core — you can mix and match, and switching between them is a one-import change.\n\nReact is not privileged in the architecture. Future Vue / Svelte / Solid adapters would be peers of the React adapter, all built on the same vanilla `VaultClient` interface.\n\n## Build from source\n\nConsumers never need this — the npm package is prebuilt. This is for\ncontributors cutting a release or developing the library. It requires Rust,\n`wasm-pack`, and `capnproto`.\n\n```bash\ngit clone https://github.com/cyberdione/aegis-vault\ncd aegis-vault\n\nnpm install            # dev deps only — no prepare hook, no toolchain run\nnpm run build          # wasm-pack + tsc + integrity manifest → dist/ + pkg/\nnpm run smoke          # prove the packed tarball needs no toolchain to install\n```\n\n`npm install` does **not** build (there is no `prepare` hook). You must run\n`npm run build` explicitly; the `prepack` guard refuses to `npm pack`/publish\nuntil `dist/` + `pkg/` + `dist/integrity.json` exist.\n\nTests:\n\n```bash\n# Rust unit tests (native target)\ncd crates/aegis-vault\ncargo test\n```\n\n## Phases\n\nThis repo delivers aegis-vault in phases. v0.2 is the current release.\n\n### Shipped\n\n- **v0.1** — Rust crate + TS shim + React hooks, in-process backend only. Bootstrapped the project.\n- **v0.2** (current) — Vanilla `<aegis-vault-modal>` Web Component widget. Async API contract (`pageGet`, `identityOpen`, `IdentityHandle.sign` all `Promise`-returning). Transport-agnostic `VaultClient` interface. React modal reference implementation moved from library code into `docs/examples/` so the React subpath is purely headless hooks.\n\n### Future\n\n- **Phase B — Wanix worker isolation.** Compile the same Rust core to a WASI binary that runs as an isolated [Wanix](https://github.com/cyberdione/wanix) process, communicating with consumer code via DMA ring buffer IPC. The seed lives in a separate WebAssembly linear memory that the main thread cannot read at all. Same `VaultClient` interface; only the transport changes.\n\n- **Phase D — Cross-origin iframe deployment.** Serve the vault as a static iframe-guest app at a separate origin (e.g. `vault.cyberdione.io`). Parent pages embed it with `<iframe>` and talk over `postMessage`; the same-origin policy provides a real isolation boundary against post-unlock JavaScript in the host page. Same `VaultClient` interface — a new `/iframe-host` subpath implements it by routing calls through `postMessage`. Phase B and Phase D compose: the iframe-guest can internally run the vault in a Wanix worker for maximum strength.\n\nNeither is part of v0.2. Both are tracked in the aegis-vault roadmap and will land as separate releases. Phase D is expected to land before Phase B because it's an independently valuable deployment option and does not depend on Wanix bring-up.\n\n## License\n\nApache-2.0. See `LICENSE`.\n\n## Threat model\n\nSee [`THREATMODEL.md`](./THREATMODEL.md) for what aegis-vault protects against and what it explicitly does not.\n","readmeFilename":"README.md"}