Security Architecture and Data Flow
Last updated: August 14, 2026
This page explains how Checker for Gmail™ separates Gmail, IMAP/SMTP, KeyLockr, and the SafeX website backend. The core rules are that mail data never enters the SafeX backend, long-lived secrets persist only within the smallest necessary boundary, and plaintext keys exist only in memory or the browser session for the operation that needs them.
1. Trust-boundary overview
- The extension connects directly to Gmail Atom, the Gmail API, or the IMAP/SMTP provider you select. Message lists, bodies, attachments, searches, and mail actions are never sent to gmail.safex.cloud.
- Google Chrome uses Chrome Identity. Chrome manages the access token, and the extension does not persist it.
- Supported non-Chrome Chromium browsers use Google Web OAuth. SafeX provides only the stateless /oauth/exchange and /oauth/refresh proxy endpoints; it does not provide a Gmail API proxy.
- IMAP/SMTP connects to the provider through the optional same-device local mail component. Neither the extension nor that component sends mail or credentials to the SafeX website.
2. Web OAuth transport protection
- The extension generates a PKCE verifier/challenge and random state, and accepts only an exact redirect origin, path, and state match.
- Every exchange or refresh creates a fresh ephemeral X25519 key and nonce. The extension uses the SafeX X25519 public key pinned in the package and an authenticated box to encrypt the authorization code, PKCE verifier, or refresh token.
- The proxy authenticates the ciphertext, decrypts it only in process memory, injects the Google client secret held only in the server environment, and forwards the request to Google. The Google response is encrypted back to that request's ephemeral client key.
- The proxy rejects plaintext, malformed, or unauthenticated requests and responses. OAuth authentication values are not written to a database, file, server session, application log, or /oauth access log.
This application-layer encryption prevents TLS/CDN intermediaries from reading or modifying the OAuth payload. Intermediaries may still observe source, destination, timing, and traffic size. Because the proxy must obtain plaintext in memory before forwarding to Google, this is not end-to-end encryption from the extension to Google.
Wire v1 does not keep a server-side nonce replay set. A captured encrypted refresh request can be replayed, but without the original ephemeral private key the attacker can observe only encrypted response status or timing and cannot decrypt the response. HTTPS, production in-memory rate limits, no OAuth access log, and a fresh nonce for each extension request bound this risk. A future wire version should add explicit direction separation and replay hardening.
3. Local browser OAuth vault
The diagram below shows when this vault stands in your way: which paths require it, that the first time you choose between a local password and KeyLockr, and that the two tasks are independent of each other.
- Each browser profile generates its own random vaultId. The vaultId is a public local salt, not a password or key.
- k_oauth is derived from a random data-encryption key (DEK) with HKDF-SHA-256. Derivation binds both an independent OAuth crypto domain and that profile's vaultId, so it is not reused as a mail, lookup, manifest, or AppData subkey.
- Each account's refresh grant is encrypted with AES-256-GCM and AAD containing the schema, vaultId, keyId, and account key before it is written to chrome.storage.local. Moving ciphertext between accounts or vaults causes authentication to fail.
- Short-lived access tokens never enter persistent storage and exist only in chrome.storage.session. Each record is bound to the current grant by a SHA-256 digest of the refresh token, preventing a stale session token from being used with a replacement grant. Closing the browser removes the access token and unlocked DEK.
- A legacy plaintext grant is migrated only after the vault is unlocked. A successful encrypted write atomically replaces the old plaintext container. Key mismatch, ciphertext corruption, or a locked vault fails closed and never deletes data or falls back to plaintext.
4. Password mode
The user password derives a key-encryption key (KEK) with Argon2id13 using three operations and 64 MiB of memory. The KEK is used only to unwrap a random 256-bit DEK; neither the password nor KEK is persisted. The manifest stores only KDF salt/parameters, the AES-GCM-wrapped DEK, a non-secret keyId, and an integrity MAC. After unlock, the DEK is stored only in chrome.storage.session, so the password is entered once per browser session. SafeX cannot recover a forgotten password.
5. KeyLockr mode
- The KeyLockr data_filekey is not a user password and is not used directly as an OAuth encryption key. It is a high-entropy KEK released briefly after phone authorization, used only to unwrap the vault DEK, and overwritten when the callback ends.
- KeyLockr AppData stores and synchronizes only the encrypted portable IMAP/SMTP account bundle: account settings, credentials, local-mail policy, and server-retention state. KeyLockr is an optional third-party remote persistence service. It sees authenticated ciphertext and required non-secret envelope metadata (schema version, key ID, app tag), but that remains a third-party storage boundary.
- The canonical MsgPack envelope sent to KeyLockr contains exactly header, wrappedDEK, and stateCt; decrypted stateCt contains only schemaVersion, accounts, and updatedAt. app_set_data uses an opaque revision for compare-and-swap. The local keylockr replica is only a verified copy of the remote envelope and revision, not storage for profile-private content.
- Templates (profile-level), per-account signatures, and scheduled-send jobs (recipient, subject, body, time, and state) use an independent HKDF domain and remain encrypted only in browser-managed IndexedDB database safex-password-vault, object store replica, record key keylockr-profile:<keyId>. The manifest is only in chrome.storage.local and the unlocked DEK only in chrome.storage.session; no user-visible plaintext JSON file exists.
- Templates, signatures, scheduled messages and bodies, Gmail data, OAuth tokens/grants/ciphertext, the user vault password, and k_oauth are never written or sent to KeyLockr.
- Two profiles may share the same encrypted IMAP/SMTP account bundle and DEK through KeyLockr. Each profile keeps independent templates, signatures, scheduled sends, local vaultId, and OAuth ciphertext. Profiles may therefore reuse the same IMAP settings while connecting different Gmail accounts without obtaining each other's private content.
Public AppData packet fields
This table describes only the format and data categories. It contains no real key, ciphertext, account value, or server secret.
| Layer / field | Wire shape | Data boundary |
|---|---|---|
header |
schemaVersion, keyId, app_tag |
Fixed non-secret envelope metadata; schemaVersion is currently 2. |
wrappedDEK |
{iv, ct} |
Authenticated DEK ciphertext wrapped by the data_filekey released briefly after phone authorization. |
stateCt |
{iv, ct} |
After decryption, exactly schemaVersion, accounts, and updatedAt; accounts contains only portable IMAP/SMTP settings, credentials, and related policies. |
revision |
Opaque CAS token (outside the canonical envelope) | app_set_data commits only while the revision read by the client is still current; a conflict fails visibly. |
6. Revocation, deletion, and key changes
- Disconnect first removes the selected account's local encrypted grant and session access token, then makes a best-effort revocation request directly to Google. The extension does not proactively delete other accounts' local grants, but Google revocation may also invalidate other tokens for the same project; an affected account will require authorization again.
- When Google reports invalid_grant, the extension clears data only if the current grant still matches the refresh snapshot, preventing an older request from deleting a newer authorization.
- If a retired review build left an OAuth container tied to a different vault DEK/keyId, an upgrade does not attempt unauthenticated rewrapping or legacy-key migration. Startup and inspection do not delete ciphertext. The extension lists the container's public account indexes and clears the complete local container and session access tokens only after one explicit user confirmation, then requires each affected account to be authorized again. Because the refresh token cannot be decrypted, this recovery path cannot revoke access at Google; the user can revoke it manually from Google permissions. An ordinary password-to-KeyLockr or KeyLockr-to-password mode switch that preserves the same DEK does not affect OAuth grants.
- Uninstalling the extension causes the browser to remove that profile's local and session storage. Removing a KeyLockr device pairing does not delete the remote AppData still used by other devices.
7. Local-component update trust chain
- Update checks occur only after the user has granted the optional nativeMessaging permission. The extension service worker then invokes the separate com.safex.mail_updater host; without permission it does not probe, and there is no resident background daemon.
- The extension receives only installed and target versions plus fixed status codes. It displays an Accounts-page notice when an update exists and requests installation only after the user selects “Update now.” The extension does not use the downloads permission and does not directly download or execute programs.
- The updater is pinned to gmail.safex.cloud/native-mail/update/v1 and rejects cross-origin redirects. Its strict manifest must pass the dedicated SafeX Mail Ed25519 key, monotonic sequence, architecture, protocol, exact-size, and SHA-256 checks. Every Mach-O must also pass Apple Developer ID, Team ID YG785Y343S, identifier, hardened-runtime, secure-timestamp, designated-requirement, and Gatekeeper verification.
- Helper, updater, and bridge install through a same-filesystem staging, backup, and journal transaction, with the bridge last as the compatibility commit point. Failure or interruption rolls back; an unsafe rollback explicitly requires manual reinstallation. Publishing uploads payloads first, switches the signed manifest second, and removes old payloads last. The website update endpoints return 404 for any missing file, signature mismatch, or hash mismatch.
8. Backend and testing controls
The production website service has no mail database, token database, migrations, or server-side user sessions. Each OAuth handler is a standard-library http.Handler built with immutable configuration, preventing tests or multiple services that share the package from overwriting one another's client or key settings. Sensitive OAuth endpoints write fixed JSON responses directly and do not use generic success helpers that add an envelope or log responses.
The authorized security-testing environment is a minimal separate service. It registers only a root description, healthz, strict /key_enc, and the two OAuth endpoints; generic signing, build-version, and .well-known routes are not registered at all. It stores no token or mail data and may disable in-memory rate limiting under written authorization for DAST. A test extension package is visibly marked with its origin and public-key fingerprint and uses a separate test artifact name (the test-chrome-mv3 directory and a -test-chrome.zip archive) so it cannot be confused with the production store package.
9. Security limitations
Local encryption protects persistent storage while the browser is closed and limits data exposure across profiles and remote services. It cannot protect a running browser session that is fully controlled by malware, a browser vulnerability, a privileged malicious extension, or an attacker already signed in to the operating-system account. Users should keep the browser and operating system current, install only trusted extensions, and close the browser or lock the vault when it is not in use.
10. More information
For data categories, retention, and user deletion controls, see the Privacy Policy. For manual installation, updates, and verification of the local mail component, see Local mail component.