Nº 015/Engineering/4 min read/
Zero-Knowledge Cloud Sync in Practice
Every time you run agentsecrets secrets set, two things happen. The value is written to your OS keychain. An encrypted blob is sent to the server. Cloud sync is not a feature you turn on — it is how the system works by default.
Understanding what happens during that second step is the clearest way to understand what zero-knowledge actually means in this context.
The encryption model before anything moves
Before a credential value ever leaves your machine, it has already been encrypted twice — once during storage, and once during transit.
When you run agentsecrets secrets set API_KEY=sk_live_..., the CLI retrieves your workspace key from the local config, generates a random 12-byte nonce, and encrypts the value with AES-256-GCM. What gets sent to the API is base64(nonce + ciphertext), not the value. The server receives an encrypted blob and stores it. It has no access to the workspace key that produced it, so it cannot decrypt what it is holding.
The workspace key itself never travels to the server either. It lives in ~/.agentsecrets/config.json in its decrypted form after login, but the copy the server holds for each team member is encrypted with that member's X25519 public key using NaCl SealedBox. The server cannot combine those copies or derive the plaintext key. Only your private key, which never leaves your device, can decrypt your copy.
What push does specifically
The push command is a bulk migration tool. It exists for developers who already have a working .env file and are setting up AgentSecrets for the first time on an existing project. Rather than running secrets set for every key individually, push reads the .env file, encrypts each value using the workspace key and AES-256-GCM, and sends the encrypted blobs to the server in one operation. It is most relevant for projects using StorageMode 2, which maintains both OS keychain and .env file storage.
The server then applies a second layer of encryption on top of those already-encrypted blobs using Fernet. This is worth understanding precisely: the server is not decrypting your values and re-encrypting them. It is encrypting the ciphertext it received. The plaintext is never accessible to the server at any point in this process.
What this produces is two independent encryption layers with two different keys held by two different parties. To reach the original value, an attacker would need to break both layers in sequence, with your key and the server's key. A compromised server alone produces Fernet-encrypted blobs of AES-256-GCM ciphertext. A compromised client key alone produces AES-256-GCM ciphertext of the plaintext. Neither is useful without the other.
The key hierarchy in full
The encryption model has three layers, each with a specific role.
Your password is processed through Argon2id, a memory-hard key derivation function that makes brute-force attacks expensive regardless of hardware. The derived key decrypts your private X25519 key from the OS keychain. That private key decrypts your copy of the workspace key using NaCl SealedBox. The workspace key is what actually encrypts and decrypts your secret values with AES-256-GCM.
The reason for this structure is team sharing. When you invite someone to a workspace, their X25519 public key is fetched from the server, the workspace key is encrypted with it, and that encrypted copy is stored for them. The server holds one encrypted copy of the workspace key per user and cannot derive the plaintext from any of them. When a new team member logs in, they download and decrypt their copy using their private key, which never left their device.
This means team credential sharing works without any plaintext ever touching the server and without any out-of-band key exchange between team members.
What the server actually holds
After a push, the server holds Fernet-encrypted blobs of AES-256-GCM ciphertext. It holds encrypted copies of the workspace key for each team member, encrypted with their respective public keys. It holds the public keys themselves. It holds metadata: key names, project associations, timestamps.
It holds nothing that can be reversed into a credential value without keys the server has never had access to.
This is what zero-knowledge means in this context. The guarantee is not that the server is trustworthy. The guarantee is that the server's trustworthiness is not a dependency of the system's security. A fully compromised AgentSecrets database produces data that is not useful to an attacker without keys that were never stored there.
Pull and local storage
The pull command reverses the process. The CLI fetches all encrypted blobs for the project, decrypts each one using the workspace key, and writes the plaintext values to either the OS keychain or a .env file depending on the project's StorageMode.
The server at no point sees a plaintext value during pull either. The decryption happens on the client after the blobs are fetched, not on the server before they are sent.
Part 07 covers the agentsecrets env command, what it solves for processes that read from environment variables, and what it means to inject secrets at spawn time rather than retrieving them inside the process.
AgentSecrets is open source and MIT licensed. The full architecture is at agentsecrets.theseventeen.co. The repository is at github.com/The-17/agentsecrets.