* 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>
39 lines
4 KiB
Markdown
39 lines
4 KiB
Markdown
# Changelog
|
||
|
||
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.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, F1–F5 adversarial regressions.
|
||
|
||
[0.1.0.0]: https://github.com/bmajewski/secrets/releases/tag/v0.1.0.0
|