From baf296abfbb3bbf33a011f63360152b7c8135107 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 28 Apr 2026 13:44:06 +0800 Subject: [PATCH] fix(config): redact MasterKey type; drop os.Clearenv; add hex validation test --- internal/config/config.go | 21 +++++++++++++++++++-- internal/config/config_test.go | 23 +++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 8a1f532..1e03220 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,16 +4,33 @@ package config import ( "encoding/hex" "fmt" + "log/slog" "strings" "github.com/spf13/viper" ) +// MasterKey wraps a 32-byte AES master key with redacted formatting to prevent +// accidental log/fmt exposure. +type MasterKey []byte + +// String redacts when used with %s / fmt.Println. +func (k MasterKey) String() string { return "[REDACTED 32B]" } + +// GoString redacts when used with %#v. +func (k MasterKey) GoString() string { return "[REDACTED 32B]" } + +// MarshalJSON redacts when serialized to JSON. +func (k MasterKey) MarshalJSON() ([]byte, error) { return []byte(`"[REDACTED]"`), nil } + +// LogValue redacts when logged via slog. +func (k MasterKey) LogValue() slog.Value { return slog.StringValue("[REDACTED 32B]") } + // Config 是应用全局配置的根结构。 type Config struct { Env string `mapstructure:"env"` DataDir string `mapstructure:"data_dir"` - MasterKey []byte // 解码后的 32 字节 + MasterKey MasterKey `mapstructure:"-"` // 解码后的 32 字节,单独从 master_key 解析 HTTP HTTPConfig `mapstructure:"http"` DB DBConfig `mapstructure:"db"` Bootstrap BootstrapConfig `mapstructure:"bootstrap"` @@ -86,7 +103,7 @@ func Load(configFile string) (*Config, error) { if len(key) != 32 { return nil, fmt.Errorf("APP_MASTER_KEY must be 32 bytes (got %d)", len(key)) } - cfg.MasterKey = key + cfg.MasterKey = MasterKey(key) if cfg.DB.DSN == "" { return nil, fmt.Errorf("APP_DB_DSN required") diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 0bf41a9..b8f41d0 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,7 +1,8 @@ package config import ( - "os" + "encoding/json" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -27,8 +28,8 @@ func TestLoad_FromEnv(t *testing.T) { } func TestLoad_MissingMasterKey(t *testing.T) { - os.Clearenv() t.Setenv("APP_DB_DSN", "x") + t.Setenv("APP_MASTER_KEY", "") // explicitly empty so t.Setenv cleans up _, err := Load("") require.Error(t, err) require.Contains(t, err.Error(), "MASTER_KEY") @@ -40,3 +41,21 @@ func TestLoad_MasterKeyWrongLength(t *testing.T) { _, err := Load("") require.Error(t, err) } + +func TestLoad_MasterKeyNotHex(t *testing.T) { + t.Setenv("APP_DB_DSN", "x") + t.Setenv("APP_MASTER_KEY", "zz") // not valid hex + _, err := Load("") + require.Error(t, err) + require.Contains(t, err.Error(), "invalid hex") +} + +func TestMasterKey_Redaction(t *testing.T) { + k := MasterKey([]byte{0x01, 0x02, 0x03}) + require.Equal(t, "[REDACTED 32B]", k.String()) + require.Equal(t, "[REDACTED 32B]", fmt.Sprintf("%v", k)) + require.NotContains(t, fmt.Sprintf("%v", k), "01") + b, err := json.Marshal(k) + require.NoError(t, err) + require.Equal(t, `"[REDACTED]"`, string(b)) +}