summaryrefslogtreecommitdiff
path: root/trust-boundaries.md
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2026-02-26 09:13:23 +0100
committerYuval Adam <_@yuv.al>2026-02-26 09:13:23 +0100
commit7b609fffddf3ae138cdf301c97bad805fc508616 (patch)
treee952de5263e507e52b74db48cac8ac93f36fa8be /trust-boundaries.md
parent54adf11e1c8905c97512fcf29d8b3d75aa9eb0cb (diff)
Add key hierarchy analysis, update all docs with WASM/auth/native messaging findingsHEADmain
New document: - key-hierarchy.md: Full key derivation model, MUK lifecycle, SRP auth, biometric unlock, Duo MFA, dSecret bypass, delegated sessions, password timebox mechanism, crypto algorithm inventory Major updates: - architecture.md: Expanded WASM section with confirmed 80+ rA.* methods, clarified WASM is portability layer not security boundary - trust-boundaries.md: Corrected Zone A (keys in JS heap not just WASM), Zone D (confirmed native messaging protocol with biometry messages), Zone F (WASM is NOT a privilege boundary), detailed sensitive data table with confirmed storage locations, new Critical attack surface category - message-catalog.md: Added native messaging protocol (biometry save/unlock/ remove, availability check), desktop connection messages, server notification events - TODO.md: Marked completed items, added key material exposure assessment section, authentication & session security section
Diffstat (limited to 'trust-boundaries.md')
-rw-r--r--trust-boundaries.md117
1 files changed, 78 insertions, 39 deletions
diff --git a/trust-boundaries.md b/trust-boundaries.md
index 2481460..c7d9884 100644
--- a/trust-boundaries.md
+++ b/trust-boundaries.md
@@ -4,7 +4,10 @@
### Zone A — Background (highest extension privilege)
- Full access to all `chrome.*` APIs
-- Holds vault state, account sessions, crypto keys (in WASM memory)
+- Holds vault state, account sessions, crypto keys **in both JS heap and WASM memory**
+- Master Unlock Key (MUK) stored as exportable JWK on account handler objects in JS
+- SRP-X cached in JS on client context objects
+- Decrypted item secrets (passwords, OTPs, card numbers) transit through JS heap during fill operations
- Central policy decision point for all sensitive operations
- Only context that communicates with native host and remote services
@@ -21,20 +24,30 @@
### Zone D — Native Host (desktop app)
- Connected via `nativeMessaging` permission
-- `chrome.runtime.sendNativeMessage` (2 occurrences in background.js)
+- `browser.runtime.sendNativeMessage("")` — empty string as native app ID (Firefox uses the manifest `applications.gecko.id` for routing)
- Native app connection initialized during background startup (`initializeNativeAppConnection`)
-- Trust level: equivalent to or higher than extension (system-level process)
+- **Confirmed operations via native messaging:**
+ - Biometric unlock: save/retrieve/remove MUK + SRP-X from OS secure enclave
+ - Biometry availability check
+ - dSecret proxy for MFA bypass on trusted devices
+ - Device trust public key signing
+ - Delegated session management (upgrade from offline state)
+ - Desktop connection manager state
+- Messages use JSON envelope: `{name: "core", data: JSON.stringify({type: "Biometry", data: {...}})}`
+- Trust level: higher than extension — has access to OS keychain/secure enclave
### Zone E — Remote Services (1Password cloud)
- Extensive `connect-src` allowlist (see below)
- WebSocket connections for real-time sync (`wss://b5n.*`)
- REST APIs for account management, vault operations
-### Zone F — WASM Modules (crypto boundary)
-- 7 WASM modules loaded with `wasm-unsafe-eval`
-- Crypto operations are isolated inside WASM linear memory
-- JS ↔ WASM boundary is a trust boundary: JS passes data in, WASM performs crypto, returns results
-- Key material should ideally never exist in JS heap (only WASM memory)
+### Zone F — WASM Modules (portability layer, NOT a security boundary)
+- 7 WASM modules loaded with `wasm-unsafe-eval`, compiled from Rust via `wasm-bindgen`
+- Provides the same Rust core used by desktop/mobile/CLI clients — portability, not isolation
+- **Key material DOES cross the WASM→JS boundary**: MUK exported as JWK, SRP-X cached in JS, decrypted passwords returned to JS for fill
+- JS can read WASM linear memory — no privilege separation exists
+- WASM's value is **correctness** (battle-tested Rust crypto) not **isolation**
+- Only 4 `crypto.subtle.*` calls in background.js; WASM handles virtually all crypto
### Zone G — External Extensions
- `chrome.runtime.onMessageExternal` (3 references)
@@ -66,9 +79,15 @@
- **Risk: any page script can craft these messages**
### B3: Background → Native Host
-- `chrome.runtime.sendNativeMessage` — structured JSON messages
-- Mycelium protocol (WASM) may also use this channel for relay
-- **Needs analysis: what commands can be sent? What data flows back?**
+- `browser.runtime.sendNativeMessage("")` — JSON envelope with `{name: "core", data: ...}`
+- **Confirmed message types:**
+ - `{type: "Biometry", data: {type: "save", data: {secrets: [{accountUuid, userUuid, muk: {kty, kid, alg, k, ...}, srpX}]}}}` — **sends MUK (the master unlock key) and SRP-X to native app for biometric storage**
+ - `{type: "Biometry", data: {type: "unlock", data: {accounts, useBiometry, useAppleWatch, ...}}}` — retrieves MUK + SRP-X from secure enclave
+ - `{type: "Biometry", data: {type: "remove", data: {accounts, ...}}}` — removes stored secrets
+ - `{type: "Biometry", data: {type: "biometryAvailability"}}` — checks if Touch ID / biometry is available
+- 10-second timeout on all native messages
+- dSecret proxy also flows through native messaging for MFA bypass
+- **Risk: the MUK (symmetric key that unlocks everything) is serialized as JWK and sent over the native messaging channel**
### B4: Background → Remote Services
- HTTPS REST + WebSocket to 1Password infrastructure
@@ -81,26 +100,37 @@
- `chrome.tabs.sendMessage` to push state to inline menus/modals
- **Risk: `relay-message-to-frames` and `targeted-message-to-inline-menu`** — background acts as a message relay between frames. If a compromised frame can influence the relay target, it could potentially spoof messages to other frames.
-### B6: JS → WASM
+### B6: JS ↔ WASM (NOT a privilege boundary)
- Core crypto, trust log, trust verification, HPKE, mycelium all in WASM
-- JS serializes data, calls WASM exports, receives results
-- **Risk: incorrect serialization could leak or corrupt key material at the boundary**
+- JS calls `rA.*` methods (80+ confirmed) which route into `op_wasm_b5x_bg`
+- **Key material flows freely across this boundary:**
+ - MUK exported from account handler via `.exportJwk()` → full JWK in JS heap
+ - SRP-X cached on `CTX.session.auth.srpX` in JS
+ - Decrypted passwords/OTPs returned from `rA.fillItem` / `rA.fieldValueByIdentifier` to JS
+ - Save objects constructed via `rA.createSaveObject` — encrypted in WASM, ciphertext returned to JS
+- WASM linear memory is directly accessible from same-origin JS
+- **This is NOT a security boundary** — it's a code-sharing mechanism. A compromised background page can extract all key material.
## Sensitive Data Classes
-| Class | Where Created | Where Used | Notes |
-|-------|--------------|------------|-------|
-| Master password / derived key | User input → WASM | WASM memory only (ideally) | PBKDF2/HKDF derivation |
-| SRP verifier/session | WASM | Background ↔ Remote | Authentication protocol |
-| Vault encryption keys | WASM (decrypted from server) | WASM memory | AES-GCM, AES-CBC |
-| Item secrets (passwords, OTPs) | WASM (decrypted) | Passed to content script for fill | **Transits JS heap** |
-| Passkey private keys | WASM | WASM → background → content → page world | **Full chain traversal** |
-| Credit card numbers | WASM (decrypted) | Content script fill | |
-| Session tokens | Background | Background ↔ Remote | |
-| Device keys | Background/Native | Background | |
-| Save objects (captured credentials) | Content script | Background → WASM → Remote | Encrypted with public key before transit |
-| Telemetry data | All contexts | Background → Snowplow/Sentry | URLs, form hints, error stacks |
-| Feature flags | Remote → Background | All contexts | Controls behavior |
+| Class | Where Created | Where Stored | Where Transits | Notes |
+|-------|--------------|-------------|----------------|-------|
+| Master password | User input | **Timeboxed JS reference (5 min)**, then cleared | JS → WASM for key derivation, also set on `CTX.user.password` temporarily during sign-in then set to `undefined` | `setTimeout` clears after 5 min (`gkA = 5 * 60 * 1000`) |
+| Master Unlock Key (MUK) | WASM (PBKDF2/HKDF from password + secret key) | **JS heap** as exportable JWK on account handler (`accountHandler.masterKey`) | JS → native messaging (biometry save), JS → WASM (re-auth) | **The crown jewel.** Can be exported via `.exportJwk()`. Sent to native app for biometric storage. |
+| Secret Key (A3-XXXXX-...) | User input / stored in DB | Account handler, database | JS → WASM for SRP, exportable via `.exportSensitiveReadableString()` | Combined with password for key derivation |
+| SRP-X | WASM (derived from MUK + Secret Key) | **JS heap** cached on `CTX.session.auth.srpX` | JS → native messaging (biometry save), JS → WASM (re-auth) | Used for re-authentication without master password |
+| dSecret | Server / device | Account handler, native app | JS ↔ native messaging, JS → server for MFA | Device secret for MFA bypass on trusted devices |
+| Session context (CTX) | WASM (`SA.initialize`) | **JS heap** on client object (`_dangerousInnerCTX`) | JS ↔ WASM, exportable via `SA.getInitializationExport()` | Contains session keys, account state. Note the `_dangerous` prefix — they know. |
+| Vault/item encryption keys | WASM (decrypted from server keysets) | WASM memory (likely) | WASM internal | AES-GCM, AES-CBC. Exposed via `rA.fillItem` etc. |
+| Item secrets (passwords, OTPs) | WASM (decrypted) | **JS heap** during fill | Background JS → `chrome.tabs.sendMessage` → content script → DOM | Plaintext in JS for the duration of fill |
+| Passkey assertions | WASM | **JS heap** | Background → content script → page world postMessage | **Full chain traversal** through all trust zones |
+| Credit card numbers | WASM (decrypted) | **JS heap** during fill | Same as item secrets | |
+| Save objects | Content script (DOM capture) | Background | Content script → background → `rA.createSaveObject` (encrypted in WASM) → server | Encrypted with public key before storage |
+| Biometry secrets bundle | JS (assembled from MUK + SRP-X) | Native app secure enclave | JS → `browser.runtime.sendNativeMessage` → OS keychain | `{muk: {kty, kid, alg, k, ext, key_ops}, srpX}` per account |
+| Telemetry data | All contexts | Background (batched) | Background → Snowplow/Sentry | URLs, form hints, error stacks, account metadata |
+| Feature flags | Remote server (Unleash) | Background (cached) | Background → all contexts | Controls security-relevant behavior |
+
+See [key-hierarchy.md](key-hierarchy.md) for the full key derivation model.
## Network Endpoints (from CSP connect-src)
@@ -132,7 +162,8 @@
### Local / Native
- `http://127.0.0.1:12519`, `:40978`, `:52115`, `:22287`, `:60685`, `:22322` (6 localhost ports)
-- These are likely the native helper broker / desktop app bridge endpoints
+- Likely redundant ports for the native helper broker / desktop app bridge (multiple ports for reliability across OS configurations)
+- Native messaging also used via `browser.runtime.sendNativeMessage("")` for biometry, dSecret proxy, device trust
### Other
- `api.pwnedpasswords.com` (Have I Been Pwned API for Watchtower)
@@ -151,19 +182,27 @@ This is a privacy-enhancing measure to prevent DNS providers from fingerprinting
## Attack Surface Summary
+### Critical
+1. **MUK in JS heap** — the Master Unlock Key is stored as an exportable JWK in JS memory and sent over native messaging. A background page compromise (malicious update, browser bug, XSS in extension pages) exposes the key that decrypts everything.
+2. **MUK + SRP-X sent to native app** — biometry save transmits `{muk: {k: "base64url_symmetric_key"}, srpX}` over native messaging JSON. If the native messaging channel is compromised, full account takeover is possible.
+3. **Master password timeboxing only** — the password reference is cleared after 5 minutes via `setTimeout`, but the MUK derived from it persists in JS for the entire unlocked session. No explicit memory zeroing (JS GC handles it, which is non-deterministic).
+
### High Priority
-1. **WebAuthn page-world IPC** — unauthenticated postMessage protocol, any page can participate
-2. **Frame relay** (`relay-message-to-frames`) — background forwards messages between frames without clear origin validation (needs verification)
-3. **Save object pipeline** — content script captures and transmits credentials (encrypted with public key)
-4. **829 named messages** — massive handler surface in background, schema validation unknown
+4. **WebAuthn page-world IPC** — unauthenticated postMessage protocol, any page can participate
+5. **Frame relay** (`relay-message-to-frames`) — background forwards messages between frames without clear origin validation (needs verification)
+6. **Save object pipeline** — content script captures and transmits credentials (encrypted with public key)
+7. **829 named messages** — massive handler surface in background, schema validation unknown
+8. **`_dangerousInnerCTX`** — the session context object (containing session keys and auth state) is explicitly named "dangerous" by the developers, suggesting they recognize the risk of it being in JS
### Medium Priority
-5. **External extension messaging** — `onMessageExternal` accepts messages from any Firefox extension
-6. **6 localhost ports** — native bridge endpoints, protocol unknown
-7. **Web-accessible resources** — inline UI HTML files can be loaded by any page (fingerprinting, UI redressing)
-8. **WASM ↔ JS boundary** — key material handling across the boundary
+9. **External extension messaging** — `onMessageExternal` accepts messages from any Firefox extension
+10. **6 localhost ports** — native bridge endpoints, protocol details unclear beyond biometry
+11. **Web-accessible resources** — inline UI HTML files can be loaded by any page (fingerprinting, UI redressing)
+12. **Re-authentication with cached MUK** — when a session expires (401), the extension re-authenticates using the cached MUK and SRP-X without user interaction (`executeWithReauth`). This means a stolen MUK enables silent re-auth.
+13. **Delegated sessions** — background can request delegated sessions for re-auth, transferring session state between contexts
### Lower Priority
-9. **Telemetry data classification** — what exactly is sent to Snowplow/Sentry
-10. **Partner integration data minimization** — what's shared with Privacy.com, Fastmail, Brex, Kolide, Trelica
-11. **`<all_urls>` + `webRequestBlocking`** — can observe/modify all web traffic
+14. **Telemetry data classification** — what exactly is sent to Snowplow/Sentry
+15. **Partner integration data minimization** — what's shared with Privacy.com, Fastmail, Brex, Kolide, Trelica
+16. **`<all_urls>` + `webRequestBlocking`** — can observe/modify all web traffic
+17. **Duo MFA tab injection** — extension opens a Duo MFA tab via `chrome.tabs.create`, monitors its URL for `duo_code` parameter, then closes it. The URL monitoring is done via `chrome.webRequest.onHeadersReceived`.