From 50b73083f1a80225038ea668aa709496fb646ac5 Mon Sep 17 00:00:00 2001 From: Brian Majewski Date: Wed, 24 Jun 2026 13:03:20 -0700 Subject: [PATCH] feat: init born-multi recipients.txt + which recipients line (EGB-283) Co-Authored-By: Claude Sonnet 4.6 --- secrets | 21 +++++++++++++++++++++ test/recipients.bats | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/secrets b/secrets index aa6212b..77ee0bf 100755 --- a/secrets +++ b/secrets @@ -1260,6 +1260,11 @@ Your key file has been left untouched." local pubkey pubkey=$(get_pubkey) + # EGB-283: born-multi — seed recipients.txt with this store's public key so + # the store is multi-recipient-ready from day one. Committed (not gitignored), + # staged by the first push like .secrets-format. + printf '# self\n%s\n' "$pubkey" > "$RECIPIENTS_FILE" + info "Done! Your public key is:" echo " $pubkey" echo "" @@ -2195,6 +2200,22 @@ cmd_which() { # v1 from v2 during the migration window. v1 = legacy store, no format marker. echo "format: v$(_store_format)" + # EGB-283: surface the recipient set (store-scoped; one key per team member). + if [ -e "$RECIPIENTS_FILE" ] && [ ! -L "$RECIPIENTS_FILE" ]; then + local rcount=0 rk rn rnames="" + while IFS=$'\t' read -r rk rn; do + rcount=$((rcount + 1)) + [ -n "$rn" ] && rnames="${rnames:+$rnames, }$rn" + done < <(_recipients_dump) + if [ -n "$rnames" ]; then + echo "recipients: $rcount ($rnames)" + else + echo "recipients: $rcount" + fi + else + echo "recipients: single-key (no $RECIPIENTS_FILE_NAME)" + fi + # v2 manifest (.secrets.json): validate and summarize. Validation here # is deliberately fatal (symlink / malformed / unsupported version) so # `secrets which` doubles as the manifest linter. diff --git a/test/recipients.bats b/test/recipients.bats index 6067754..6c73759 100644 --- a/test/recipients.bats +++ b/test/recipients.bats @@ -7,14 +7,14 @@ make_second_identity() { BOB_PUB=$(age-keygen -y "$TEST_TMPDIR/bob.txt") } -@test "push without recipients.txt stays single-key (legacy behavior)" { +@test "push with only-self recipients.txt encrypts to the store key (born-multi)" { init_with_remote + # init now seeds recipients.txt with self — born-multi store. + [ -e "$SECRETS_DIR/recipients.txt" ] create_project_dir myproj run "$SECRETS_BIN" push [ "$status" -eq 0 ] - # No recipients.txt was created by push. - [ ! -e "$SECRETS_DIR/recipients.txt" ] - # Blob decrypts with the store's own key. + # Blob must still decrypt with the store's own key. run age -d -i "$SECRETS_DIR/key.txt" "$SECRETS_DIR/myproj/.env.age" [ "$status" -eq 0 ] } @@ -49,6 +49,8 @@ make_second_identity() { init_with_remote STORE_PUB=$(age-keygen -y "$SECRETS_DIR/key.txt") printf '%s\n' "$STORE_PUB" > "$TEST_TMPDIR/elsewhere.txt" + # Remove the born-multi recipients.txt so we can replace it with a symlink. + rm -f "$SECRETS_DIR/recipients.txt" ln -s "$TEST_TMPDIR/elsewhere.txt" "$SECRETS_DIR/recipients.txt" create_project_dir myproj run "$SECRETS_BIN" push @@ -58,6 +60,8 @@ make_second_identity() { @test "recipients list on a legacy store shows the single derived key" { init_with_remote + # Simulate a legacy store by removing the born-multi recipients.txt. + rm -f "$SECRETS_DIR/recipients.txt" run "$SECRETS_BIN" recipients list [ "$status" -eq 0 ] [[ "$output" == *"single-key"* ]] || false @@ -117,6 +121,8 @@ make_second_identity() { create_project_dir myproj run "$SECRETS_BIN" push before=$(cat "$SECRETS_DIR/key.txt") + # Simulate a legacy store by removing the born-multi recipients.txt. + rm -f "$SECRETS_DIR/recipients.txt" run "$SECRETS_BIN" rekey [ "$status" -eq 0 ] [ "$(cat "$SECRETS_DIR/key.txt")" != "$before" ] @@ -229,3 +235,23 @@ make_second_identity() { [ "$status" -ne 0 ] [[ "$output" == *"No recipient matches"* ]] || false } + +@test "init seeds recipients.txt with the new store key (born-multi)" { + run "$SECRETS_BIN" init + [ "$status" -eq 0 ] + [ -e "$SECRETS_DIR/recipients.txt" ] + STORE_PUB=$(age-keygen -y "$SECRETS_DIR/key.txt") + run cat "$SECRETS_DIR/recipients.txt" + [[ "$output" == *"$STORE_PUB"* ]] || false +} + +@test "which reports the recipient count" { + init_with_remote + make_second_identity + run "$SECRETS_BIN" recipients add "$BOB_PUB" --name bob + create_project_dir myproj + run "$SECRETS_BIN" which + [ "$status" -eq 0 ] + [[ "$output" == *"recipients: 2"* ]] || false + [[ "$output" == *"bob"* ]] || false +}