Commit graph

28 commits

Author SHA1 Message Date
Brian Majewski
4d975d447d chore: rewrite onboarding docs + bump version (v0.7.3.0)
README rewritten to the install.sh + init --remote + join flow; dropped the
macOS-only prerequisite (age+jq install hints now cover apt/dnf too). CHANGELOG
entry for EGB-671.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:23:50 -07:00
Brian Majewski
446256caf1 feat: secrets list --json machine-readable output (EGB-699)
Add a --json flag to `secrets list` that emits a structured object for
tooling/CI instead of the human table — feeds the EGB-671 install scripts,
which need to enumerate a cloned store programmatically.

Contract: {"store", "projects":[{"name","entries":[...]}]}, each entry
self-describing via a type discriminator — {type:dotenv,path} or
{type:external,subtype:properties|file,path}. cmd_list_json mirrors the same
recursive store walk as the human list (nested <project>/<relpath>.age +
external/<slug>.age); jq assembles the JSON so paths escape correctly and
stdout stays pure JSON (the non-default-store hint is suppressed; jq is a
hard dep only in --json mode).

Tests: 7 new bats cases (dotenv, nested relpath, external properties + file
subtypes, empty store, pure-stdout-under-notice, store path). Full suite
261 pass / 0 fail.

VERSION 0.7.1.0 -> 0.7.2.0; CHANGELOG/README/CLAUDE.md updated.
2026-06-08 13:56:45 -07:00
Brian Majewski
50476e19fd docs: version-skew nudge + writer-version; bump 0.7.1.0 (EGB-713) 2026-06-08 12:07:51 -07:00
Brian Majewski
9b9af2f30c docs: additive-v2 propagation + optional-GC finalize; bump 0.7.0.0 (EGB-712) 2026-06-08 10:52:34 -07:00
Brian Majewski
5729d84e75 docs: migrate --status + manifest-free copy-forward; bump 0.6.1.0 (EGB-710) 2026-06-08 08:01:44 -07:00
Brian Majewski
2549832f0e docs: sync test counts and which manifest-version note (EGB-700)
- README: test suite total 236 -> 237
- CLAUDE.md: manifest.bats 77 -> 78 tests
- CLAUDE.md: note `secrets which` now prints the .secrets.json schema
  version in its manifest header line alongside `format: vN`

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 06:21:26 -07:00
Brian Majewski
c4da47f320 docs: correct test counts for migrate.bats (236 total)
README.md: test suite count 210 → 236, "both files" → "three files".
CLAUDE.md: testing command lists migrate.bats; Project Structure tree
adds migrate.bats (EGB-703 store-format-v2 migration, 26 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 16:57:53 -07:00
Brian Majewski
e2ad661da5 feat: store-format-v2 self-describing migration (EGB-703, folds in EGB-700)
Stage 2 of EGB-677. Makes the store self-describing and unifies the legacy
external `properties` blob suffix, via a copy-forward migration that never
destroys data until an explicit, gated finalize.

Scope decision (see eureka): the EGB-677 CEO plan's "flatten dotenv blobs to
basename" was dropped as LOSSY — it discards the restore relpath that makes
the store self-describing and adds basename collisions. Engineering analysis
(4 parallel design agents) showed the store is already relpath-self-describing;
the only real v1→v2 delta is the `properties` suffix. This implements the
minimal, safe v2 that achieves the epic's self-describing goal.

What's added:
- `.secrets-format` marker (committed, one line `2`). Absence ⇒ v1 (every
  pre-EGB-703 store). `_store_format()` reads it; `init` stamps fresh stores
  born-v2. `secrets which` prints `format: vN` (EGB-700 folded in).
- `_external_blob_suffix(type)` — single source of truth for the external
  suffix (v2: gradle-properties → properties; file unchanged). push/pull/verify
  all route through it, so v1 and v2 stores never disagree on blob location.
- `secrets migrate` — per-project copy-forward (writes `.properties.age` twins
  beside v1 blobs; idempotent; needs the project manifest), `--dry-run`
  (reports old→new, writes nothing), `--finalize` (store-wide, the only
  destructive step: gates on `verify --all` green + every v1 blob twinned,
  cuts a `pre-v2-migrate-<sha>` recovery tag, stamps the marker, then drops v1
  blobs; refuses without `--yes`/operator confirmation).

rekey and verify --all stay format-agnostic (recursive find walk) — no change.

21 new bats tests (test/migrate.bats): marker/born-v2, format-aware suffix,
v1 back-compat, dry-run, copy-forward idempotency, no-manifest die, finalize
gates (verify-not-green refusal, untwinned refusal, recovery tag, confirmation),
and full v1→window→finalize round-trip. Updated 4 existing tests for the
born-v2 suffix. Full suite 231/231, bash 3.2 clean.
2026-06-07 16:07:31 -07:00
Brian Majewski
e33bc272d2 docs: surface secrets verify in troubleshooting, refresh test counts (v0.5.0.0)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:31:30 -07:00
Brian Majewski
414c02b902 fix: pre-landing review fixes for verify (test assertions, ekeys discard, double-report, docs)
Pre-landing review (0 critical, all informational) auto-fixes:
- Tighten external-corrupt test to assert the decrypt-fail message, not any
  external finding (was *"external"*, now *"does not decrypt"*).
- Pin the verified-count in the nested+external happy-path test so a silent
  under-count (exit 0 while skipping a blob) is caught.
- Account for an unsafe dotenv entry in `expected` so a matching stray blob
  isn't double-reported as both unsafe and orphan.
- Discard the unused external `keys` read field (read -r etype epath _).
- Document the optional [project] positional in the README verify row.

Deferred to EGB-701 (stage-2 dedup): the external blob-path literal and the
find-walk overlap with cmd_rekey/cmd_list. Full suite 210/210.
2026-06-07 15:22:58 -07:00
Brian Majewski
52528f2e06 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.
2026-06-07 14:55:42 -07:00
Brian Majewski
a35bff8d4b docs: correct manifest.bats and total test counts for v0.4.0.0
CLAUDE.md: manifest.bats 58 -> 60 tests (actual @test count).
README.md: total 191 -> 193 tests across both files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 13:53:05 -07:00
Brian Majewski
588f290dcc chore: security review policy + operator runner, doc updates (EGB-677 stage 1)
Add .ship-policy.json (opts out AI adversarial/red-team/security-specialist
review; requires local operator sign-off) and test/run-security.sh (the
operator-local security regression subset). Document the policy in CLAUDE.md
and README, fix stale test counts (manifest.bats 41->58, total 174->191), and
update the storage-recursion note to reflect rekey/list now walking the full
project tree.
2026-06-07 13:12:39 -07:00
Brian Majewski
89e851278b feat: jq gating, platform-aware install hints, stage-1 docs (EGB-677 stage 1)
- jq required only when .secrets.json exists or is being written;
  manifest-less projects run jq-free (features skipped with a notice) —
  clone-and-run for v1 users survives (no-jq fixture excludes /usr/bin,
  macOS ships jq there now)
- check_cmd: platform-aware hints (brew/apt-get/dnf/generic) instead of
  hardcoded brew — correct guidance on Linux/CI
- cmd_help: add command, push flags, manifest section with example
- README: manifest section, external files rewritten around
  .secrets.json (legacy .secrets-files documented as absorbed),
  troubleshooting entries, command table, test instructions
- CLAUDE.md: manifest architecture notes, bash-3.2 '[[ ]] || false'
  testing convention, project structure refresh
2026-06-07 08:58:16 -07:00
Brian Majewski
e0474f3ce9 feat: 'file' external type — whole-file sync for .secrets-files (EGB-652)
Built for the Beacon Android upload keystore: binary files outside the
project root can now ride the encrypted store.

- manifest: 'file <path>' (no keys; keys present = rejected loudly)
- push: encrypts the file verbatim (age is binary-safe)
- pull: restores next to target (atomic same-fs mv), mode 600, TOCTOU
  symlink recheck, divergent existing target backed up to .secrets-bak
- _validate_external_target_path parameterized by type (basename
  restriction stays gradle-properties-only; $HOME/../symlink rails apply
  to both)
- README/help/CLAUDE docs + 7 bats tests (133/133 pass)
2026-06-07 06:43:45 -07:00
Brian Majewski
ba4c53a160 docs: update README for v0.2.1.0
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:29:07 -07:00
Brian Majewski
ffdff4dafb fix: pre-landing review fixes for external-file sync (EGB-531)
Adversarial review of the diff surfaced 6 non-critical findings, all fixed:
- pull now warns + reports when a merge fails (read-only $HOME, full disk)
  instead of silently exiting 0 as if it synced
- push skips a multi-line (continuation) managed value with a warning rather
  than writing a dangling backslash that would corrupt the target
- absent-key pull check escapes regex '.' so systemProp.foo can't false-match
- fix garbled 'Merged 0\n0 key(s)' count when a blob has no '=' lines
- slug gets a cksum suffix so paths that clean to the same string (a/b vs a_b)
  don't overwrite each other's blob
- docs: backup happens before each merge, not just the first

Tests: 112 -> 113 (continuation-value skip).
2026-05-26 14:40:27 -07:00
Brian Majewski
31f2741c44 test: coverage for external-file workspace/multi-entry/warning paths (EGB-531)
Close the high-value gaps surfaced by the ship coverage audit (71% → ~92%):
push -w / pull -w external sync, multi-entry manifests, partial-key push
warnings, missing-blob pull warnings, unsafe-key-charset rejection, symlinked
parent dir refusal, and source-side comment/continuation skipping.

Tests: 104 -> 112. Docs updated to match.
2026-05-26 14:26:41 -07:00
Brian Majewski
110ac514cc v0.2.0.0 feat: sync gradle.properties keys via .secrets-files (EGB-531)
Add a committed .secrets-files manifest that lets secrets track designated
keys from files outside the project root (motivating case:
~/.gradle/gradle.properties for Android Clerk publishable keys, which
Android Studio GUI builds read but terminal env vars can't reach).

- push extracts only the named keys, encrypts under <project>/external/
- pull MERGES them into the target, preserving unrelated keys/comments/order
- pure-bash merge (no sed/regex): exact-string key match, opaque values
- path validator: basename gradle.properties, within $HOME, no symlink/..
- external/ subdir keeps blobs out of the dotenv *.age globs; rekey + list
  recurse explicitly
- which reads back the manifest; list shows [external]; pre-commit blocks
  plaintext gradle.properties

Also fixes two latent bugs in 'secrets rekey' (never completed before, no
prior test): age-keygen refusing to overwrite key.txt, and an EXIT trap
referencing an out-of-scope local under set -u.

Tests: 80 -> 104.

Reviewed via /autoplan (CEO/Eng/DX). EGB-531.
2026-05-26 12:42:04 -07:00
Brian Majewski
ac2195d830 Update repository URLs in README and CHANGELOG to reflect migration to Codeberg 2026-05-15 08:20:56 -07:00
Brian Majewski
7c3a76e8c1
v0.1.1.0 feat: optional remote URL in .secrets-store (EGB-282) (#2)
* chore: ignore .gstack/ (per-project local state)

* feat: optional remote URL in .secrets-store (EGB-282)

A second whitespace-separated token after the store name in .secrets-store
is treated as the store's git remote URL. When a teammate clones a project
bound to a store they don't have locally yet, the directed missing-store
error now fills in `git clone <url> <path>` so they can copy-paste instead
of asking the original setter for the URL.

Backward compatible: single-token .secrets-store files (the v0.1.0.x
format) continue to work and produce the existing `<their-store-remote>`
placeholder.

Security hardening (caught by adversarial review during /ship):
- The rendered git clone line is meant to be copy-pasted by a teammate.
  Without sanitization, `work evil.git;rm -rf ~` would render verbatim
  and execute `rm -rf ~` on paste. The parser now rejects URLs containing
  shell metacharacters (;&|<>$`(){}*?!"'\\), control characters (incl.
  ANSI escape sequences that could spoof terminal output), and embedded
  whitespace. Rejected URLs are dropped with a stderr warning; the error
  falls back to the safe placeholder.
- Switched from `set -- $line` to `read -r spec rest` so the URL field
  isn't glob-expanded or word-split — important so `work *` from a
  populated directory doesn't leak filenames into the URL field.

Tests 72 → 80. New: backward compat, SSH+HTTPS+~/-prefix URL forms,
comment-and-URL form, four named injection vectors (shell metachar,
backtick, $(), ANSI escape), multi-token URL, glob char, and a positive
test asserting standard git URL chars round-trip unchanged.

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

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

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:54:45 -07:00
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
Brian Majewski
edb1614941 Update README to correct the repository URL for the secrets tool
- Changed the clone command for the secrets tool from SSH to HTTPS for easier access.
- Ensured consistency in instructions for downloading the secrets tool across different sections of the README.
2026-05-04 13:31:36 -07:00
Brian Majewski
11b6ef87fa Update README to enhance documentation on secrets run functionality and improve diagram formatting
- Replaced static diagram with a mermaid flowchart for better visualization of secrets management.
- Expanded section on `secrets run` to clarify its operation as a pull → run → clear pipeline.
- Added examples and syntax details for using `secrets run` in various scenarios.
- Included information on automatic clearing of plaintext files after command execution.
2026-03-30 07:21:45 -07:00
Brian Majewski
cc647b922f Update README to clarify code block formatting for diagrams and shell commands
- Changed code block syntax from generic to specific types (`diagram` and `shell`) for better readability.
- Enhanced the presentation of secrets storage and organization examples.
2026-03-24 07:04:09 -07:00
Brian Majewski
e347e73976 Enhance secrets CLI to support additional secret file types and clear command
- Updated file tracking to include `.dev.vars` alongside `.env` and `.env.*`.
- Improved pre-commit hook to block plaintext secret files, including `.dev.vars`.
- Added `clear` command to remove plaintext secret files from the current directory and workspaces.
- Enhanced tests to cover new functionality for `.dev.vars` and the `clear` command.
- Updated documentation to reflect changes in tracked files and command usage.
2026-03-24 06:58:45 -07:00
Brian Majewski
585367b9a6 Add support for package.json workspaces in secrets CLI
- Introduced `--workspaces` flag for `push` and `pull` commands to handle environment files in monorepos.
- Updated README and CLAUDE.md to reflect new workspace functionality and installation instructions.
- Enhanced test suite with cases for workspace operations, ensuring proper encryption and decryption of environment files.
- Improved error handling for missing package.json and workspaces field.
- Increased test coverage from 20 to 25 tests.
2026-03-23 17:01:22 -07:00
Brian Majewski
7eae4ea9a1 initial commit 2026-03-23 16:49:04 -07:00