Features

What's in the box

Safe defaults, sharp diagnostics, zero ceremony.

hardware

Hardware-backed identities

Your private key is generated inside hardware and cannot be read, copied, or exfiltrated: the Secure Enclave of an Apple Silicon Mac (Touch ID), or a YubiKey PIV slot (PIN and a touch) on Intel Macs and Linux. Only the public recipient is ever shared.

init

One-command bootstrap

sopsy init installs missing tools, then writes .sops.yaml, .env.example, an encrypted .env.encrypted, .gitignore safety rules, and .sopsy.yml, and sets up the break-glass key. Idempotent — re-running is always safe.

stores

Key stores & repo policy

Four stores, strongest first: enclave, yubikey, keychain (macOS Keychain or Linux Secret Service), and a plaintext file as a last resort. --store auto picks the best one your machine has; sopsy init --allow-stores decides which ones members may use, hardware only by default. Hardware always beats software, whatever order the list is in.

diagnostics

doctor health checks

macOS, Apple Silicon, Secure Enclave, Touch ID, YubiKey, 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, ykman included.

edit

Friendlier edits

Open an encrypted file through SOPS with automatic file-type detection and nicer errors. Your $EDITOR, your workflow — none of the flag-juggling.

secrets

Scriptable encrypt / decrypt

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')"

recipients

Recipient management

Add, remove, list, and rotate who can decrypt. Every change re-wraps existing secrets via sops updatekeys, so the key set never drifts from the ciphertext. The data key itself stays the same: sopsy rotate-data-key replaces it.

run / set

Secrets straight into a process

sopsy run -- bin/rails server decrypts into one command's environment and writes nothing to disk. sopsy set API_KEY and sopsy unset change one value without an editor, and the value never shows up in a process listing.

check

A CI gate that bites

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.

join / approve

Self-service onboarding

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.

provenance

A built-in audit trail

Every member carries their name, username, when they requested access, and who granted it — approved_by recorded as "Grace Hopper (grace)". sopsy recipient list shows it all in one table.

integrity

Tamper-evident config

.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.

ci

One-secret CI decryption

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

Plaintext stays local. Only ciphertext ships.

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

.env · plaintext

Your real KEY=value secrets. Gitignored automatically — never committed.

02

sops --encrypt --input-type dotenv

Recipients come from .sops.yaml creation_rules. sopsy never touches ciphertext itself.

03

.env.encrypted · committed

Encrypted values + sops metadata. Safe to push — only public recipients live here.

04

sopsy edit → decrypt → re-encrypt

A Touch ID prompt releases your Enclave key, opens your editor, and re-encrypts on save.

The CI gate

Seven invariants stand between you and a leak.

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

Stop secrets before the commit ever lands.

Don't rely on remembering. Run sopsy check from a pre-commit hook and in every CI pipeline, and pair it with a content scanner such as gitleaks or detect-secrets in your own hook manager. Three complementary checks, each catching what the others miss.

$printf '#!/bin/sh\nexec sopsy check\n' > .git/hooks/pre-commit
$chmod +x .git/hooks/pre-commit
sopsy check

Is the encryption intact?

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.

gitleaks

Does anything look like a token?

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.

detect-secrets

Anything new since we audited?

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 same sopsy check in CI is the gate that can't be bypassed.