Add to Chrome

How it works

A technical overview for security reviewers. Everything here is verifiable against the source — including the parts that are less flattering, which we state rather than omit.

Architecture

There is no backend. The extension is a Manifest V3 Chrome extension that generates TOTP/HOTP codes locally with OTPAuth. We operate no servers, hold no accounts, and have no channel through which user data could reach us.

Because there is no server, there is also no server-side key material, no key escrow and no recovery path controlled by us. If a user forgets their password and loses their recovery code, we cannot help them — by design.

Cryptography

Password protection is optional and off by default. With it on:

Cipher
AES-256-GCM, random 96-bit IV per record and per rewrite
Key derivation
PBKDF2-HMAC-SHA256, 600,000 iterations, 128-bit random salt (OWASP guidance)
Master key
256-bit, from crypto.getRandomValues, generated once; the password only wraps it
Fingerprints
HMAC-SHA256 under an HKDF-derived subkey, used to merge records across devices
Unlocked key storage
chrome.storage.session — memory only, never written to disk, cleared when the browser closes, unreachable from content scripts
Auto-lock
Every open / 5 / 15 / 60 minutes idle / until the browser closes
Implementation
WebCrypto only. No primitive is hand-rolled and no crypto library is bundled.

Why two levels of key

Data is never encrypted with a password-derived key directly. A random master key encrypts the records; PBKDF2 output only wraps that master key. Two consequences follow, and both matter:

  • Changing the password rewrites 32 bytes instead of re-encrypting every record, every backup and every export. That bulk rewrite is where storage systems lose data.
  • A 160-bit recovery code can wrap the same master key independently, so a forgotten password is recoverable. The code is shown once, must be typed back to confirm, and is rotated after each use. Nothing is written to disk until the user has confirmed it.

What is encrypted, and what is not

With password protection on, the entire account record is encrypted except its identifier and fingerprint — including the service name, so a stolen profile does not reveal which services the user holds accounts with. Enabling it encrypts the records, verifies them by decrypting and comparing, and only then removes every cleartext copy: browser local storage, sync, and all seven rolling backup snapshots.

Stated plainly, because it matters for your assessment:

  • With password protection off, accounts are stored unencrypted and, if Chrome Sync is on, replicated through the user's own Google account. We never receive them, but Google holds them. Sync can be switched off, which also removes what is already there.
  • The per-site usage history — which account is used on which domain — is stored locally and is not covered by the vault while it is being collected. Enabling password protection deletes it; it can also be switched off independently.

Threat model

Protects against

  • A stolen or copied browser profile directory
  • Infostealer malware that exfiltrates browser data in bulk
  • Someone with physical access to an unattended machine
  • Account records reaching Google through Chrome Sync
  • A backup file being lost or stolen (password-protected export)

Does not protect against

  • Malware running as the user while the vault is unlocked
  • A keylogger capturing the password as it is typed
  • A compromised browser or operating system
  • Compromise of the third-party services the codes are for

In short: this protects data at rest. It does not, and cannot, defend a device that is already under an attacker's control while in use.

Permissions

The manifest declares exactly two permissions and no host permissions:

storage
Accounts and settings in local storage, the unlocked key in session storage, and the optional sync copy
activeTab
Reads the active tab's hostname to highlight the matching account, and captures the tab when the user scans a QR code from screen

There are no content scripts, no tabs, cookies or webRequest permissions, and no web-accessible resources. The extension cannot read or modify the content of any page.

Camera

QR scanning by camera runs on an extension page opened in a tab, not in the popup — a popup is destroyed when it loses focus, which a permission prompt does. This needs no manifest permission: it uses the browser's standard camera prompt, granted per extension origin and revocable in site settings, and it is never requested until the user opens the scanner. Video is decoded locally and never transmitted or stored.

Network egress

The extension makes no automatic requests during normal use, and Manifest V3 forbids remote code. Every connection it can make:

HostWhenCarries user data?
worldtimeapi.org
timeapi.io
Clock-drift check, cached, fails silently. TOTP breaks on a drifted clock.No
authenticator.shWelcome page on install; feedback page on uninstall or rating.No (ordinary web server logs)

No fonts, scripts, styles or images load from third-party hosts. Everything needed to render the interface ships inside the package.

Verifying what we publish

You do not have to take any of the above on trust. The extension is open source, and each release is reproducible:

  1. Download the published .crx from the Chrome Web Store and unzip it
  2. Check out the matching git tag
  3. Run npm ci && npm run build on Node 20 LTS
  4. Compare the resulting dist/ with the unzipped package

A SHA-256 for every file in the produced dist/ is published with each GitHub release as SHA256SUMS-v<version>.txt, in sha256sum format, so step 4 can be a single sha256sum -c run. Differences should be limited to file ordering inside archives and whitespace in minified output across Node patch versions.

Runtime dependencies are deliberately few — OTPAuth, React, jsQR, Zustand, Framer Motion and Lucide — pinned by a committed lockfile. A CycloneDX SBOM is available on request.

Read the source

What we do not have

We are a small independent vendor. If any of the following is a hard requirement for your process, it is better that you know now than three weeks into an evaluation:

  • No SOC 2, ISO 27001 or comparable certification
  • No third-party penetration test commissioned to date
  • No paid bug bounty programme

What we offer instead is full source transparency, a verifiable build, a minimal permission surface, and no server-side infrastructure that could hold your data in the first place.

Talking to us

Vulnerability reports and security questions: security@authenticator.sh. Our disclosure policy, scope, response times and safe-harbour terms are on the security policy page; what is stored and what leaves the device is set out in the privacy policy.

We are happy to answer a written vendor assessment questionnaire or walk a security team through the code.

For anything that is not a security matter — installation, account recovery, billing questions from a deployment — see support. Please do not send vulnerability reports there; that inbox is not treated as confidential.