-
v0.7.5.0 — 2026-06-24 Stable
released this
2026-06-24 14:55:29 -07:00 | 6 commits to main since this releaseMulti-recipient age encryption (EGB-283): store-scoped recipients.txt encrypts every blob to N age keys (one per team member). Managed via secrets recipients add/rm/list; new secrets reencrypt re-encrypts to the current set. Composes with EGB-712 additive-v2 dual-write. Absence of recipients.txt preserves legacy single-key behavior.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
Source code (ZIP)
-
v0.7.3.0 — 2026-06-08 Stable
released this
2026-06-08 16:30:08 -07:00 | 30 commits to main since this releaseEGB-671 — real install / onboarding scripts.
- secrets join --remote --key : one-command second-machine onboarding that verifies the key decrypts the store before declaring success (empty vault never shows a false VERIFIED; wrong key fails loudly).
- secrets init --remote : wires the remote + pushes the initial store so the first project push won't ff-only-die.
- install.sh: thin bootstrap (checks age+jq+git, prints PATH/next-steps/upgrade/key hint; never edits rc, never runs sudo).
- options.autoAdd prompt on first-manifest scaffold (EGB-677 contract #2).
- Fix: secrets pull now dies loudly on a wrong-key decrypt instead of warning and exiting 0.
282 bats tests pass. Operator security sign-off recorded for the branch HEAD.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
v0.7.2.0 — 2026-06-08 Stable
released this
2026-06-08 14:00:47 -07:00 | 34 commits to main since this releasefeat:
secrets list --jsonmachine-readable output (EGB-699)Adds a --json flag to
secrets listemitting a structured object for tooling/CI (feeds the EGB-671 install scripts): {store, projects[].entries[]}, each entry self-describing via a type discriminator (dotenv→path / external→subtype+path). Mirrors the recursive store walk; jq assembles it so stdout stays pure JSON (notices→stderr); jq required only in --json mode. 7 new bats tests; full suite 261 pass / 0 fail.Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
Source code (ZIP)
-
v0.6.0.1 — 2026-06-08 Stable
released this
2026-06-08 06:23:18 -07:00 | 55 commits to main since this releaseEGB-700:
secrets whichnow prints the .secrets.json schema version in its manifest header line (manifest (... version N):), alongside the store-format line shipped in v0.6.0.0. A singlesecrets whichnow surfaces both the store format and the manifest version. Full bats suite: 237/237. Operator security sign-off recorded.Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
Source code (ZIP)
-
v0.6.0.0 — 2026-06-07 Stable
released this
2026-06-07 17:00:34 -07:00 | 59 commits to main since this releaseEGB-677 stage 2 (EGB-703): self-describing store format +
secrets migrate.Added: a committed
.secrets-formatmarker (fresh stores born v2;secrets whichprints the format);secrets migrate(per-project copy-forward of encrypted blobs to the v2 layout — non-destructive),migrate --dry-run(preview), andmigrate --finalize(the one destructive step, run store-wide; refuses unlesssecrets verifypasses and every blob has its v2 twin, cuts a pre-v2-migrate recovery tag, then drops the old blobs).Changed: the external
propertiesblob is stored as<name>.properties.agein a v2 store (was.gradle-properties.age); push/pull/verify pick the right name automatically so v1 and v2 stores both keep working during a migration.Full bats suite: 236/236. Operator security sign-off recorded.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
Source code (ZIP)
-
v0.5.0.0 — 2026-06-07 Stable
released this
2026-06-07 15:34:02 -07:00 | 66 commits to main since this releaseAdds
secrets verify(EGB-698) — a read-only integrity check.secrets verifycross-checks a project's committed .secrets.json against the store both ways (declared-but-missing blobs + orphaned blobs) and decrypt-tests every dotenv + external blob with the current key.secrets verify --alldecrypt-tests every blob in every project (store-wide integrity sweep). Read-only (plaintext streamed to /dev/null); recurses the whole project tree; exits non-zero on any finding so it can gate CI and the stage-2 migrate --finalize.Full bats suite: 210/210. Operator security sign-off recorded.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
Source code (ZIP)
-
v0.4.0.0 — 2026-06-07 Stable
released this
2026-06-07 13:57:21 -07:00 | 72 commits to main since this releaseEGB-677 stage 1: .secrets.json manifest-driven sync.
Added: committed .secrets.json manifest (version 2; dotenv[]/external[]/options) as the source of truth for what syncs; secrets add command; manifest-aware push (auto-add via options.autoAdd, --frozen, --dry-run, legacy .secrets-files absorb); manifest-driven pull (nested restore, restore-time path rail, empty no-op); jq gating + platform-aware install hints.
Fixed: secrets rekey and secrets list now walk the entire project tree, so nested manifest blobs and external/ blobs survive key rotation (previously rekey could orphan nested blobs under the old key = data loss); bash 3.2 assertion correctness.
Full bats suite: 193/193. Operator security sign-off recorded.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
Source code (ZIP)
-
v0.3.0.0 — 2026-06-07 Stable
released this
2026-06-07 07:22:43 -07:00 | 85 commits to main since this releaseWhat's new
fileexternal type for.secrets-files(EGB-652): whole-file verbatim sync for files outside the project — binary-safe, built for the Android upload keystore. Targets are restored at mode 600, with a.secrets-bakbackup taken if a divergent file already exists. Thegradle.propertiesbasename restriction is waived for this type; all other path-validation rails apply.
Full diff: https://codeberg.org/egbt/secrets/compare/v0.2.1.0...v0.3.0.0
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
v0.2.1.0 — 2026-06-05 Stable
released this
2026-06-05 10:50:13 -07:00 | 90 commits to main since this releaseFixed
secrets rekeyno longer bricks dotenv stores. The re-encrypt loop used a bare"$dir"*glob, which never matches dotfiles — so.envblobs were decrypted to the temp dir but never re-encrypted, leaving them on the old key while the new key overwrotekey.txt. After a rotation, every dotenv file in the store was undecryptable. The glob now mirrors the decrypt loop ("$dir"* "$dir".*), and a round-trip test (push → rekey → pull) pins it. If you ranrekeyon an earlier version andpullnow fails withno identity matched any of the recipients, your blobs are on a pre-rotation key — recover with an oldkey.txtfrom another machine.secrets initon a second machine now fails helpfully instead of half-initializing. Copyingkey.txtinto~/.secretsand then runninginit(instead of cloning your secrets repo) used to rungit init, crash on the existing key, and leave a store with no.gitignore— a state where a laterpushwould commit the private key. The guard now fires beforegit init, leaves the key untouched, and prints the exactgit clonecommand to run — using the real remote URL when your.secrets-storefile declares one.
Security
- The private key can no longer be committed by a store missing its
.gitignore.push,pull, andrekeynow self-heal store protections immediately before anygit add -A: a missing or corrupted.gitignore(one without thekey.txtline) is rewritten, and the pre-commit hook is reinstalled if absent. The heal runs after the fast-forward pull, closing a window where remote history without a.gitignorecould strip protection mid-push. - An already-tracked
key.txtis now untracked automatically..gitignorecan't untrack a file that was committed in the past; the heal now removes a tracked key from the index with a warning that history may need scrubbing and the key may warrant rotation.
Changed
- Project
CLAUDE.mdgained agent skill-routing guidance and an updated test-suite count (126 bats tests, up from 113).
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
v0.2.0.0 — 2026-05-26 Stable
released this
2026-05-26 15:02:55 -07:00 | 99 commits to main since this releaseAdded
- Sync designated keys from external files (Gradle properties). A new committed
.secrets-filesmanifest letssecretstrack specific keys from files that live outside the project root — the motivating case being~/.gradle/gradle.properties, where Android builds read Clerk publishable keys (beaconClerkPkTest,beaconClerkPkLive) that Android Studio's GUI builds can only get from that persistent global file, not from terminal env vars. One entry per line:gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest beaconClerkPkLive. (EGB-531)- push extracts only the named keys and encrypts them under
<project>/external/in the store. - pull merges those keys into the target file, preserving every unrelated key, comment, and line order. An existing managed key is updated in place; the target is backed up to
gradle.properties.secrets-bakbefore each merge. secrets whichreads back the parsed manifest;secrets listshows[external]entries;secrets rekeyre-encrypts external blobs alongside dotenv ones.- Backward compatible: no
.secrets-files→ identical behavior to before.
- push extracts only the named keys and encrypts them under
Security
- The merge is pure bash with exact-string key matching — no
sed/regex. This is deliberate: ased-based substitution would corrupt values containing&,\, or/(common in API keys) and would false-match substring keys (beaconClerkPkvsbeaconClerkPkTest). Values are treated as opaque literals and round-trip byte-exact. - External write targets are validated against an attacker-controlled path. Because the target path comes from a committed manifest, the writable target is locked down: basename must be
gradle.properties, the path must resolve inside$HOME,..traversal is rejected, and symlinked targets (file or parent dir) are refused — blocking a malicious manifest from appending decrypted keys to~/.gitconfig,~/.bashrc, etc. Manifest parsing rejects shell metacharacters and control characters in paths and keys, mirroring the.secrets-storeposture. Writes are atomic (temp-in-same-dir + rename), mode-preserving, and default to600on create. - Storage isolation. External blobs live in a
<project>/external/subdir so the existing broad*.ageglobs (pull, list, rekey) structurally never decrypt them into the working directory or orphan them. - Note on plaintext. Merged Gradle keys are written as permanent plaintext into the target file (
secrets cleardoes not remove them) — appropriate for publishable/low-secrecy values like Clerk publishable keys, by design.
Fixed
secrets rekeywas broken and never completed. Two latent bugs, exposed by the new rekey test: (1)age-keygen -o key.txtaborts because age-keygen refuses to overwrite an existing file — the new key is now generated into a temp dir and moved into place only on success, so the old key survives a failed rotation; (2) theEXITtrap referenced the function-local$tmpdirafter the function returned, erroring underset -uand leaking the plaintext temp dir — the temp dir is now removed explicitly and the trap cleared on normal completion.
Tests
- 80 → 113 (+33). New coverage: manifest parse/read-back, key extraction across
=/:/space separators, merge (preserve unrelated/comments/order, substring-key isolation, sed-metachar value round-trip, duplicate-key collapse, continuation-line safety, idempotency), path validation (wrong basename, outside$HOME, symlinked target, symlinked parent dir), first-create mode600, manifest injection/symlink/unsafe-key rejection, rekey round-trip of external blobs, glob isolation (blob not leaked to cwd),listsurfacing, pre-commit blocking plaintextgradle.properties, workspace (push -w/pull -w) external sync, multi-entry manifests, partial-key push warnings, missing-blob pull warnings, source-side comment/continuation skipping, and backward compatibility.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
- Sync designated keys from external files (Gradle properties). A new committed