Envisible is a CLI tool to safely manage encrypted secrets within your configuration files (YAML, JSON, TOML, .env, etc.) using explicit ENC[...] markers.
Your secrets normally live somewhere your config doesn't: a separate secret store, a .env that's gitignored and passed around by hand, a wiki page that drifts out of date. Envisible keeps the secret next to the setting it belongs to and encrypts only that value, so the file stays safe to commit. That means your configuration is versioned alongside your code — every change to a secret shows up in git history, code review, and rollbacks like any other line. Deploys get simpler too: there's one source of truth in the repo and nothing to sync at release time, since values are decrypted on the fly at runtime (injected into the environment with envisible run, or emitted to stdout for build pipelines). Start with a local keypair and no infrastructure; move to cloud KMS when you want managed keys and audit logs — the file format doesn't change.
Two key-management modes are supported:
- Local keypair (default) — a NaCl Box keypair (Curve25519 + XSalsa20-Poly1305) generated with
envisible keygen. Simple, no network, no cost. The private key file (envisible.key) must be kept off-repo and provisioned to anything that needs to decrypt. - Cloud KMS — an asymmetric RSA-OAEP-SHA-256 key in Google Cloud KMS, AWS KMS, or Azure Key Vault. The private half never leaves the cloud; only the public key is downloaded into
envisible.pub. Decryption authenticates via the cloud SDK's default credential chain (gcloud auth / IAM role / Managed Identity / etc.). See Cloud KMS-backed keys below.
go install github.com/rubysolo/envisible@latestor via homebrew:
brew tap rubysolo/tools
brew install envisibleEnvisible ships with a setup skill so coding agents can wire encrypted secrets into a project for you — discovering plaintext secrets, choosing a key mode (local keypair or cloud KMS), encrypting, and wiring decryption into the runtime, CI, and git.
Claude Code — install the plugin (bundles the skill):
/plugin marketplace add rubysolo/envisible
/plugin install envisible@rubysolo-tools
Then just ask: "set up envisible to encrypt the secrets in this repo" and the agent follows the skill end-to-end.
Other agents (Cursor, Codex, Aider, Zed, …) — the skill is a plain Markdown file at skills/envisible/SKILL.md. Point your agent at it, or copy it into your agent's rules/skills directory. This repo also includes an AGENTS.md describing the project for any agent that lands here.
Plenty of good tools encrypt secrets for version control. Envisible's particular niche is value-level markers in any text file: you wrap a single value (or even a substring of one) in ENC[...], and only that value becomes ciphertext. Everything else — keys, structure, comments, the rest of a connection string — stays plaintext, so diffs and code review stay readable and there's no extra metadata block in your files.
| Tool | Granularity | Works in | Keys | Diff stays readable? |
|---|---|---|---|---|
| Envisible | Per-value ENC[...], including partial substrings |
any text file (YAML/JSON/TOML/.env/INI/…) | local NaCl keypair, or GCP/AWS/Azure KMS (private key stays in the cloud) | Yes — only the marked value is ciphertext |
| SOPS | Per-value (whole file, or keys matching encrypted_regex) |
structured formats; adds a sops metadata block |
age, PGP, AWS/GCP/Azure KMS, HashiCorp Vault | Mostly — values encrypt, plus an appended MAC block |
| git-crypt | Whole file | any file (via .gitattributes) |
GPG or a symmetric key | No — the file is an opaque blob in git |
| dotenvx | Per-value | .env files |
ECIES keypair (private key in .env.keys) |
Yes, within .env |
| Sealed Secrets | Whole Secret | Kubernetes Secret manifests |
in-cluster controller key | N/A (Kubernetes CRD) |
Reach for something else when:
- You want age or PGP keys, HashiCorp Vault, or a mature GitOps/Flux workflow → SOPS is the deeper, more battle-tested ecosystem.
- You want whole files encrypted transparently with no change to how your app reads them → git-crypt.
- Your secrets are Kubernetes
Secrets and you want an in-cluster operator to do the decryption → Sealed Secrets. - You live entirely in
.envfiles and want that workflow polished end to end → dotenvx.
Envisible is the right fit when your secrets don't map one-to-one with key names (e.g. encrypt just the password in a database connection string), you want diffs and reviews to stay legible, or you'd like to start with zero infrastructure (a local keypair) and graduate to managed cloud keys later without changing the file format. It also ships a SKILL.md, so a coding agent can handle the setup for you.
Generate a keypair. This creates envisible.pub (safe to check in) and envisible.key (keep secret / add to .gitignore).
envisible keygen
# Generated keys:
# Public: envisible.pub
# Private: envisible.keyEdit your configuration file and wrap sensitive values in ENC[...].
config.yaml
database:
host: localhost
password: ENC[my-secret-password]Run the encrypt command. This will encrypt the values in-place (with -i).
envisible encrypt -i config.yamlYour file now looks like:
database:
host: localhost
password: ENC[v1:hk9RWQA3BRsmYT4FY...]Use the run command to execute your application. Envisible will decrypt the secrets and inject them into the environment variables.
# Injects decrypted values into the environment
envisible run -e .env -- npm startNote: For non-environment variable use cases (like config files), you can decrypt to a temporary file or use the decrypt command.
decrypt writes the file contents (or a decrypted value) to stdout, so command substitution and pipes just work — no flags needed:
export DB_PASSWORD=$(envisible decrypt --strip secrets.env)
envisible decrypt config.yaml | jq '.database'All informational output (Loading environment…, Starting:, KMS summaries, etc.) goes to stderr, alongside errors. If you want to silence the chatter — for cleaner CI logs or interactive sessions — pass --quiet (or -q); it's a global flag and works on every subcommand:
envisible -q run -- ./deploy.sh # no banner on stderr eitherTo edit secrets without manually decrypting and re-encrypting:
envisible edit config.yamlThis opens the decrypted file in your $EDITOR. When you save and quit, it automatically re-encrypts the file.
Ensure no unencrypted secrets are committed:
envisible check config.yamlIn Cloud KMS mode, the project's asymmetric private key lives in GCP KMS / AWS KMS / Azure Key Vault and never leaves it. Only the public key is downloaded into envisible.pub — the file is safe to commit. Decryption (envisible run, envisible decrypt, envisible edit) authenticates to the cloud at runtime using each SDK's default credential chain.
Cloud-backed values use a v2: envelope marker:
ENC[v2:base64( RSA-OAEP-SHA256(data_key) || nonce || secretbox.Seal(plaintext, nonce, data_key) )]
For each value, envisible generates a random 32-byte data key, seals the payload locally with NaCl secretbox, and wraps the data key with the KMS public key via stdlib RSA-OAEP. No network at encrypt time. At decrypt time the wrapped data key is sent to KMS for unwrapping — one call per ENC[...] value, parallelized inside envisible run.
Plaintexts are unbounded in size (PEM keys, certificates, etc. — the envelope handles them).
You have a key already (Terraform, console, gcloud, etc.):
envisible kms init --provider gcp \
--resource projects/P/locations/L/keyRings/R/cryptoKeys/K/cryptoKeyVersions/NYou want envisible to provision the key (requires KMS-admin permission):
envisible kms create --provider gcp \
--project P --location L --keyring R --name KBoth commands end the same way: envisible.pub is written with the key's public half + the resource pointer. From there, envisible encrypt / envisible run work just like the local-keypair case — no envisible.key is needed.
GCP KMS — create an asymmetric decryption key:
# One-time: provision the keyring and key
gcloud kms keyrings create my-app --location us
gcloud kms keys create my-key \
--keyring my-app --location us \
--purpose asymmetric-encryption \
--default-algorithm rsa-decrypt-oaep-2048-sha256
# Register with envisible
envisible kms init --provider gcp \
--resource projects/MY-PROJECT/locations/us/keyRings/my-app/cryptoKeys/my-key/cryptoKeyVersions/1Auth: any source picked up by Application Default Credentials — gcloud auth application-default login for local dev, workload identity in GKE, service-account JSON via GOOGLE_APPLICATION_CREDENTIALS. Required roles: cloudkms.viewer to read the public key during kms init, cloudkms.cryptoKeyDecrypter at decrypt time.
AWS KMS — create an asymmetric customer-managed key:
aws kms create-key \
--key-spec RSA_2048 \
--key-usage ENCRYPT_DECRYPT \
--description "envisible asymmetric envelope key"
# (note the KeyId from the output)
aws kms create-alias --alias-name alias/my-app --target-key-id <KEY_ID>
# Register with envisible — full ARN or alias ARN both work
envisible kms init --provider aws \
--resource arn:aws:kms:us-east-1:123456789012:key/<KEY_ID>Auth: the AWS SDK's standard credential chain — env vars, ~/.aws/credentials, IMDSv2 on EC2, IRSA in EKS, SSO. Required actions: kms:GetPublicKey for init, kms:Decrypt at runtime.
Azure Key Vault — create an RSA-2048 key:
az keyvault key create \
--vault-name myvault \
--name my-key \
--kty RSA \
--size 2048
# Register with envisible — note the version segment is required
envisible kms init --provider azure \
--resource https://myvault.vault.azure.net/keys/my-key/<VERSION>Auth: DefaultAzureCredential — az login for local dev, managed identity on Azure compute, env vars (AZURE_CLIENT_ID/AZURE_TENANT_ID/AZURE_CLIENT_SECRET) for service principals. Required permissions: keys/get to read the public key during init, keys/decrypt at runtime.
To rotate to a new key version (or a different key under the same provider):
# 1. Create the new version
gcloud kms keys versions create --location us --keyring my-app --key my-key
# (note the new version number, e.g. 2)
# 2. Re-wrap every v2 marker in the file. The secretbox payload stays bit-for-bit
# identical; only the wrapped data key is swapped. envisible.pub is updated
# last to point at the new resource.
envisible kms rotate --to projects/.../cryptoKeyVersions/2 config.yaml .env
# 3. Once you've confirmed everything decrypts with the new version, disable
# or destroy the old version via your cloud provider's UI/CLI.Rotation is same-provider only. For cross-provider migration (e.g. GCP → AWS), decrypt with the old key, then kms init against the new provider and re-encrypt.
envisible.pubis required at decrypt time in v2 mode. It carries the KMS resource pointer. With a local NaCl key, decrypt only needsenvisible.keyandenvisible.pubis optional at runtime — that property changes when you switch to KMS. Commitenvisible.pub.- Mixed v1/v2 files work during transition. If a project has both
envisible.pubpointing at a KMS key (v2) and a leftoverenvisible.key(v1 NaCl), envisible builds a composite decryptor that opens both marker versions. New encrypts go to whichever formatenvisible.pubdescribes. envisible runintroduces a boot-time network dependency. Each unique wrapped data key in the file costs one KMSDecryptcall. With dozens of secrets the SDK parallelizes these into a single round-trip's wall-time, but the dependency is real — provision IAM/credentials accordingly.- Authenticated but not bound. RSA-OAEP authenticates the data key under the KMS public key; NaCl secretbox authenticates the payload under the data key. The pairing between them isn't authenticated as a unit, so an attacker who can write to the file could swap whole envelopes between values and cause a downstream secretbox decrypt to fail. File integrity is git's job in this threat model; this is worth knowing but not a known attack path against a well-managed repo.
Envisible includes helpers to integrate with your local git workflow.
To view decrypted secrets in git diff (when you have the key):
# 1. Configure git (adds diff driver)
envisible git setup
# 2. Add attributes (as instructed by the command above)
# Add this to your .gitattributes file:
# *.yaml diff=envisibleNow git diff will show the decrypted changes for matching files.
To automatically prevent committing unencrypted secrets:
envisible git install-hookThis installs a .git/hooks/pre-commit script that runs envisible check on staged files.
- Markers: Look for
ENC[content]. - Encryption: Replaces
ENC[content]withENC[v1:base64_ciphertext]. - Keys:
- Public Key: Used for encryption. Can be shared.
- Private Key: Used for decryption. Must be protected.
MIT