* 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>
4 KiB
4 KiB
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 pushandsecrets pullagainst 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-storefile 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 myappworks without touching files. Bare names like--store workexpand to$HOME/.secrets-work.--store defaultis sugar for~/.secrets.secrets whichprints the active store path and which rule chose it (flag,.secrets-storefile, env var, or default). Aliases:secrets where,secrets status.- Directed errors for teammate onboarding. When
.secrets-storeresolves to an uninitialized store or one missingkey.txt, the error message names both recovery paths:git clone <remote>to join an existing store, orsecrets --store <name> initto start fresh. - Active-store echo.
secrets pushandsecrets pullprint==> Store: <path> (from <source>)whenever a non-default store is active, so wrong-store mistakes surface immediately.
Changed
secrets listnow hints atsecrets whichwhen a non-default store is active.cmd_helpdocuments the four-rule resolution order (--store>.secrets-storefile >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-storeis literal-only. Noeval, no$VARinterpolation, no$(...)execution. A committed.secrets-storecontaining$(rm -rf ~)reads as plain text, not as a command. - Walk-up bounded by
$HOME.secretsnever reads$HOME/.secrets-store, never walks past$HOMEto/, and never follows symlinked.secrets-storefiles. Symlinks (potential supply-chain attack via committed link to~/.aws/credentialsor similar) are ignored. KEY_FILEre-derives after--storeswitches stores. Previously, callingsecrets pull --store otherwould have decrypted ciphertext from the new store using the default store's key. Nowsecretsupdates bothSECRETS_DIRandKEY_FILEtogether insideresolve_store().secrets runcleanup 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/codestill get plaintext cleared after the wrapped command exits.- Test isolation: the bats suite now sets
HOME=$TEST_TMPDIRso.secrets-storewalk-up cannot wander into the developer's real home directory. --storeflag value validation. Empty values (--store=) and flag-shaped values (--store --workspaces) are rejected with directed errors instead of silently mapping to~/.secrets--something.- Unset
HOMEis 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.