April 2026

Threat Model

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

Key assets

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.

AssetDescriptionLocation 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

What conseal resists

defended Malicious or compromised server
Capability

Full read access to the database — ciphertext, wrapped keys, salts, public keys.


What the attacker gains

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.


Defence

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.


Residual risk

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.

defended Offline brute-force against a stolen wrapped key
Capability

Offline dictionary or exhaustive-search attack on a { wrappedKey, salt } pair retrieved from the server or a backup.


Defence

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.

defended Ciphertext tampering
Capability

Flip bits in a stored or transmitted ciphertext.


Defence

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.

defended Passive key logging by the library
Capability

Concern that conseal silently exfiltrates key material to a remote endpoint.


Defence

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.

defended Network eavesdropping (with TLS)
Capability

Passive observation or active modification of HTTP traffic when TLS is in use.


Defence

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.

What conseal does not protect against

not protected Cross-Site Scripting (XSS)

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.

not protected Server compromise → malicious JS delivery

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.

not protected Hardware side-channel attacks

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.

partial JavaScript heap / memory scanning

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.

not protected Weak passphrases

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.

by design Secret Key loss / permanent data inaccessibility

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.

not protected BIP-39 mnemonic exposure

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.

Preconditions for security properties to hold

If any of the following conditions are violated, the corresponding security property collapses.

#AssumptionConsequence 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

Cryptographic primitives

PrimitiveAlgorithmParametersJustification
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

Deliberately not addressed

Transport security

Use HTTPS / TLS. conseal does not manage network connections.

Authentication

Use a dedicated auth layer — passwords, WebAuthn, OAuth. conseal handles encryption, not identity.

Access control

Decide who may call your API before passing data to conseal. Authorization is out of scope.

Post-quantum cryptography

None of the required algorithms are yet available in SubtleCrypto. conseal will revisit when browser APIs catch up.

Obfuscation

conseal does not hide that data is encrypted — only that the server cannot read the plaintext.

Anonymity

conseal does not hide who is communicating with whom, or who owns which encrypted data.

Reporting security issues

Please follow the responsible disclosure process rather than opening a public GitHub issue. Encryption bugs are treated with the highest priority.

Security policy →