-
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