What conseal is designed to protect against, what it explicitly does not protect against, and what assumptions you must satisfy as a caller.
Scope: conseal v0.x – v1.x · Audience: integrators, security reviewers, auditors
The table below lists every asset conseal handles, what it is, and where it lives at rest. Protection goals are derived from this asset list.
| Asset | Description | Location at rest |
|---|---|---|
| AEK | Root AES-256-GCM key; all user data is encrypted with it | IndexedDB — non-extractable CryptoKey |
| Wrapped AEK | AEK protected by PBKDF2 + AES-KW | Server / cloud storage |
| Secret Key (SK) | 128-bit second factor for AEK wrapping | Device-local (e.g. localStorage) |
| Passphrase | Human-chosen credential; combined with SK into PBKDF2 | User's memory only |
| Plaintext | Application data before encryption | Transient in JavaScript memory only |
| Ciphertext | AES-256-GCM encrypted application data | Server / cloud storage |
| ECDH / ECDSA key pairs | Per-user asymmetric keys for messaging and signing | Server (public) / IndexedDB (private) |
| BIP-39 mnemonic | 24-word phrase derived from AEK for offline backup | User's written record only |
Full read access to the database — ciphertext, wrapped keys, salts, public keys.
Ciphertext blobs — useless without the AEK. Wrapped AEK + salt — useless without the passphrase and (if used) the Secret Key. ECDH / ECDSA public keys — non-sensitive by design. No access to the plaintext AEK, Secret Key, or passphrase.
PBKDF2 at 600,000 HMAC-SHA-256 iterations makes offline passphrase guessing expensive. Combined with a 128-bit Secret Key (which never reaches the server), the wrapping key cannot be brute-forced even with specialised hardware.
A weak passphrase without a Secret Key reduces to the strength of that passphrase alone. Callers should enforce minimum passphrase entropy or mandate Secret Key use.
Offline dictionary or exhaustive-search attack on a { wrappedKey, salt } pair retrieved from the server or a backup.
600,000 iterations ≈ 200–500 ms per guess on modern hardware. A 128-bit random Secret Key raises the effective key space to 2¹²⁸ · |passphrase space|, making exhaustive search infeasible. Each random salt prevents pre-computation (rainbow table) attacks.
Flip bits in a stored or transmitted ciphertext.
AES-256-GCM is an authenticated encryption scheme. Any modification to the ciphertext, IV, or AAD causes unseal() / unsealMessage() / unsealEnvelope() to throw a DOMException — tampered bytes are never returned to the caller. There is no "decrypt-and-ignore-the-tag" path.
Concern that conseal silently exfiltrates key material to a remote endpoint.
The library is open-source and auditable. It makes no network requests. Key material is passed only to SubtleCrypto, which enforces extractable: false on stored keys — preventing JavaScript code (including conseal itself) from reading raw key bytes out of IndexedDB.
Passive observation or active modification of HTTP traffic when TLS is in use.
All data transmitted between browser and server is either ciphertext or wrapped keys. Neither is useful without the passphrase and Secret Key, which are never transmitted.
Injected JavaScript in the same origin can call conseal.seal(), conseal.unseal(), and loadCryptoKey() directly. Non-extractable CryptoKey objects cannot have their bytes read, but the attacker can still use them to encrypt, decrypt, or sign arbitrary data. XSS breaks the browser's same-origin boundary — once arbitrary JavaScript runs on your page, it can use every API as legitimately as your own code.
Caller responsibility: Implement a strict Content Security Policy, sanitise all user-supplied HTML, and follow OWASP XSS prevention guidelines.
If an attacker controls page delivery they can serve modified JavaScript that intercepts the passphrase or key material before conseal handles it. This is the classic "server compromise breaks client-side crypto" problem — conseal wraps keys on the client, but cannot protect against a backdoored bundle.
Caller responsibility: Use Subresource Integrity (SRI) for all scripts, deploy to platforms with tamper-evident deployments, and monitor for unexpected bundle changes.
Timing attacks, cache-timing (Spectre / Meltdown), power analysis, and EM side-channels target the hardware or the browser's SubtleCrypto implementation. conseal has no influence over CPU caches, OS schedulers, or physical hardware.
Caller responsibility: Rely on browser vendor mitigations for Spectre-class attacks. If your threat model includes nation-state physical access, conseal is not the right layer.
AEK and wrapped keys are kept as native CryptoKey objects wherever possible so raw bytes are never exposed to JavaScript. However, the passphrase string and transient byte arrays (during wrapping / unwrapping) live in the JS heap. JavaScript has no manual memory management — strings and Uint8Array values cannot be zeroed before GC.
Mitigation: conseal minimises the window during which raw key bytes are in scope, but cannot eliminate it entirely.
A passphrase chosen from a small dictionary ("123456", "password") dramatically shrinks the effective key space regardless of iteration count. conseal does not validate passphrase entropy — that is an application policy decision.
Caller responsibility: Enforce minimum passphrase length and entropy, or always use generateSecretKey() to make server-side brute force infeasible regardless of passphrase quality.
If the Secret Key is lost (device destroyed, localStorage cleared, no backup) and the user has not retained their BIP-39 mnemonic, the AEK cannot be recovered — all encrypted data is permanently inaccessible. This is the intended consequence of zero-knowledge: the server cannot recover the AEK on the user's behalf.
Caller responsibility: Prompt users to record their mnemonic and Secret Key during setup. Offer a "recovery export" step before destructive operations.
The 24-word mnemonic is a complete export of the AEK. Anyone who obtains it can reconstruct the AEK and decrypt all data. It is intended for offline backup only and should never be transmitted unencrypted.
Caller responsibility: Display the mnemonic only on explicit user request, prompt the user to acknowledge its sensitivity, and never log or transmit it.
If any of the following conditions are violated, the corresponding security property collapses.
| # | Assumption | Consequence of violation |
|---|---|---|
| 1 | Application served over HTTPS with a valid certificate | Network attacker can substitute a malicious JavaScript bundle |
| 2 | Strict Content Security Policy prevents inline scripts and untrusted sources | XSS can call conseal APIs and use all IndexedDB keys directly |
| 3 | Passphrases have adequate entropy, or a Secret Key is always used | Offline brute-force against a stolen wrapped key becomes feasible |
| 4 | Secret Key is never stored on the server or in any server-readable location | Server-compromise defence collapses |
| 5 | BIP-39 mnemonic treated as a high-sensitivity credential (offline only) | A single leak allows full AEK reconstruction |
| 6 | Application does not eval or execute untrusted scripts in the same origin |
Equivalent to assumption 2 — any script can use loaded CryptoKey objects |
| 7 | Browser's SubtleCrypto implementation is not compromised | All cryptographic operations are delegated to the browser; a backdoored browser breaks everything |
| 8 | init() is called before any seal() / unseal() operation |
Encryption will throw or operate on an unexpected key |
| Primitive | Algorithm | Parameters | Justification |
|---|---|---|---|
| Symmetric encryption | AES-GCM | 256-bit key · 12-byte IV · 128-bit tag | NIST standard; authenticated; hardware-accelerated in all modern CPUs and browsers |
| Key derivation | PBKDF2-HMAC-SHA256 | 600,000 iterations · 16-byte random salt | RFC 8018; NIST SP 800-132 recommends ≥ 210,000 for SHA-256; universal browser support |
| Key wrapping | AES-KW | 256-bit wrapping key | RFC 3394; deterministic and safe for uniformly-random key material |
| Key agreement | ECDH P-256 | NIST P-256 curve | Universally available in SubtleCrypto; ephemeral key per message provides forward secrecy |
| Signatures | ECDSA P-256 | SHA-256 hash | Compact 64-byte signatures; baseline curve in WebCrypto; no additional dependencies |
| Hashing | SHA-256 | — | Universal; used as PBKDF2 PRF and standalone digest via digest() |
| Mnemonic encoding | BIP-39 | 24 words · 256-bit entropy + 8-bit checksum | Human-writeable offline backup; well-specified; implemented by @scure/bip39 |
| Randomness | crypto.getRandomValues |
— | CSPRNG provided by the browser/OS; no user-space RNG |
Use HTTPS / TLS. conseal does not manage network connections.
Use a dedicated auth layer — passwords, WebAuthn, OAuth. conseal handles encryption, not identity.
Decide who may call your API before passing data to conseal. Authorization is out of scope.
None of the required algorithms are yet available in SubtleCrypto. conseal will revisit when browser APIs catch up.
conseal does not hide that data is encrypted — only that the server cannot read the plaintext.
conseal does not hide who is communicating with whom, or who owns which encrypted data.
Please follow the responsible disclosure process rather than opening a public GitHub issue. Encryption bugs are treated with the highest priority.
Security policy →