Secure Enclave identities
Your private key is generated inside Apple Silicon hardware, gated by Touch ID, and impossible to read, copy, or exfiltrate. Only the public recipient is ever shared.
A CLI for SOPS Β· age Β· Apple Secure Enclave
YubiKey Linux Support Coming Soon
sopsy encrypts the secrets in your .env, .yml, and .json files β values only, names left in the clear. The file stays diffable and git blame-able, so you can see exactly when each key appeared and who added it, then commit the whole thing without flinching. Every teammate's key is minted inside their Mac's Secure Enclave.
Secrets decrypt only on Touch ID, unless you used the --without-touch-id flag when you join. The private key never leaves the chip, and any current member can grant the next one access. No server. No subscription. No plaintext in Slack β or anywhere for that matter.
Built on SOPS + age β the encryption tools teams already trust. sopsy just makes them delightful.
Quick start
git init my-app && cd my-app
sopsy init β tools check, Secure Enclave identity, config files.
sopsy edit .env.encrypted decrypts to your editor and re-encrypts on save.
sopsy check β the same command your CI runs.
git add .sops.yaml .sopsy.yml .sopsy.sha .env.example .env.encrypted .gitignore β plaintext .env is already gitignored. Push the ciphertext with confidence.
The new dev clones the repo and runs sopsy join "Alan Turing" β mints their own Secure Enclave key with Touch ID and records a pending request. They commit and open a PR.
Any current member runs sopsy approve "Alan Turing" β vouches for the key, promotes it into .sops.yaml, and re-keys every secret. Who approved, and when, is recorded in .sopsy.yml β sopsy recipient list shows the whole audit trail. Now Alan can decrypt.

sopsy does not replace SOPS. It makes SOPS delightful.
age-plugin-se mints your keypair via Touch ID. The private key is sealed in the Secure Enclave; your public key is recorded pending in .sopsy.yml.
You open a PR; any active member vouches and promotes your public key from pending into .sops.yamlβs recipient list β recording who approved you, and when, in .sopsy.yml.
Every encrypted file is re-wrapped for the new recipient set β so your key, and only listed keys, can open them.
git pull, then Touch ID decrypts into your editor and re-encrypts on save. Commit the ciphertext; plaintext .env stays gitignored.
SOPS stays the encryption engine. age stays the cryptography. The Secure Enclave holds the private key.
sopsy orchestrates all three with safe defaults, great diagnostics, and a scriptable interface β so the right thing is finally the easy thing.
Why not just use sops?
Everything here runs sops + age under the hood. The difference is the friction β the steps you have to remember, the flags you have to know, and the safety rails that aren't there until you build them.
βHand-write .sops.yaml creation_rules, craft .gitignore rules, build .env.example, then encrypt by hand.
βOne idempotent command writes every file and seeds an encrypted .env.encrypted for you.sopsy init
βRun age-plugin-se keygen, copy the public key, paste it into config without fat-fingering it.
βThe Enclave identity is generated, printed prominently, and recorded in config automatically.sopsy init
βHand-edit .sops.yaml, run sops updatekeys on every file β and just trust on faith that the key really belongs to them.
βThe newcomer joins to record a pending request (no access yet); any active member approves β vouching for the key, promoting it, and re-keying every secret in one step.sopsy join β sopsy approve
βgit blame .sops.yaml and hope the commit history tells the story of who let whom in.
βEvery member carries requested_at, approved_at, and approved_by β one command prints the whole audit trail as a table.sopsy recipient list
βManually re-key each file; forget one and a departed key still decrypts new commits.
βEvery recipient change re-wraps the existing secrets via sops updatekeys automatically, so a departed key can't read new commits.sopsy recipient remove
βPass --input-type/--output-type yourself β .env.encrypted has no extension sops recognizes.
βFile type is auto-detected (dotenv / yaml / json / binary); your $EDITOR just opens.sopsy edit
βCall sops --encrypt with the right --input-type, then redirect the output yourself and hope the artifact matches a creation rule.
βsopsy encrypt writes a .encrypted artifact, auto-detecting the format β .env, json, yaml, ini β all encrypt values only; anything else as one blob.sopsy encrypt [file] -o file.encrypted
βsops --decrypt with manual type flags, then redirect β easy to clobber the wrong file or leak plaintext to disk.
βsopsy decrypt prints plaintext to stdout or writes it with -o file.sopsy decrypt file.encrypted [-o file]
βDig through docs to learn which formats support partial, values-only encryption versus whole-file.
βsopsy list-supported-types prints exactly which extensions value-encrypt (.env / .yaml / .json / .ini); everything else is encrypted as a single binary blob.sopsy list-supported-types
βRoll your own grep for tracked plaintext and hope it catches every case.
βSeven hygiene invariants, a clear pass/fail checklist, and a non-zero exit on any violation.sopsy check
βGenerate an age key on a laptop, paste the private half around, hand-edit .sops.yaml, and remember to delete the local copies.
βA guided ceremony provisions a portable key, waits while you store one CI secret (SOPS_AGE_KEY), then deletes the local files and re-keys. A Linux runner with only sops installed can decrypt.sopsy recipient ci
βDig through $PATH, tool versions, and Enclave status by hand.
βA grouped, colorful report you can paste straight into a GitHub issue β always exits 0.sopsy doctor
βNothing reminds you to keep an offline recovery key. Lose the device, lose the secrets.
βOne guided ceremony generates a portable emergency key, waits while you store it offline, then deletes the local copy and registers it β and doctor and check nag until it exists.sopsy recipient break-glass
βNet effect: the same battle-tested crypto, minus the foot-guns β every interactive prompt also has a flag, so a human at a terminal and an unattended CI job run the exact same tool.
What's in the box
Your private key is generated inside Apple Silicon hardware, gated by Touch ID, and impossible to read, copy, or exfiltrate. Only the public recipient is ever shared.
sopsy init writes .sops.yaml, .env.example, an encrypted .env.encrypted, .gitignore safety rules, and .sopsy.yml. Idempotent β re-running is always safe.
macOS, Apple Silicon, Secure Enclave, Touch ID, every external tool, and repo health β a colorful report you can paste straight into an issue. sopsy deps is the remedy: it installs whatever doctor found missing.
Open an encrypted file through SOPS with automatic file-type detection and nicer errors. Your $EDITOR, your workflow β none of the flag-juggling.
One-shot, no-editor crypto for scripts and CI. sopsy encrypt .env -o .env.encrypted and sopsy decrypt .env.encrypted (to stdout, or -o a file). Structured .env / JSON / YAML / INI encrypt values only; everything else becomes one binary blob. Pipe decrypted values straight into your shell β via direnv in .envrc or a wrapper around your app β with no plaintext ever hitting disk:eval "$(sopsy decrypt .env.encrypted | sed -E '/^#/d; /^$/d; s/^([A-Z])/export \1/g')"
Add, remove, and list who can decrypt. Every change re-wraps existing secrets via sops updatekeys, so the key set never drifts from the ciphertext.
Seven hygiene invariants, a pass/fail checklist, and a non-zero exit the moment a plaintext secret is tracked. Needs no key β runs on Linux runners too.
Newcomers sopsy join to mint a key and record a pending request β no access granted yet. Any active member sopsy approves: vouch for the key, promote it, re-key. Requests expire after 72h by default.
Every member carries their name, username, when they requested access, and who granted it β approved_by recorded as "Konstantin Gredeskoul (kig)". sopsy recipient list shows it all in one table.
.sopsy.sha β a SHA-256 checksum of .sopsy.yml plus the admin public key β is refreshed on every write and verified on every read, so hand edits are surfaced instead of silently trusted. sopsy doctor repairs it.
sopsy recipient ci runs a guided ceremony that provisions a portable decryption key for your pipeline. Store one CI secret β SOPS_AGE_KEY β and a Linux runner with nothing but sops installed can decrypt.
How encryption flows
sopsy shells out to sops, which uses age recipients drawn from.sops.yaml. The primary path is dotenv: a plaintext.env becomes an encrypted .env.encrypted that's safe to commit.
Only encrypted files and public metadata belong in Git. The plaintext.env must always stay on your machine.
Your real KEY=value secrets. Gitignored automatically β never committed.
Recipients come from .sops.yaml creation_rules. sopsy never touches ciphertext itself.
Encrypted values + sops metadata. Safe to push β only public recipients live here.
A Touch ID prompt releases your Enclave key, opens your editor, and re-encrypts on save.
The Secure Enclave security model
age1se1β¦ string is safe to commit and safe to share. It is all a teammate or admin needs to grant you access.Encryption & team onboarding
Every teammate must be admitted before they can decrypt. A stranger who clones the repo, encrypted files and all, can never open them β they were never added to the recipient set. The first encryptor is the prime, and holds the offline break-glass pair in a vault for the day every laptop is lost.
Add --git to any command that changes files β init, join,approve, edit, and everyrecipient mutation β and sopsy git adds exactly the files it touched (never your plaintext .env) and prints ready-to-paste commit and pull-request commands. The secrets themselves are written with the scriptable shorthandsopsy encrypt .env -o .env.encrypted β no editor required.
Command reference
Bootstrap an encrypted repository: verify the toolchain, acquire an age recipient, and write every config file. Idempotent β keeps existing files unless --force.
Print a grouped health report β system, tools, repository, recipients β and always exit 0. Repairs a stale .sopsy.sha checksum (its only write). Safe to paste into a bug report.
Self-service onboarding (alias of request-access): generate your Secure Enclave key and record a pending request in .sopsy.yml. Commit, open a PR, and ask any member to approve. Pass -t / --without-touch-id to mint the key with no Touch ID gate β handy for direnv and many-window workflows.
Any active member vouches for a pending key, promotes it into .sops.yaml, and re-keys every secret β recording approved_by and approved_at for the audit trail, with atomic rollback if the re-key fails. Refuses requests older than join_request_ttl (default 72h).
Open an encrypted file in your editor through SOPS, with friendly errors and automatic file-type detection. Resolution: --editor β $EDITOR β $VISUAL β vi.
Manage who can decrypt. add/remove keep .sopsy.yml and .sops.yaml in sync and re-key secrets; list prints every member with their approval provenance; break-glass and ci run guided portable-key ceremonies; keygen mints an Enclave key without registering it.
One-shot, scriptable crypto outside the editor. sopsy encrypt writes a .encrypted artifact (or stdout); sopsy decrypt prints plaintext to stdout or -o a file β ideal for direnv and CI. Structured .env/yaml/json/ini encrypt values only; anything else as one blob.
Print the file formats sopsy understands and which extensions auto-detect to each: dotenv, yaml, json, ini (value-encrypted) plus binary (whole-file). Takes no flags; the longer list-supported-types name still works.
The CI gate: seven hygiene invariants, a pass/fail checklist, non-zero exit on any violation. Never decrypts β needs no key, runs anywhere.
Install the external tools (sops, age, age-plugin-se) with Homebrew. Probes first, installs only what is missing. The remedy that pairs with doctor: --check fails CI if anything is absent, --dry-run previews the brew command.
Generate shell completions for bash, zsh, fish, powershell, and elvish β derived from the CLI definition, so they never drift from the real flags.
A clap-based CLI: sopsy --help and sopsy <command> --help are always authoritative. Every interactive prompt has an equivalent flag. The global --git flag stages exactly the files a command changed and prints ready-to-paste commit + PR instructions β a no-op when nothing was modified.
Install
Installs a prebuilt binary β no Rust toolchain β and pulls the tools sopsy orchestrates (sops, age, and age-plugin-se) automatically. Nothing else to set up.
Builds from source with Cargo. You'll still need sops, age, and age-plugin-se on your PATH β or run sopsy deps to fetch them.
βmacOS-first (v1.3.0). Homebrew installs the prebuilt binary and every prerequisite; the Cargo path needs the Rust toolchain.
The CI gate
sopsy check is the sops-hygiene gate for CI and your pre-commit hook β it verifies the encryption discipline itself, and pairs with content scanners like gitleaks (below). It validates encrypted files by their on-disk sops metadata β never by decrypting β so it needs no private key and no Secure Enclave. Drop it on a Linux runner and it just works.
And when a job must actually decrypt β deploys, integration tests β sopsy recipient ci provisions a portable key in one guided ceremony. Store a single CI secret, SOPS_AGE_KEY, and a Linux runner with nothing but sops installed can decrypt. Never reuse the break-glass key for CI β offline/few-hands and online/every-run are opposite threat models.
Defense in depth
Don't rely on remembering. sopsy setup pre-commit installs a lefthook hook that runs three complementary checks before every commit; sopsy setup github-workflow adds a CI job that scans the entire repository.
The structural gate. Confirms sopsy's seven invariants β plaintext .env never tracked, every managed file genuinely encrypted, a break-glass recipient present. It knows which files must be encrypted, so it's deterministic and needs no key β but it doesn't read file contents looking for secret-shaped strings.
The pattern gate. Regex + entropy, git-aware. Catches known secret shapes β cloud keys, tokens, private keys β anywhere in your staged diff, including files sopsy never manages.
The baseline gate. Scans against a committed .secrets.baseline you triage once; only new, un-audited findings block the commit β so noise stays low and real leaks stand out.
sopsy check proves the sops workflow itself is correct;gitleaks and detect-secrets read the content for secrets that never went through sops at all. What one heuristic misses, another catches β and because the pre-commit hook is advisory (--no-verify skips it), the github-workflow is the gate that can't be bypassed.
Break-glass keys
A break-glass key is a separate emergency age key pair β portable by design, stored offline in a vault and shared with only a few admins. If every developer's Secure Enclave device is lost, it can still decrypt and re-key the repository. sopsy recipient break-glass runs the whole ceremony in one command: generate, hand off, wait for ENTER, delete the local copies, register and re-key.
Lose your only Enclave device with no break-glass key, and every secret becomes permanently undecryptable. That's why doctor and check nag until one exists β and sopsy refuses to remove the sole break-glass recipient. The same guided ceremony powers sopsy recipient ci for your pipeline β but the two keys must never be the same: offline/few-hands and online/every-run are opposite threat models.
Further reading
Join a sopsy-managed repo with sopsy join, get approved, the day-to-day workflow, and troubleshooting.
read βBootstrap a repo, the join/approve membership lifecycle, offboarding, and break-glass procedures.
read βInstall with cargo, browse versions, and read the published crate metadata.
read βThe full API documentation for the sopsy crate, generated from source.
read βSmall, fast, colorful, and macOS-first. Your secrets stay in Git β encrypted β while the key that decrypts them never leaves the chip.