lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 5ea5428099e22d0feae96d142c6447572d0ff889
parent 290bb3d47f715ba493c46073e254416ba1f4f494
Author: Sean Enck <sean@ttypty.com>
Date:   Wed,  2 Jul 2025 13:04:14 -0400

the url field is not a password and can be shown plaintext

Diffstat:
Minternal/app/core.go | 6+++---
Minternal/app/insert.go | 7+++++--
Minternal/app/insert_test.go | 20+++++++++++++++++---
Minternal/app/rekey.go | 2+-
Minternal/app/rekey_test.go | 6+++++-
Minternal/kdbx/core.go | 4+++-
Minternal/platform/os.go | 19++++++++++++-------
7 files changed, 46 insertions(+), 18 deletions(-)

diff --git a/internal/app/core.go b/internal/app/core.go @@ -23,7 +23,7 @@ type ( UserInputOptions interface { CommandOptions IsPipe() bool - Input(bool, string) ([]byte, error) + Input(bool, bool, string) ([]byte, error) } // DefaultCommand is the default CLI app type for actual execution @@ -78,6 +78,6 @@ func (a *DefaultCommand) IsPipe() bool { } // Input will read user input -func (a *DefaultCommand) Input(interactive bool, prompt string) ([]byte, error) { - return platform.GetUserInput(interactive, prompt) +func (a *DefaultCommand) Input(interactive, isPassword bool, prompt string) ([]byte, error) { + return platform.GetUserInput(interactive, isPassword, prompt) } diff --git a/internal/app/insert.go b/internal/app/insert.go @@ -40,7 +40,8 @@ func Insert(cmd UserInputOptions) error { } } } - password, err := cmd.Input(!isPipe && !strings.EqualFold(base, kdbx.NotesField), base) + isPass := !strings.EqualFold(base, kdbx.URLField) + password, err := cmd.Input(!isPipe && !strings.EqualFold(base, kdbx.NotesField), isPass, base) if err != nil { return fmt.Errorf("invalid input: %w", err) } @@ -53,7 +54,9 @@ func Insert(cmd UserInputOptions) error { return err } if !isPipe { - fmt.Fprintln(cmd.Writer()) + if isPass { + fmt.Fprintln(cmd.Writer()) + } } return nil } diff --git a/internal/app/insert_test.go b/internal/app/insert_test.go @@ -18,6 +18,7 @@ type ( pipe func() bool token func() string prompt string + isPass bool interactive bool } ) @@ -36,9 +37,10 @@ func (m *mockInsert) IsPipe() bool { return m.pipe() } -func (m *mockInsert) Input(interactive bool, prompt string) ([]byte, error) { +func (m *mockInsert) Input(interactive, isPass bool, prompt string) ([]byte, error) { m.interactive = interactive m.prompt = prompt + m.isPass = isPass return m.input() } @@ -121,7 +123,7 @@ func TestInsertDo(t *testing.T) { if m.command.buf.String() == "" { t.Error("invalid insert") } - if m.prompt != "password" { + if m.prompt != "password" || !m.isPass { t.Error("invalid field prompt") } m.command.confirm = false @@ -152,7 +154,19 @@ func TestInsertDo(t *testing.T) { if m.command.buf.String() == "" || m.interactive { t.Errorf("invalid insert %s %v", m.command.buf.String(), m.interactive) } - if m.prompt != "notes" { + if m.prompt != "notes" || !m.isPass { + t.Error("invalid field prompt") + } + m.interactive = false + m.command.buf = bytes.Buffer{} + m.command.args = []string{"test/test2/test1/url"} + if err := app.Insert(m); err != nil { + t.Errorf("invalid error: %v", err) + } + if m.command.buf.String() != "" || !m.interactive { + t.Errorf("invalid insert %s %v", m.command.buf.String(), m.interactive) + } + if m.prompt != "url" || m.isPass { t.Error("invalid field prompt") } } diff --git a/internal/app/rekey.go b/internal/app/rekey.go @@ -24,7 +24,7 @@ func ReKey(cmd UserInputOptions) error { } var pass string if !vars.NoKey { - p, err := cmd.Input(!piping, "password") + p, err := cmd.Input(!piping, true, "password") if err != nil { return err } diff --git a/internal/app/rekey_test.go b/internal/app/rekey_test.go @@ -2,6 +2,7 @@ package app_test import ( "bytes" + "errors" "io" "testing" @@ -32,7 +33,10 @@ func (m *mockKeyer) Args() []string { return m.args } -func (m *mockKeyer) Input(bool, string) ([]byte, error) { +func (m *mockKeyer) Input(_, pass bool, _ string) ([]byte, error) { + if !pass { + return nil, errors.New("invalid request, always password") + } return []byte(m.pass), nil } diff --git a/internal/kdbx/core.go b/internal/kdbx/core.go @@ -17,7 +17,7 @@ import ( var ( errPath = errors.New("input paths must contain at LEAST 2 components") // AllowedFields are the same of allowed names for storing in a kdbx entry - AllowedFields = []string{NotesField, OTPField, "Password", "URL"} + AllowedFields = []string{NotesField, OTPField, "Password", URLField} ) const ( @@ -28,6 +28,8 @@ const ( OTPField = "otp" // NotesField is the multiline notes key NotesField = "Notes" + // URLField is a non-secret (in terms of input) entry field + URLField = "URL" ) type ( diff --git a/internal/platform/os.go b/internal/platform/os.go @@ -42,10 +42,10 @@ func termEcho(on bool) { } // GetUserInput will read the user's input from stdin via multiple means. -func GetUserInput(interactive bool, prompt string) ([]byte, error) { +func GetUserInput(interactive, isPassword bool, prompt string) ([]byte, error) { var value string if interactive { - input, err := confirmInputsMatch(prompt) + input, err := confirmInputsMatch(isPassword, prompt) if err != nil { return nil, err } @@ -63,16 +63,21 @@ func GetUserInput(interactive bool, prompt string) ([]byte, error) { return []byte(value), nil } -func confirmInputsMatch(prompt string) (string, error) { - termEcho(false) - defer func() { - termEcho(true) - }() +func confirmInputsMatch(isPassword bool, prompt string) (string, error) { + if isPassword { + termEcho(false) + defer func() { + termEcho(true) + }() + } fmt.Printf("please enter %s: ", prompt) first, err := Stdin(true) if err != nil { return "", err } + if !isPassword { + return first, nil + } fmt.Printf("\nplease re-enter %s: ", prompt) second, err := Stdin(true) if err != nil {