secrets/CHANGELOG.md
Brian Majewski 7e6ddf3a12
v0.1.0.0 feat: multi-store support (EGB-281) (#1)
* feat: multi-store support via .secrets-store + --store flag

Layer four-rule store resolution on top of the existing SECRETS_DIR primitive
so users can manage multiple isolated encrypted stores (work vs personal,
per-client, etc.) without giving up the tool's small-bash-script pitch.

Resolution order (highest first):
  1. --store <dir>  flag (parsed in main pre-pass)
  2. .secrets-store file in cwd or any ancestor up to $HOME
  3. SECRETS_DIR    env var (legacy escape hatch)
  4. ~/.secrets     default

resolve_store() updates both SECRETS_DIR and KEY_FILE so existing single-store
codepaths just work. New cmd_which / where / status report the active store.
cmd_init, push, pull, push_workspaces, pull_workspaces, list, rm, rekey, run,
which all call resolve_store at entry.

Hardening from the EGB-281 adversarial review:
- F1: cmd_run EXIT trap is now a named function (not string-interpolated),
  so paths with apostrophes still get plaintext cleaned up
- F2: symlinked .secrets-store files are skipped, never read
- F3/F4: --store flag rejects flag-shaped values and empty --store=
- F5: HOME unset is detected up-front with a directed error
- F11: check_initialized / check_key give context-aware errors that name
  both recovery paths (git clone vs secrets init) when a teammate clones
  a project bound to a non-existent store on their machine

Tests: 37 → 66 (29 new). HOME=\$TEST_TMPDIR added to test setup so the
walk-up logic stays bounded inside fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add Multiple stores section to README

Five subsections walk users through: how store resolution works, how to
set up a second store on a machine, how to bind a project, how teammates
join a bound project, and how to undo or change a binding. SECRETS_DIR
table entry now points readers at the new --store flag and .secrets-store
file as the preferred mechanisms.

* chore: bump version and changelog (v0.1.0.0)

First formal release. EGB-281 adds multi-store support; this commit
seeds the VERSION file (4-digit MAJOR.MINOR.PATCH.MICRO) and the
CHANGELOG.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:30:28 -07:00

4 KiB
Raw Permalink Blame History

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to a four-digit MAJOR.MINOR.PATCH.MICRO version scheme.

0.1.0.0 - 2026-05-09

Added

  • Multiple stores per user. Run secrets push and secrets pull against any encrypted store directory you choose, not just ~/.secrets/. Use cases: keep work secrets isolated from personal, run a separate store per client, or onboard a teammate to one project without giving them every other project's keys.
  • .secrets-store file for per-project bindings. Drop a one-line file at the project root (e.g. echo work > .secrets-store && git add .secrets-store && git commit) and every machine that clones the project automatically uses ~/.secrets-work/ for that repo. No env var to remember, no per-machine setup.
  • --store <dir> flag for one-shot overrides on any subcommand. secrets --store ~/.secrets-clientA pull myapp works without touching files. Bare names like --store work expand to $HOME/.secrets-work. --store default is sugar for ~/.secrets.
  • secrets which prints the active store path and which rule chose it (flag, .secrets-store file, env var, or default). Aliases: secrets where, secrets status.
  • Directed errors for teammate onboarding. When .secrets-store resolves to an uninitialized store or one missing key.txt, the error message names both recovery paths: git clone <remote> to join an existing store, or secrets --store <name> init to start fresh.
  • Active-store echo. secrets push and secrets pull print ==> Store: <path> (from <source>) whenever a non-default store is active, so wrong-store mistakes surface immediately.

Changed

  • secrets list now hints at secrets which when a non-default store is active.
  • cmd_help documents the four-rule resolution order (--store > .secrets-store file > SECRETS_DIR > default).
  • Error messages for missing init / missing key file are now context-aware: they distinguish between "default store on a fresh machine" and "non-default store referenced by .secrets-store."

Security

  • Path expansion in .secrets-store is literal-only. No eval, no $VAR interpolation, no $(...) execution. A committed .secrets-store containing $(rm -rf ~) reads as plain text, not as a command.
  • Walk-up bounded by $HOME. secrets never reads $HOME/.secrets-store, never walks past $HOME to /, and never follows symlinked .secrets-store files. Symlinks (potential supply-chain attack via committed link to ~/.aws/credentials or similar) are ignored.
  • KEY_FILE re-derives after --store switches stores. Previously, calling secrets pull --store other would have decrypted ciphertext from the new store using the default store's key. Now secrets updates both SECRETS_DIR and KEY_FILE together inside resolve_store().
  • secrets run cleanup survives paths with apostrophes. The EXIT trap is now a named function rather than a string-interpolated command, so projects at e.g. /Users/you/Mom's Mac/code still get plaintext cleared after the wrapped command exits.
  • Test isolation: the bats suite now sets HOME=$TEST_TMPDIR so .secrets-store walk-up cannot wander into the developer's real home directory.
  • --store flag value validation. Empty values (--store=) and flag-shaped values (--store --workspaces) are rejected with directed errors instead of silently mapping to ~/.secrets--something.
  • Unset HOME is detected with a directed error before the script tries to expand it. Helps cron, sudo without -H, and minimal CI runners.

Tests

  • 37 → 66 tests. New coverage: store resolution rules and precedence, walk-up boundaries, command-injection prevention, key-file re-derivation across stores, teammate-onboarding error path, monorepo workspace binding, F1F5 adversarial regressions.