The HexVault browser extension is built on Manifest V3 with a security model designed around one principle: the extension should be as difficult to exploit as the vault itself.
HexVault uses Manifest V3, Chrome's current extension platform. MV3 replaced background pages with service workers, removed remote code execution, and tightened the permission model. Every API call is auditable in the extension source.
{
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
}
}The wasm-unsafe-eval directive is required to run Argon2id as a WebAssembly module for key derivation. All other script execution is restricted to 'self' — extension-bundled files only.
Every permission in the manifest has a specific, documented purpose. The extension requests nothing it doesn't use.
| Permission | Why it's needed |
|---|---|
| storage | Persists the encrypted vault cache and session mirror locally. All stored data is ciphertext — never plaintext credentials. |
| activeTab | Reads the current tab's URL to match credentials. Only granted for the active tab at the moment the popup is open — not persistent. |
| scripting | Injects the autofill content script into pages when the user explicitly triggers fill. Not injected automatically on page load. |
| contextMenus | Adds "Fill with HexVault" to the right-click menu on input fields. |
| offscreen | Creates a hidden page with relaxed CSP to run Argon2id WebAssembly for key derivation. Crypto never runs in the service worker itself. |
| alarms | Schedules the auto-lock timer. Chrome service workers can't use setTimeout reliably — alarms survive service worker termination. |
| idle | Detects when the system is idle to trigger auto-lock after the configured timeout. |
| notifications | Shows breach alerts and update notifications. |
| tabs | Reads tab URL for credential matching when the popup opens. Not used for cross-tab tracking. |
https://hexvault.co.uk/* only. The extension cannot read or modify content on any other website except when the user explicitly triggers autofill.All cryptographic operations — Argon2id key derivation, AES-256-GCM encryption and decryption — run in a dedicated offscreen document, not in the service worker.
This architecture means a compromised popup or content script cannot directly access the master key or perform decryption — it can only request operations through the background service worker, which validates every request.
The offline vault cache — encrypted entries exactly as received from the server. This is the same ciphertext the server stores. The master key is never written here. If an attacker gains access to chrome.storage.local, they get the same ciphertext they would get from compromising the server.
The derived session keys, decrypted vault entries (when unlocked), and session token. Cleared automatically when the browser closes. On Chrome, chrome.storage.session persists across service worker restarts within the same browser session but not across browser restarts.
// chrome.storage.local — persists across browser restarts OFFLINE_VAULT_KEY // encrypted entries (ciphertext only) _SESSION_LOCAL_KEY // session mirror for SW restart recovery hv_prefs // UI preferences (timeout, theme) // chrome.storage.session — cleared on browser close SESSION_KEY // session token + loggedIn state VAULT_KEY // decrypted entries (in-memory only) MASTER_KEY // derived master key (never on disk)
The autofill content script (content.js) is injected on-demand only — when the user opens the popup or explicitly triggers fill. It is not injected on every page load.
When injected, the content script operates in an isolated world — it has its own JavaScript context, separate from the page's JavaScript. A malicious page cannot access the content script's variables, and the content script cannot be overridden by page scripts.
Credentials are matched to the current tab's hostname — not the full URL. A credential for github.com will not autofill on evil-github.com. Subdomain matching is configurable per-entry.
TOTP secrets are stored encrypted in the vault like any other credential field. Code generation happens locally in the extension — the TOTP secret is never sent to the server for computation.
The extension auto-locks after a configurable idle timeout (default 15 minutes). Lock state is enforced by the background service worker — the popup cannot display vault data if the service worker considers the session locked.
When locked, the derived keys are wiped from chrome.storage.session. Re-opening the vault requires re-entering the master password to re-derive the keys — no server round-trip, all local.
1. Idle timeout fires (chrome.alarms) 2. background.js clears SESSION_KEY, VAULT_KEY, MASTER_KEY from chrome.storage.session 3. Popup re-renders locked screen on next open 4. User enters master password 5. Argon2id re-derives master key in offscreen document 6. Vault decrypted from local cache — no network required
The extension checks credentials against the Have I Been Pwned dataset using the k-anonymity model. Only the first 5 characters of the SHA-1 hash of a password are sent to the HIBP API. The full hash — and therefore the password — never leaves your device.
const hash = sha1(password) const prefix = hash.slice(0, 5) // sent to HIBP const suffix = hash.slice(5) // never leaves device // HIBP returns all hashes starting with prefix // Extension checks locally if suffix appears in results
If you find a security issue in the HexVault extension, please report it to [email protected]. Do not open a public GitHub issue. We acknowledge all reports within 48 hours and aim to ship a fix within 14 days for critical issues.
The extension source is available for review in our GitHub repository.