security: phase D pre-release review — pass; harden saslpasswd2 argv

Fable review of the full diff from the v1.0 audit (Phase 11, 65a420d) to
HEAD plus a complete pass over the docs/security.md checklist (former spec
7.6). No exploitable findings. One defence-in-depth fix: the application
login is passed to saslpasswd2 behind a -- end-of-options marker so a
login starting with - can never be parsed as a flag. Accepted risks
unchanged; plan § D closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 13:32:52 +03:00
parent 00983cce39
commit e93a277ee7
6 changed files with 45 additions and 34 deletions
+6 -3
View File
@@ -50,7 +50,10 @@ func (s *SASLDB) Set(login, password string) error {
// -c: create the account / set the password.
// -f: operate on our sasldb2 rather than the system default path.
// -u: the realm the account lives under.
args := []string{"-p", "-c", "-f", s.path, "-u", s.realm, login}
// --: end of options, so a login can never be parsed as a flag (the
// whitelist already forbids nothing that getopt would eat, but a login
// starting with '-' is legal there — this keeps it an operand).
args := []string{"-p", "-c", "-f", s.path, "-u", s.realm, "--", login}
if err := s.run(args, []byte(password)); err != nil {
return fmt.Errorf("saslpasswd2 set %q: %w", login, err)
}
@@ -63,8 +66,8 @@ func (s *SASLDB) Delete(login string) error {
if err := validateLogin(login); err != nil {
return err
}
// -d: delete the account.
args := []string{"-d", "-f", s.path, "-u", s.realm, login}
// -d: delete the account. "--" as in Set: the login is always an operand.
args := []string{"-d", "-f", s.path, "-u", s.realm, "--", login}
if err := s.run(args, nil); err != nil {
return fmt.Errorf("saslpasswd2 delete %q: %w", login, err)
}
+4 -3
View File
@@ -39,8 +39,9 @@ func TestSASLSetPassesPasswordOnStdinNotArgv(t *testing.T) {
if strings.Contains(joined, secret) {
t.Errorf("password leaked into argv: %q", joined)
}
// Expected fixed flags and the login as its own trailing argument.
want := []string{"-p", "-c", "-f", "/data/sasl/sasldb2", "-u", "mail.example.com", "alerts"}
// Expected fixed flags and the login as its own trailing argument, behind
// "--" so it can never be parsed as an option.
want := []string{"-p", "-c", "-f", "/data/sasl/sasldb2", "-u", "mail.example.com", "--", "alerts"}
if len(fr.args) != len(want) {
t.Fatalf("args = %v, want %v", fr.args, want)
}
@@ -56,7 +57,7 @@ func TestSASLDeleteArgs(t *testing.T) {
if err := s.Delete("alerts"); err != nil {
t.Fatalf("Delete: %v", err)
}
want := []string{"-d", "-f", "/data/sasl/sasldb2", "-u", "mail.example.com", "alerts"}
want := []string{"-d", "-f", "/data/sasl/sasldb2", "-u", "mail.example.com", "--", "alerts"}
if strings.Join(fr.args, " ") != strings.Join(want, " ") {
t.Errorf("delete args = %v, want %v", fr.args, want)
}