Make an unlocker's ID the name of its directory (closes #98)
check / check (push) Failing after 2s

Keychain and Secure Enclave unlocker IDs were the creation time to the
minute plus the host name, and passphrase unlocker IDs the time to the
minute, so two created within one minute shared an ID, and `unlocker
select`, `unlocker remove` and the selection after `unlocker add` acted on
the older one. Every unlocker's ID is now its directory name, unique in
its vault. `vault.ListUnlockers` returns each unlocker's metadata keyed by
that name, so `unlocker list` and shell completion no longer find IDs by
matching metadata. PGP unlocker IDs were `pgp-<fingerprint>`; a second
PGP unlocker for one key is refused by comparing fingerprints in metadata.

Model: opus-5-5
This commit was merged in pull request #109.
This commit is contained in:
2026-10-04 21:25:00 +02:00
parent 1a23fd3125
commit 0e6a4afb71
20 changed files with 189 additions and 401 deletions
+6 -29
View File
@@ -1,10 +1,10 @@
package cli
import (
"path/filepath"
"maps"
"slices"
"strings"
"git.eeqj.de/sneak/secret/internal/secret"
"git.eeqj.de/sneak/secret/internal/vault"
"github.com/spf13/afero"
"github.com/spf13/cobra"
@@ -44,7 +44,7 @@ func getSecretNamesCompletionFunc(fs afero.Fs, stateDir string) func(
}
// getUnlockerIDsCompletionFunc returns a completion function that provides
// unlocker IDs
// unlocker IDs, the names of the unlockers' directories in unlockers.d
func getUnlockerIDsCompletionFunc(fs afero.Fs, stateDir string) func(
cmd *cobra.Command, args []string, toComplete string,
) ([]string, cobra.ShellCompDirective) {
@@ -57,38 +57,15 @@ func getUnlockerIDsCompletionFunc(fs afero.Fs, stateDir string) func(
return nil, cobra.ShellCompDirectiveNoFileComp
}
// Get unlocker metadata list
unlockerMetadataList, err := vlt.ListUnlockers()
unlockerMetadata, err := vlt.ListUnlockers()
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
// Get vault directory
vaultDir, err := vlt.GetDirectory()
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
// Collect unlocker IDs
var completions []string
unlockersDir := filepath.Join(vaultDir, "unlockers.d")
for _, metadata := range unlockerMetadataList {
// Get the actual unlocker ID by creating the unlocker instance
id, err := findUnlockerIDByMetadata(
fs, unlockersDir, metadata, false,
)
if err != nil {
secret.Warn(
"Could not read unlockers directory during completion, "+
"skipping unlocker",
"unlockers_dir", unlockersDir, "error", err)
continue
}
if id != "" && strings.HasPrefix(id, toComplete) {
for _, id := range slices.Sorted(maps.Keys(unlockerMetadata)) {
if strings.HasPrefix(id, toComplete) {
completions = append(completions, id)
}
}