Skip to content

Security Model

This document covers how the wallet protects private material, manages sessions, and what trade-offs have been accepted.

Vault encryption

All private data is stored in an encrypted vault. The encryption pipeline (defined in src/domain/crypto.ts):

  • Key derivation - PBKDF2 with 600,000 iterations, SHA-256 hash, 16-byte random salt
  • Cipher - AES-GCM with a 256-bit derived key and 12-byte random IV
  • Plaintext - JSON-serialized VaultPlaintext containing accounts, settings, permissions, and network-scoped data

The iteration count has a validated range of 10,000 to 1,000,000. Vaults created with older iteration counts are transparently migrated to 600,000 on unlock.

Storage at rest

The encrypted vault is stored in chrome.storage.local under the key tao_wallet_encrypted_v1. This is the only location where account secrets (mnemonics, derived keys) persist, and they are always encrypted.

The EncryptedVault structure contains the KDF parameters (salt, iterations), cipher parameters (IV), and the ciphertext. No plaintext secrets touch disk.

Session management

When the user unlocks the vault, a session is created containing the derived CryptoKey, the salt, and the iteration count. This session is stored in chrome.storage.session under the key tao_wallet_unlock_session_v1.

chrome.storage.session is memory-backed - it survives MV3 service worker hibernation but is cleared when the browser closes. This is a deliberate choice: MV3 service workers can be suspended at any time, and the session must survive those suspensions without writing decrypted material to disk.

Password requirements

Wallet passwords must be at least 8 characters. New passwords (on creation or change) must also meet strength requirements: at least one uppercase letter, one digit, and one special character.

These requirements are enforced in the UI during wallet creation (CreatePassword.tsx), password change (SettingsSecurityPassword.tsx), and backup restore with new password (RestoreBackup.tsx).

Auto-lock

The wallet locks itself after a configurable period of inactivity (default varies by user setting). Auto-lock is implemented via chrome.alarms - when the alarm fires, the in-memory session is cleared, signing keys are zeroed, and the vault returns to a locked state.

Activity is tracked on non-passive background operations. Read-only actions (balance checks, state queries) do not extend the session.

To configure auto-lock, see Security settings.

Passkey unlock

The wallet supports passkey (WebAuthn) as an alternative unlock method. The implementation uses the PRF (pseudo-random function) extension to derive a wrapping key from the authenticator. This wrapping key encrypts the vault session material, which is then stored alongside passkey metadata in chrome.storage.local.

On unlock, the PRF output is used to decrypt the stored session, bypassing password entry. Passkey enrollment requires the side panel display mode due to browser API constraints.

Public metadata caching

Some data is stored unencrypted in chrome.storage.local for performance:

  • accountsPublicCache - public account info (addresses, labels, types) so the locked wallet can show account counts and respond to wallet_getAccounts for authorized origins
  • proxyRelationshipsCache - proxy delegation topology (public on-chain data)
  • networkConfigCache - selected RPC endpoint and genesis hash

None of these contain secrets. This is consistent with how MetaMask and the Polkadot.js extension handle public metadata.

Accepted trade-offs

These are accepted trade-offs with full rationale:

  • AES key in session storage - the derived CryptoKey lives in chrome.storage.session while unlocked. Memory-backed, cleared on browser close. Physical-access attack surface only.
  • Metadata on disk without encryption - public addresses and network topology are cached unencrypted for performance. No secrets exposed.
  • Clipboard overwrite edge case - the 30-second auto-clear for copied seed phrases may overwrite unrelated clipboard content. Same behavior as 1Password and Bitwarden.
  • Unmount before clipboard clear - navigating away from the seed display before the 30-second timer fires prevents clipboard clearing. Short window, user is actively navigating.

Threat model

  • Phishing dApps - mitigated by explicit per-request approval with the requesting origin displayed prominently in the approval window
  • Origin spoofing - the origin is taken from the content script's window.location.origin, not from the dApp's self-reported data
  • Storage exfiltration - at-rest storage contains only the encrypted vault. Extracting chrome.storage.local yields ciphertext protected by PBKDF2 + AES-GCM
  • MV3 worker restarts - the unlocked session is memory-only (via chrome.storage.session) and intentionally lost when the browser closes