When we started building Relic, we wanted a secret manager with two strict rules:
- Our servers should never be able to read a user's secrets.
- Secret values should never sit unencrypted on disk.
Most cloud secret managers encrypt data at rest on their backend, which means their servers hold the keys to decrypt your variables. And when you want to sync those variables to Vercel or Cloudflare, you have to give their backend a long-lived API token to your cloud account.
Here is how we designed Relic so encryption, execution, and platform sync all happen on your own machine.
On-device keys
When you set a master password in Relic, your machine generates an RSA-2048 key pair. We run your password through Argon2id (64 MB memory, 3 iterations) to derive an AES-256-GCM key, encrypt your private key locally, and upload only the ciphertext. Your master password stays in your OS keychain (macOS Keychain with Touch ID, Windows Credential Manager, or Linux Secret Service).
Every project gets its own random AES-256 key, wrapped with your RSA public key. When you share a project with a teammate, your client unwraps the project key and wraps a copy with their public key. When you remove a collaborator and rotate keys, your client re-encrypts the project's secrets and history locally under a fresh key.
The Rust runner
In Node.js or Bun, strings in the JavaScript heap are garbage-collected eventually, which makes it hard to guarantee when a decrypted secret is actually wiped from memory.
So when you run relic run -e production -- npm start, the CLI hands the decrypted key-value pairs over FFI to a small native library written in Rust (packages/runner):
- It starts the child process with a cleared environment (
env_clear()), keeping only basic system variables (PATH,HOME,USER,SHELL,TERM, locale,TMPDIR,TZ) plus your secrets. - It disables core dumps (
RLIMIT_CORE = 0on Unix) so a crashing process doesn't write memory to disk. - It holds secret values in
Zeroizing<String>from thezeroizecrate, overwriting the bytes with zeroes as soon as they go out of scope.
Syncing without a middleman (relic push)
You can't always wrap a process with relic run. Serverless platforms like Vercel and Cloudflare Workers expect secrets in their own store.
Instead of asking you to paste your VERCEL_TOKEN or CLOUDFLARE_API_TOKEN into our web dashboard, we built relic push into the CLI:
relic push -e production --target vercel
relic push -e staging --target cloudflare --wrangler-env staging
relic push -e production --target github --github-env production
relic push -e production --target fly
relic push reuses the vercel, wrangler, gh, or fly login you already have on your laptop, decrypts the Relic environment locally, prints a diff plan (+, ~, -, =), and pushes the values straight to the target API or CLI over stdin. Our backend only records an audit log entry that a push happened, never the values.
The entire stack (packages/crypto, packages/runner, packages/tui, apps/cli) is MIT licensed on GitHub. If you want to dig deeper into the key hierarchy or OIDC service accounts, check out the security docs.