v0.2.0.0 feat: sync gradle.properties keys via .secrets-files (EGB-531)
Add a committed .secrets-files manifest that lets secrets track designated keys from files outside the project root (motivating case: ~/.gradle/gradle.properties for Android Clerk publishable keys, which Android Studio GUI builds read but terminal env vars can't reach). - push extracts only the named keys, encrypts under <project>/external/ - pull MERGES them into the target, preserving unrelated keys/comments/order - pure-bash merge (no sed/regex): exact-string key match, opaque values - path validator: basename gradle.properties, within $HOME, no symlink/.. - external/ subdir keeps blobs out of the dotenv *.age globs; rekey + list recurse explicitly - which reads back the manifest; list shows [external]; pre-commit blocks plaintext gradle.properties Also fixes two latent bugs in 'secrets rekey' (never completed before, no prior test): age-keygen refusing to overwrite key.txt, and an EXIT trap referencing an out-of-scope local under set -u. Tests: 80 -> 104. Reviewed via /autoplan (CEO/Eng/DX). EGB-531.
This commit is contained in:
parent
ac2195d830
commit
110ac514cc
7 changed files with 816 additions and 13 deletions
25
CHANGELOG.md
25
CHANGELOG.md
|
|
@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to a four-digit MAJOR.MINOR.PATCH.MICRO version scheme.
|
||||
|
||||
## [0.2.0.0] - 2026-05-26
|
||||
|
||||
### Added
|
||||
|
||||
- **Sync designated keys from external files (Gradle properties).** A new committed `.secrets-files` manifest lets `secrets` track 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-bak` before the first merge.
|
||||
- `secrets which` reads back the parsed manifest; `secrets list` shows `[external]` entries; `secrets rekey` re-encrypts external blobs alongside dotenv ones.
|
||||
- Backward compatible: no `.secrets-files` → identical behavior to before.
|
||||
|
||||
### Security
|
||||
|
||||
- **The merge is pure bash with exact-string key matching — no `sed`/regex.** This is deliberate: a `sed`-based substitution would corrupt values containing `&`, `\`, or `/` (common in API keys) and would false-match substring keys (`beaconClerkPk` vs `beaconClerkPkTest`). 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-store` posture. Writes are atomic (temp-in-same-dir + rename), mode-preserving, and default to `600` on create.
|
||||
- **Storage isolation.** External blobs live in a `<project>/external/` subdir so the existing broad `*.age` globs (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 clear` does not remove them) — appropriate for publishable/low-secrecy values like Clerk publishable keys, by design.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`secrets rekey` was broken and never completed.** Two latent bugs, exposed by the new rekey test: (1) `age-keygen -o key.txt` aborts 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) the `EXIT` trap referenced the function-local `$tmpdir` after the function returned, erroring under `set -u` and leaking the plaintext temp dir — the temp dir is now removed explicitly and the trap cleared on normal completion.
|
||||
|
||||
### Tests
|
||||
|
||||
- 80 → 104 (+24). 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`, symlink), first-create mode `600`, manifest injection/symlink rejection, rekey round-trip of external blobs, glob isolation (blob not leaked to cwd), `list` surfacing, pre-commit blocking plaintext `gradle.properties`, and backward compatibility.
|
||||
|
||||
## [0.1.1.0] - 2026-05-09
|
||||
|
||||
### Added
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue