Files
go-admin/common/database/dsn_test.go
zhangwenjian 484de2e698 fix🔒: stop writing the database password into the log
The startup line printed the DSN whole:

  * => goadmin:<password>@tcp(host:3306)/go-admin?...

So every deployment wrote its own database credential into its own logs,
where a log shipper, a support bundle or a screenshot of a terminal
carries it onward. Found while reading deploy output, which is exactly
how it leaks.

The host and username stay - they are what makes the line worth printing
- and only the password is replaced. Both DSN shapes this project accepts
are covered, a sqlite path is left alone, and anything unparseable is
withheld rather than echoed, since it may hold a credential too.
2026-08-27 16:17:12 +08:00

59 lines
1.9 KiB
Go

package database
import (
"strings"
"testing"
)
// The startup line printed the DSN whole, so every deployment wrote its
// database password into its own logs - readable by anyone with the log file, a
// log shipper, or a screenshot of the terminal.
func TestRedactDSNKeepsThePasswordOut(t *testing.T) {
const secret = "s3cr3t-do-not-log"
cases := map[string]string{
"mysql": "goadmin:" + secret + "@tcp(db.example.com:3306)/go-admin?charset=utf8mb4&parseTime=True",
"postgres": "postgres://goadmin:" + secret + "@db.example.com:5432/go-admin?sslmode=disable",
"sqlserver": "sqlserver://goadmin:" + secret + "@db.example.com:1433?database=go-admin",
"empty-ish pwd": "goadmin:@tcp(db.example.com:3306)/go-admin",
}
for name, dsn := range cases {
t.Run(name, func(t *testing.T) {
got := redactDSN(dsn)
if strings.Contains(got, secret) {
t.Fatalf("password survived redaction: %s", got)
}
// Still has to be useful: the host is what makes the line worth logging.
if !strings.Contains(got, "db.example.com") {
t.Errorf("host was lost, the line no longer says anything: %s", got)
}
if !strings.Contains(got, "goadmin") {
t.Errorf("username was lost: %s", got)
}
})
}
}
// sqlite has no credential to hide, and its path is the useful part.
func TestRedactDSNLeavesAPathAlone(t *testing.T) {
const path = "./go-admin-db.db"
if got := redactDSN(path); got != path {
t.Errorf("redactDSN(%q) = %q, want it unchanged", path, got)
}
}
// An unparseable string might still hold a password, so it is not echoed.
func TestRedactDSNSaysNothingAboutWhatItCannotParse(t *testing.T) {
got := redactDSN("://not a url at all:hunter2@")
if strings.Contains(got, "hunter2") {
t.Fatalf("password survived: %s", got)
}
}
func TestRedactDSNHandlesEmpty(t *testing.T) {
if got := redactDSN(""); got != "" {
t.Errorf("redactDSN(\"\") = %q", got)
}
}