Security Architecture
How your API keys are encrypted, what our servers hold, what they can never read, and — just as important — the things this design does not protect you from.
What “zero-knowledge” means here
Your API key values are encrypted on your own device, with a key derived from a master password we never receive. What reaches our servers is ciphertext. We cannot decrypt it, we cannot reset your master password, and a subpoena or a breach of our database yields encrypted blobs rather than credentials.
That claim is narrow on purpose, so here is its exact shape:
- It applies to key values — the secret string itself, and the contents of imported
.envtemplates. - It does not apply to all metadata. The name you give a key, the service it belongs to and its timestamps are stored unencrypted so they can be indexed and listed. See what is visible below — we would rather you read it here than discover it later.
- It is a statement about our servers, not about your machine. Once a key is decrypted on your device — into your clipboard, a
.envfile, or an editor buffer — it is plaintext under your operating system’s protection, not ours.
The cryptography, with real parameters
Both clients — the browser extension and the VS Code extension — use the same parameters and the same envelope format, so a vault written by one is readable by the other. All primitives come from the platform’s Web Crypto implementation; none of it is hand-rolled.
AES-256-GCM
Authenticated encryption: 256-bit key, 12-byte IV, 128-bit authentication tag. Tampering with stored ciphertext makes decryption fail rather than return corrupted output.
PBKDF2-HMAC-SHA-256, 600,000 iterations
Your master password is stretched with a 32-byte random salt. 600,000 iterations matches the OWASP recommendation for PBKDF2-SHA-256. The derived key is marked non-extractable, so browser and Node code cannot read its bytes back out.
Fresh salt and IV every time
Every encryption generates a new 32-byte salt and a new 12-byte IV. Two keys with the same value produce unrelated ciphertext, and no IV is ever reused under a given derived key.
salt ‖ IV ‖ ciphertext+tag
One base64 string of 32 bytes of salt, 12 bytes of IV, then the ciphertext with its 16-byte tag appended. That is the entire record — there is no separate key store to compromise.
A master password must be at least 12 characters. Its strength is the load-bearing part of this design; the iteration count only sets the cost of each guess.
What the server stores
Your encrypted vault backup lives in Google Cloud Firestore, under your user document. It is reachable only through our API: the Firestore security rules deny client access to these collections outright, so no browser SDK — ours or anyone else’s — can read them directly.
| Stored item | What it is | Readable by us? |
|---|---|---|
| Encrypted key values | AES-256-GCM ciphertext under your master password | No |
| Project metadata blob | Project names, URLs, icons, colours and descriptions — encrypted client-side into one opaque string before upload | No |
| Sharing keypair backup | Your ECDH private key, encrypted with your master password, plus the matching public key (public by design) | Private half: no |
| Shared-project keys | Key values encrypted with a per-project data key (DEK) | No |
| Wrapped DEKs | One copy of the project DEK per member, wrapped to that member’s public key | No |
| Key and project identifiers, names, timestamps | Listing and sync metadata | Yes — see below |
| Account and billing records | Email, plan, subscription status, team membership | Yes |
Your master password, the key derived from it, and your recovery key are never sent anywhere. The password lives in the browser’s session storage — memory only, cleared when the browser closes — or, in VS Code, in a process buffer that is zeroed on lock. The local password check is a PBKDF2 verifier stored on your own device and never uploaded.
The metadata that is visible to us
Most security pages stop at “we can’t read your secrets”. Here is the rest of it. When your vault is backed up, each key record carries these fields unencrypted:
- The key’s random identifier, and the identifier of the project it belongs to.
- The name you gave the key (for example “Stripe production”) — so avoid putting anything confidential in a key name.
- The service it is for and its category (for example
openai, “AI & ML”), plus that service’s sensitivity label. - Creation time, last-used time, and whether it is marked as the project default.
- How many keys you have, and when the vault was last synced.
For shared projects, the same key metadata is visible, along with which account added each key and who the project members are. Project names are the exception: they travel inside the encrypted blob, so a project reads as an opaque identifier to us.
What this means in practice. Someone with our database could tell that you hold a production Stripe key and when you last used it. They could not tell you what the key is. If that inference alone is unacceptable in your threat model, name your keys generically — the name is never used for anything but display.
In the browser extension, encrypted keys and this metadata also sync through your browser’s own account sync so they reach your other profiles. That copy is subject to your browser vendor’s protections, not ours.
How a key gets from the browser to VS Code
- You add a key in the browser extension.The value is encrypted in the page with your master password before it is written anywhere. Storage never receives the plaintext.
- The vault backs itself up.The already-encrypted records are uploaded to your account. Project names are encrypted separately into a single blob first. Nothing is decrypted for transport.
- VS Code authorises a device.Sign-in happens in your browser and returns tokens to a local loopback server bound to
127.0.0.1. Tokens are stored in VS Code’s SecretStorage, which uses the operating system keychain. - VS Code downloads the same ciphertext.It fetches the encrypted backup with its bearer token. The server does not decrypt anything; it hands back the blobs it was given.
- You unlock locally.VS Code prompts for your master password, derives the key with the same parameters, and decrypts in-process. The password is not stored, not written to settings, and not sent to any server.
- You use the key.Insert it at the cursor, copy it (the clipboard is cleared after 45 seconds), or generate a
.envfile. From this point the value is plaintext on your machine.
At no point in that sequence does a plaintext key value, or anything that would let us derive one, cross the network.
Account security
Encryption protects the key values. These controls protect the account that holds the encrypted backup — a different job, and worth being precise about.
Two-factor authentication
Optional TOTP (the six-digit-code kind, from any authenticator app). Enrolment only completes after a code from your app verifies, so a misconfigured authenticator cannot lock you out, and the QR code is generated inside your browser rather than fetched from a third-party image service. The stored secret is itself encrypted at rest, and you get ten single-use recovery codes which we keep only as hashes.
Where it is enforced: the admin panel, and authorising a device your account has never used before. Routine token refresh on a device you already approved does not prompt. If you have not enabled it, nothing changes for you.
Devices and sessions
- Every client token is bound to a device identifier and to a session record you can revoke. A token with no device binding is refused.
- Access tokens last 24 hours; the device session is a sliding 30-day window that extends on each refresh, so an idle device eventually has to sign in again.
- Revoking a device invalidates its token on the next request. Verdicts are cached briefly for performance, so revocation can take up to a minute to take effect on an already-warm server instance — we would rather state that than imply it is instant.
Transport
The API is served over HTTPS. The browser extension is additionally pinned by its own manifest: its content security policy permits network connections only to this origin, so a compromised extension page cannot exfiltrate to an arbitrary host. Tokens are sent in request headers. The one exception is the VS Code sign-in handoff, where tokens arrive as parameters on a 127.0.0.1 URL — that hop never leaves your machine, but it does mean the tokens pass through your browser’s history.
What the browser extension can reach
An extension that manages credentials should ask for as little as it can, and you should be able to check that from the store listing. Ours is Manifest V3 and requests storage, windows, sidePanel, activeTab and scripting, with host access to this site only.
- No content scripts. The manifest declares none, so the extension is not running on the pages you browse. There is no background code watching your tabs.
activeTab+scriptinginstead of broad host access. Code runs in a page only when you click a button in the extension, only in that one tab, and only in its main frame — never in cross-origin iframes.- It reads site details, not credentials. That injection scans a page for things like a company name or contact details, or fills those back in. The fill policy explicitly refuses any field that looks like a password, secret, token, API key, one-time code, payment detail or government identifier.
- API keys are never typed into a web page for you. They reach you by clipboard or by generated file, both of which you trigger.
- Network egress is restricted by the manifest to this origin, and only this site may message the extension.
The VS Code extension
- Decryption is local. The extension downloads ciphertext and decrypts in-process with the same parameters as the browser. It never asks a server to decrypt.
- The master password is prompted, never stored. It is not written to SecretStorage, to workspace or global state, or to a settings file, and it is not sent over the network. It is held in a buffer that is zeroed when the vault locks.
- Tokens live in SecretStorage, VS Code’s OS-keychain-backed store — not in
settings.json, where they would be committable. - Auto-lock defaults to 15 minutes of no vault activity. Locking zeroes the password buffer and drops every cached derived key and decrypted project name. Ciphertext is kept; it is not secret.
- Key values stay out of the interface. The tree shows names and services only, and the extension writes no key value to its log. Values appear only where you put them: the editor, the clipboard, or a generated file.
Generated .env files and the clipboard
These are the two places where a key deliberately becomes plaintext, so they deserve to be spelled out rather than buried.
Clipboard
Copying a key from the vault schedules the clipboard to be wiped 45 seconds later, in both the browser and VS Code, and the countdown is shown when you copy. The wipe is skipped if you have since copied something else, so it never destroys work. While it sits there, any application on your machine can read it — that is how clipboards work on every operating system.
Generated .env files
A .env file is plaintext by definition; that is the point of generating one. Once written it is an ordinary file with your project’s default permissions, outside anything we control, and it is not added to your .gitignore automatically. The file carries a “do not commit” header, but the responsibility for keeping it out of source control, backups and container images is yours. Prefer inserting a single key where you need it over generating a whole file you then have to remember to delete.
If you forget your master password
We cannot reset it. There is no support path, no override, and no administrator anywhere who can decrypt your vault — that is the direct consequence of never having your password in the first place. Any product that can reset a master password can also read your data.
The way back in is a recovery key, generated when you set up your vault: a 25-character code with 125 bits of entropy, written in an alphabet that avoids confusable characters so it survives being copied by hand. It encrypts your master password into a blob that is useless to anyone without the key itself.
Two things to know about it. The recovery blob is stored on the device where you created it, so a recovery key alone will not rescue a vault on a machine you no longer have — keep a copy of the key somewhere durable and separate, such as a password manager or paper in a safe. And losing both the master password and the recovery key means the data is unrecoverable, by us or by anyone.
What this does not protect against
Every one of the following is a real limit of this design. If a security page does not have a section like this, it is describing an aspiration rather than a system.
A weak master password
600,000 PBKDF2 iterations set the price of one guess; they do not set the number of guesses required. Anyone who obtained your encrypted backup could attack it offline, at their own pace, with no rate limit we can impose. Against a short or reused password that is a matter of time. Use a long, unique passphrase — this single choice does more for your security than anything else on this page.
A compromised device
Malware, an attacker at your unlocked machine, or a hostile process able to read another process’s memory can capture your master password as you type it, or read decrypted values while the vault is unlocked. Client-side encryption assumes the client is trustworthy. If it is not, nothing here holds.
Other software on your machine
A malicious browser extension or a malicious VS Code extension runs with its own permissions alongside ours. Anything on the clipboard is readable by every application. A generated .env is readable by anything that can read your project directory. We cannot sandbox the rest of your system.
A key you have already used somewhere else
Once you paste a key into a terminal, a CI dashboard, a chat message or a screenshot, it lives there too. We protect the copy in your vault; we cannot protect the copies you make.
What your metadata reveals
As set out above, key names, services, counts and timestamps are stored unencrypted. That is enough to profile which providers you depend on. Only the values themselves are protected.
Someone who steals an active session
A valid token downloads your encrypted backup — that is what it is for. It does not decrypt it: without your master password the attacker holds the same ciphertext we do. Two-factor authentication is what stops a stolen website password from turning into a new authorised device, which is why we recommend turning it on.
A team member who has already seen a secret
Removal and key rotation stop future access. They cannot un-see a value someone already decrypted. Rotate the credential at the provider when someone leaves.
Us, if we shipped malicious client code
The encryption runs in code we publish. A future release could, in principle, be built to exfiltrate your password — that is true of every client-side-encrypted product, including the ones that do not mention it. What limits it is that the clients are inspectable and the extension’s manifest restricts where it may send data at all.
Reporting a vulnerability
If you find something that contradicts this page, we want to hear it before anyone else does. Write to gauthier.bros@gmail.com with enough detail to reproduce it. Please do not include real API keys or customer data in a report — use test credentials and redacted values.
This page describes the system as implemented in the current clients and backend. It is maintained alongside the code rather than written once for marketing, and it is meant to be checkable: the cryptographic parameters, the permission model and the stored fields described here are all observable in the published extensions and in network traffic from your own machine.