_find_secrets_store_file used to set _LAST_FOUND_AT inside the $(...) subshell that resolve_store invoked it from, so the parent shell never received the value and `secrets which` reported `source: .secrets-store file ()` with empty parens. Function now returns a tab-separated <dir>\t<source-path> tuple; resolve_store splits it in the parent shell. Caught by /land-and-deploy post-merge fresh-clone verification — the existing test grepped for the substring ".secrets-store file" which matched the broken truncated form. Tightened to assert the full file path appears in the parenthetical. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
46 lines
4.7 KiB
Markdown
46 lines
4.7 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.1] - 2026-05-09
|
||
|
||
### Fixed
|
||
|
||
- `secrets which` now prints the full path of the `.secrets-store` file that won resolution. Before this fix, `_find_secrets_store_file` set `_LAST_FOUND_AT` inside a `$(...)` subshell, so the parent shell never saw it; the source line read `.secrets-store file ()` with empty parens. The function now returns a tab-separated `<dir>\t<source-file-path>` tuple that `resolve_store` splits in the parent shell. Caught by the `/land-and-deploy` post-merge fresh-clone check; the existing test was too lenient and matched the truncated form. Test tightened to assert the full path appears in the parenthetical.
|
||
|
||
## [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.1]: https://github.com/bmajewski/secrets/releases/tag/v0.1.0.1
|
||
[0.1.0.0]: https://github.com/bmajewski/secrets/releases/tag/v0.1.0.0
|