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>
This commit is contained in:
Brian Majewski 2026-05-09 14:54:45 -07:00 committed by GitHub
parent 1bb729f73b
commit 7c3a76e8c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 296 additions and 20 deletions

View file

@ -267,6 +267,14 @@ git add .secrets-store
git commit -m "use work secrets store"
```
For teammates who haven't set up the store yet, you can include the store's git remote URL on the same line so they don't have to ask you for it:
```bash
echo "work git@github.com:acme/work-secrets.git" > .secrets-store
```
That second token (whitespace-separated) is optional, ignored when the store already exists locally, and used as a copy-paste-ready hint in the missing-store error message when it doesn't. See "Joining a teammate's bound project" below.
After that, every `secrets push` / `secrets pull` from this project (or any subdirectory) automatically uses `~/.secrets-work`. Teammates who clone the project get the same binding for free — the file is in the repo.
When you push or pull from a non-default store, `secrets` echoes which one is active so you can spot mistakes immediately:
@ -278,7 +286,9 @@ When you push or pull from a non-default store, `secrets` echoes which one is ac
#### Joining a teammate's bound project
If you clone a project that has a committed `.secrets-store: work` file but you don't have `~/.secrets-work` set up locally, `secrets pull` will tell you exactly what to do:
If you clone a project that has a committed `.secrets-store: work` file but you don't have `~/.secrets-work` set up locally, `secrets pull` will tell you exactly what to do.
When the original setter included the remote URL in `.secrets-store` (recommended), the error fills in the actual `git clone` command for you to copy-paste:
```
ERROR: Store not initialized: /Users/you/.secrets-work
@ -286,16 +296,16 @@ ERROR: Store not initialized: /Users/you/.secrets-work
This path doesn't exist on this machine yet.
If you're joining a teammate's existing store:
git clone <their-store-remote> /Users/you/.secrets-work
git clone git@github.com:acme/work-secrets.git /Users/you/.secrets-work
# then copy their key.txt to /Users/you/.secrets-work/key.txt
If you want a fresh new store at this path:
secrets --store /Users/you/.secrets-work init
```
You'll need two things from the teammate who set it up:
If the URL wasn't in the file, the error shows `<their-store-remote>` as a placeholder — you'll need to ask the teammate who set it up. Either way, you need two things to finish onboarding:
1. **The git remote URL** of the work-secrets repo — clone it to `~/.secrets-work` (or wherever the `.secrets-store` file resolves to on your machine).
1. **The git remote URL** of the work-secrets repo — clone it to `~/.secrets-work` (or wherever the `.secrets-store` file resolves to on your machine). The `.secrets-store` file may already include this for you.
2. **The age key file** (`key.txt`) — same as standing up any new machine. AirDrop, scp, or USB.
Once both are in place, `secrets pull` works.