feat: share.keyRules, how long and varied a share key must be
CI / Checks (pull_request) Successful in 39s
PR title / Conventional title (pull_request) Successful in 2s

Sharing refuses a key shorter than minLength characters (default 32,
never under 16) or of fewer than minClasses kinds of lowercase,
uppercase, digits and symbols (default 2, so random hex passes),
saying so without showing it. Opening doesn't check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kebandClaude Opus 5.5 committed 2026-10-13 12:16:12 +02:00
1 parent b46991c7c2
commit 0bfa2a3141
13 files changed
+156 -21

No files matched your search

+2 -1
View File
@@ -317,7 +317,7 @@ Flags:
if shareArgs != nil {
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
return shareFiles(ctx, providers, st, cfg.Share.Key, shareArgs, os.Stdout)
return shareFiles(ctx, providers, st, cfg.Share.Key, shareRules(cfg), shareArgs, os.Stdout)
}
// Links opened elsewhere (lazysecrets launch) can come here.
@@ -391,6 +391,7 @@ Flags:
Changelog: lazysecrets.Changelog,
Dir: placeDir,
ShareKey: cfg.Share.Key,
ShareRules: shareRules(cfg),
Open: open,
Styles: &theme,
DefaultFg: defaultFg,
+8 -2
View File
@@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"git.kebcloud.no/keb/lazysecrets/internal/config"
"git.kebcloud.no/keb/lazysecrets/internal/provider"
"git.kebcloud.no/keb/lazysecrets/internal/provider/repo"
"git.kebcloud.no/keb/lazysecrets/internal/share"
@@ -19,7 +20,7 @@ import (
// the --key secret's value, else the repo's or the config's share.key:
//
// lazysecrets share .env apps/api/.env
func shareFiles(ctx context.Context, providers []provider.Provider, st *store.Store, general string, args []string, out io.Writer) error {
func shareFiles(ctx context.Context, providers []provider.Provider, st *store.Store, general string, rules share.Rules, args []string, out io.Writer) error {
fs := flag.NewFlagSet("share", flag.ContinueOnError)
keyLink := fs.String("key", "", "the secret to encrypt with: its lazysecrets:// link, Key Vault URL or reference (default: share.key)")
if err := fs.Parse(args); err != nil {
@@ -78,10 +79,15 @@ func shareFiles(ctx context.Context, providers []provider.Provider, st *store.St
if err != nil {
return fmt.Errorf("reading the key %s: %w", key, err)
}
link, err := share.Seal(share.Header{Key: share.Pin(key, version), Repo: id}, []byte(v.Reveal()), files)
link, err := share.Seal(share.Header{Key: share.Pin(key, version), Repo: id}, []byte(v.Reveal()), files, rules)
if err != nil {
return err
}
_, err = fmt.Fprintln(out, link)
return err
}
// shareRules are the config's share.keyRules.
func shareRules(cfg config.Config) share.Rules {
return share.Rules{MinLength: cfg.Share.KeyRules.MinLength, MinClasses: cfg.Share.KeyRules.MinClasses}
}
+5 -2
View File
@@ -16,6 +16,9 @@ import (
"git.kebcloud.no/keb/lazysecrets/internal/store"
)
// demoRules take the demo key, made up to be remembered.
var demoRules = share.Rules{MinLength: 16, MinClasses: 1}
func TestShareFiles(t *testing.T) {
root, _ := filepath.EvalSymlinks(t.TempDir())
for _, args := range [][]string{{"init", "-q"}, {"remote", "add", "origin", "git@git.example.com:team/app.git"}} {
@@ -31,7 +34,7 @@ func TestShareFiles(t *testing.T) {
key := "lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password"
var out bytes.Buffer
if err := shareFiles(ctx, providers, st, key, []string{filepath.Join(root, ".env")}, &out); err != nil {
if err := shareFiles(ctx, providers, st, key, demoRules, []string{filepath.Join(root, ".env")}, &out); err != nil {
t.Fatal(err)
}
r, err := ref.Parse(strings.TrimSpace(out.String()))
@@ -57,7 +60,7 @@ func TestShareFiles(t *testing.T) {
{key, nil, "usage"},
{"", []string{"--key", "lazysecrets://azure/kv-app-prod.vault.azure.net", filepath.Join(root, ".env")}, "isn't a secret"},
} {
if err := shareFiles(ctx, providers, st, c.general, c.args, &out); err == nil || !strings.Contains(err.Error(), c.want) {
if err := shareFiles(ctx, providers, st, c.general, demoRules, c.args, &out); err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("%v: %v, want %q", c.args, err, c.want)
}
}