Security architecture

How the extension protects
your credentials

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.

Manifest V3

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.

No remote code execution. MV3 prohibits extensions from fetching and executing arbitrary JavaScript at runtime. Every line of code that runs in the extension is reviewed during store submission.
manifest.json
{
  "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.


Minimal permissions

Every permission in the manifest has a specific, documented purpose. The extension requests nothing it doesn't use.

PermissionWhy it's needed
storagePersists the encrypted vault cache and session mirror locally. All stored data is ciphertext — never plaintext credentials.
activeTabReads the current tab's URL to match credentials. Only granted for the active tab at the moment the popup is open — not persistent.
scriptingInjects the autofill content script into pages when the user explicitly triggers fill. Not injected automatically on page load.
contextMenusAdds "Fill with HexVault" to the right-click menu on input fields.
offscreenCreates a hidden page with relaxed CSP to run Argon2id WebAssembly for key derivation. Crypto never runs in the service worker itself.
alarmsSchedules the auto-lock timer. Chrome service workers can't use setTimeout reliably — alarms survive service worker termination.
idleDetects when the system is idle to trigger auto-lock after the configured timeout.
notificationsShows breach alerts and update notifications.
tabsReads tab URL for credential matching when the popup opens. Not used for cross-tab tracking.
!
Host permissions are restricted to https://hexvault.co.uk/* only. The extension cannot read or modify content on any other website except when the user explicitly triggers autofill.

Crypto isolation via offscreen documents

All cryptographic operations — Argon2id key derivation, AES-256-GCM encryption and decryption — run in a dedicated offscreen document, not in the service worker.

popup.js / content.js
Sends encrypted messages to background service worker. Never touches crypto directly.
background.js service worker
Orchestrates requests. Relays crypto operations to offscreen document via chrome.runtime.sendMessage. Holds session state in chrome.storage.session.
offscreen.js isolated
Runs Argon2id (WASM) and Web Crypto API (AES-256-GCM). Only reachable via extension message passing. Cannot make network requests.
chrome.storage.session ephemeral
Holds derived keys and decrypted entries in memory only. Cleared when the browser closes. Never written to disk in plaintext.

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.


What the extension stores — and where

chrome.storage.local (persistent)

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.

chrome.storage.session (memory only)

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.

Storage model
// 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 master key is never written to disk. It lives only in chrome.storage.session which is an in-memory store. If Chrome crashes, the master key is gone — you re-enter your master password to re-derive it.

Autofill and content script security

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.

Credential values are never exposed to page JavaScript. Autofill writes directly to input element values via the DOM API. The filled password is not accessible via window variables or events that page scripts can intercept.

URL matching

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 codes

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.


Session and lock behaviour

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.

Lock sequence
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

Breach checking — k-anonymity

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.

k-Anonymity lookup
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

Responsible disclosure

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.