You've already forked agentic-coding-workflow
fix(config): redact MasterKey type; drop os.Clearenv; add hex validation test
This commit is contained in:
@@ -4,16 +4,33 @@ package config
|
|||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"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 是应用全局配置的根结构。
|
// Config 是应用全局配置的根结构。
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Env string `mapstructure:"env"`
|
Env string `mapstructure:"env"`
|
||||||
DataDir string `mapstructure:"data_dir"`
|
DataDir string `mapstructure:"data_dir"`
|
||||||
MasterKey []byte // 解码后的 32 字节
|
MasterKey MasterKey `mapstructure:"-"` // 解码后的 32 字节,单独从 master_key 解析
|
||||||
HTTP HTTPConfig `mapstructure:"http"`
|
HTTP HTTPConfig `mapstructure:"http"`
|
||||||
DB DBConfig `mapstructure:"db"`
|
DB DBConfig `mapstructure:"db"`
|
||||||
Bootstrap BootstrapConfig `mapstructure:"bootstrap"`
|
Bootstrap BootstrapConfig `mapstructure:"bootstrap"`
|
||||||
@@ -86,7 +103,7 @@ func Load(configFile string) (*Config, error) {
|
|||||||
if len(key) != 32 {
|
if len(key) != 32 {
|
||||||
return nil, fmt.Errorf("APP_MASTER_KEY must be 32 bytes (got %d)", len(key))
|
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 == "" {
|
if cfg.DB.DSN == "" {
|
||||||
return nil, fmt.Errorf("APP_DB_DSN required")
|
return nil, fmt.Errorf("APP_DB_DSN required")
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -27,8 +28,8 @@ func TestLoad_FromEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLoad_MissingMasterKey(t *testing.T) {
|
func TestLoad_MissingMasterKey(t *testing.T) {
|
||||||
os.Clearenv()
|
|
||||||
t.Setenv("APP_DB_DSN", "x")
|
t.Setenv("APP_DB_DSN", "x")
|
||||||
|
t.Setenv("APP_MASTER_KEY", "") // explicitly empty so t.Setenv cleans up
|
||||||
_, err := Load("")
|
_, err := Load("")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), "MASTER_KEY")
|
require.Contains(t, err.Error(), "MASTER_KEY")
|
||||||
@@ -40,3 +41,21 @@ func TestLoad_MasterKeyWrongLength(t *testing.T) {
|
|||||||
_, err := Load("")
|
_, err := Load("")
|
||||||
require.Error(t, err)
|
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))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user