docs-audit: architecture, schema, security and the rest against the code

The owner's "проверь, что документация актуальна с кодом" (2026-10-16).
Read-only reviews checked every present-tense claim against the code;
each finding was read in the source before it was fixed here.

architecture.md: held inbound mail has its row from the journal milter,
not the tailer; the antispam action is accept or tempfail, anything else
stops the start; tls-internal's certificate is 0644; the signer logs to
stderr; the log's fingerprint is the inode, not the device; a mail.log
postlogd makes is 0640 (maillog_file_permissions); an unreadable stored
place starts at end-of-file too; the test-message route; no map in page
data; design-first compares whitespace aside; the queue and sasldb2.new
are not backed up; antispam/ under /data; POSTFIX_QUEUE_DIR's desync;
SELFPOST_BACKUP_PASSWORD (the CLI's).
schema-migrations.md: inbound_spam_log's upstream columns (v12),
send_log's sender reason, row 9's client_ip.
security.md: /data/opendkim since 2.9.0 (removed, or the start refused),
sasldb2 under /data/sasl, the trigger for mandatory encryption, the
password minimum counted in characters.
development.md: Staticcheck in CI, the e2e ports 20025 and 20053/udp.
README: the client IP list refuses at MAIL FROM, overrides are per
application. product.md: the optional rspamd sidecar.

Code: validate.AdminPassword counted bytes, so six Cyrillic letters
passed for twelve characters; it counts runes, as the backup password
always did (TestAdminPasswordCountsCharacters). envdoc reads
cmd/selfpost-backup too, and a name kept in a constant. A stale comment
of cmd/panel's run ("three roles").

go vet + go test (26 packages) and make e2e on the test server for this
tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-17 02:07:55 +03:00
co-authored by Claude Opus 5.5
parent 2dded921f5
commit be98efa549
11 changed files with 104 additions and 57 deletions
+5 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// MinAdminPasswordLen is the floor for the administrator password. The panel is
@@ -39,7 +40,10 @@ func Username(u string) error {
// AdminPassword enforces a minimum length. Composition rules beyond length tend
// to reduce entropy in practice, so length is the sole gate.
func AdminPassword(p string) error {
if len(p) < MinAdminPasswordLen {
// Characters, as the message says and as the backup password counts
// them (secretFilePassword, cmd/selfpost-backup): len would let six
// Cyrillic letters, twelve bytes, pass for twelve.
if utf8.RuneCountInString(p) < MinAdminPasswordLen {
return fmt.Errorf("password must be at least %d characters", MinAdminPasswordLen)
}
return nil
+11
View File
@@ -65,3 +65,14 @@ func TestValidateDomainLongLabelRejected(t *testing.T) {
t.Error("expected error for over-long label")
}
}
// The minimum counts characters, not bytes: six Cyrillic letters are twelve
// bytes and six characters.
func TestAdminPasswordCountsCharacters(t *testing.T) {
if err := AdminPassword("пароль"); err == nil {
t.Error("six Cyrillic letters passed for twelve characters")
}
if err := AdminPassword("парольпароль"); err != nil {
t.Errorf("twelve Cyrillic letters: %v", err)
}
}