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:
Brian Majewski 2026-05-26 12:42:04 -07:00
parent ac2195d830
commit 110ac514cc
7 changed files with 816 additions and 13 deletions

View file

@ -1,9 +1,10 @@
#!/usr/bin/env bash
# Pre-commit hook for the secrets repo.
# Rejects staged files matching .env patterns without .age extension.
# Rejects staged plaintext secret files (.env, .dev.vars, gradle.properties)
# without a .age extension.
# This is a safety net, not a security boundary (--no-verify bypasses it).
BLOCKED=$(git diff --cached --name-only | grep -E '\.(env|dev\.vars)' | grep -v '\.age$' || true)
BLOCKED=$(git diff --cached --name-only | grep -E '(\.env|\.dev\.vars|gradle\.properties)' | grep -v '\.age$' || true)
if [ -n "$BLOCKED" ]; then
echo "ERROR: Plaintext secret files staged for commit:"
echo "$BLOCKED"