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.
$brew install kigster/tap/sopsy
$cargo install sopsy
sopsy init bootstrapping your encrypted repository ✔ git repository ✔ found sops > Generate a Secure Enclave identity? Yes ✔ created Secure Enclave identity (Touch ID) ℹ private key never leaves the Enclave ➜ public recipient age1se1q2a48h0242huw4… > Your name (key owner): Konstantin ✔ .sops.yaml ✔ .env.example ✔ .env.encrypted ✔ .gitignore ✔ .sopsy.yml ✔ .sopsy.sha > Set up a break-glass emergency key? Yes ✔ wrote break-glass.private + .public ⚠ store both offline in 1Password, then continue press ENTER once the keys are saved … ✔ removed local key files ✔ recorded break-glass in both config files ✔ repository bootstrapped
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.
sopsy join
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.
sopsy approve
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.
sops updatekeys
Every encrypted file is re-wrapped for the new recipient set — so your key, and only listed keys, can open them.
sopsy edit
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.
Task
raw sops + age
with sopsy
Bootstrap a repo
✕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
Get a hardware identity
✕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
Onboard a teammate
✕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
Audit who has access
✕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
Rotate / offboard keys
✕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
Edit an encrypted file
✕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
Encrypt a file in one shot
✕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
Decrypt to stdout or a file
✕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]
Know what's value-encryptable
✕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
Guard CI against leaks
✕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
Give CI decryption access
✕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
Diagnose a broken setup
✕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
Stay disaster-proof
✕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.
01
Your real KEY=value secrets. Gitignored automatically — never committed.
02
Recipients come from .sops.yaml creation_rules. sopsy never touches ciphertext itself.
03
Encrypted values + sops metadata. Safe to push — only public recipients live here.
04
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.
# Alan Turing's MacBook — branch off maingit checkout -b sopsy/add/alan-turing# request to join, and stage the change with --gitsopsy join "Alan Turing" --git# …trade the thumb for --without-touch-id ➜ pending request written to .sopsy.yml ✔ git add .sopsy.yml .sopsy.sha — staged for you ◇ ready-to-paste commit + PR printed below ↓git commit -m "Request access" && git push
# Any already-approved engineer — same branchgit fetch && git checkout sopsy/add/alan-turing# vouch out of band, then approve + stage with --gitsopsy approve "Alan Turing" --git# …or bare `sopsy approve` to triage every request ✔ re-keyed every secret for the new recipient ✔ staged the re-keyed files — commit + PR printedgit commit -m "Approve Alan Turing" && git push ✔ merge the PR — Alan can now decrypt
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
sopsyinit
Bootstrap an encrypted repository: verify the toolchain, acquire an age recipient, and write every config file. Idempotent — keeps existing files unless --force.
sopsydoctor
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.
sopsyjoin<name> [-t]
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.
sopsyapprove<name>
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).
sopsyedit<file>
Open an encrypted file in your editor through SOPS, with friendly errors and automatic file-type detection. Resolution: --editor → $EDITOR → $VISUAL → vi.
sopsyrecipientadd · remove · list · keygen · break-glass · ci
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.
sopsyencrypt · decrypt<file>
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.
sopsytypesalias: list-supported-types
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.
sopsycheck
The CI gate: seven hygiene invariants, a pass/fail checklist, non-zero exit on any violation. Never decrypts — needs no key, runs anywhere.
sopsydeps
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.
sopsycompletion<shell>
Generate shell completions for bash, zsh, fish, powershell, and elvish — derived from the CLI definition, so they never drift from the real flags.
sopsy--help· global --git
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
$brew install kigster/tap/sopsy
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.
requires Rust
$cargo install sopsy
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.
$sopsy check && echo "secrets hygiene: OK"
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.
$sopsy recipient ci
01.env is not tracked by git
02.env is gitignored
03.sops.yaml valid + has creation_rules
04every encrypted file matches a creation rule
05no plaintext secrets tracked
06every encrypted file carries sops metadata
07a break-glass recipient is configured
all checks passed · exit 0
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.
$sopsy setup pre-commit
$sopsy setup github-workflow
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.
# one guided ceremony: generate → store → delete → registersopsy recipient break-glass -o break-glass✔ wrote break-glass.private + break-glass.public⚠ copy both into 1Password / a vault, then continue press ENTER once the keys are saved … ✔ deleted both local key files ✔ registered break-glass recipient✔ re-keyed every secret (sops updatekeys)
Further reading
Small, fast, colorful, and macOS-first. Your secrets stay in Git — encrypted — while the key that decrypts them never leaves the chip.
$brew install kigster/tap/sopsy
$cargo install sopsy