feat: secrets verify — manifest↔store consistency + decrypt integrity (EGB-698)
Read-only integrity check, the safety net for the stage-2 store migration. Default mode (current project) cross-checks $PWD/.secrets.json against the store both ways — declared-but-missing blobs and orphaned blobs (no manifest entry) — and decrypt-tests every dotenv + external blob with the current key, streaming plaintext to /dev/null so nothing is ever written to disk. `verify --all` decrypt-tests every blob in every project (integrity only; the store carries no manifests, so consistency can't be checked store-wide). Both recurse the whole project tree (find -type f), the same walk rekey/list use, so nested manifest blobs are covered. Exits non-zero on any finding so it can gate `migrate --finalize` and CI. 12 bats tests (clean, nested+external, missing blob, decrypt failure, orphan, missing external, no-manifest die, symlink refusal, --all clean/corrupt/orphan, nested decrypt failure). Full suite 205/205. bash 3.2 clean.
This commit is contained in:
parent
fb71b956da
commit
52528f2e06
4 changed files with 291 additions and 1 deletions
|
|
@ -56,12 +56,13 @@ skips security specialist + red team, and Step 11 skips adversarial review.
|
|||
|
||||
## Architecture
|
||||
|
||||
Single bash script (`secrets`) with subcommands: init, push, pull, list, rm, rekey.
|
||||
Single bash script (`secrets`) with subcommands: init, push, pull, list, rm, rekey, verify.
|
||||
|
||||
- Encryption: `age` with key files (not passphrases — age passphrases are non-scriptable)
|
||||
- Storage: Private git repo at `~/.secrets/`
|
||||
- Convention: Tracks `.env`, `.env.*`, and `.dev.vars` (not `.envrc`, `.environment-*`)
|
||||
- Manifest (EGB-677 stage 1): committed `.secrets.json` is the source of truth for what syncs — `dotenv[]` (project-relative, nested ok, `@` allowed; rail rejects `..`/absolute/symlink) + `external[]` (`properties`/`file`). Push discovery auto-adds (gated by committed `options.autoAdd`, default ON; `--frozen`/`--dry-run` overrides), bootstraps the manifest on first push (written only after ≥1 blob encrypts), and absorbs a legacy `.secrets-files` (gradle-properties → `properties`; on pull the legacy file is superseded with a warning). v1 store layout unchanged in stage 1: nested entries land at `<project>/<relpath>.age`; `properties` blobs keep the legacy `.gradle-properties.age` suffix until the stage-2 store migration. jq is a hard dep only when a manifest exists/is written; manifest-less projects run jq-free (manifest features skipped with a notice). `check_cmd` prints platform-aware install hints.
|
||||
- Verify (EGB-698): `secrets verify` is a read-only integrity check. Default mode (current project) cross-checks `$PWD/.secrets.json` against `$SECRETS_DIR/<project>/` both ways (declared-but-missing blobs + orphaned blobs) and decrypt-tests every blob (dotenv + external) by streaming plaintext to `/dev/null` (never written to disk). `secrets verify --all` decrypt-tests every blob in every project (integrity only — the store carries no manifests, so consistency can't be checked store-wide). Both recurse the whole project tree (`find -type f`, same as rekey/list). Exits non-zero on any finding so it can gate the stage-2 `migrate --finalize` and CI. The store deliberately holds no manifest — `.secrets.json` is committed in each project's own repo and read from `$PWD`.
|
||||
- External files: `.secrets-files` manifest tracks designated keys from files outside the project (e.g. `~/.gradle/gradle.properties`, merged not overwritten — EGB-531) and whole binary files (type `file`, e.g. an Android upload keystore — EGB-652); see below
|
||||
- Workspaces: `--workspaces` flag reads `package.json` workspaces, requires `jq`
|
||||
- Safety: Pre-commit hook rejects plaintext secret files (`.env`, `.dev.vars`, `gradle.properties`)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue