fix: redact sensitive values in sanitized startup config logs

This commit is contained in:
copilot-swe-agent[bot]
2026-06-05 18:45:18 +00:00
committed by GitHub
parent 44894652e3
commit cea72a4c08
2 changed files with 167 additions and 3 deletions
+78 -3
View File
@@ -3,8 +3,10 @@ package config
import (
"encoding/json"
"maps"
"net/url"
"os"
"slices"
"strings"
"sync"
"sync/atomic"
"time"
@@ -23,6 +25,8 @@ var (
oauth2SupportedProviders = [...]string{"github"} //nolint: gochecknoglobals
)
const redactedSecret = "******"
type StorageConfig struct {
RootDirectory string
MaxRepos int
@@ -836,6 +840,10 @@ func (c *Config) Sanitize() *Config {
// Sanitize HTTP config
if c.HTTP.Auth != nil {
redactSecretsInMap(sanitizedConfig.HTTP.Auth.SessionDriver)
sanitizedConfig.HTTP.Auth.SessionHashKey = nil
sanitizedConfig.HTTP.Auth.SessionEncryptKey = nil
// Sanitize LDAP bind password
if c.HTTP.Auth.LDAP != nil && c.HTTP.Auth.LDAP.bindPassword != "" {
sanitizedConfig.HTTP.Auth.LDAP = &LDAPConfig{}
@@ -844,7 +852,7 @@ func (c *Config) Sanitize() *Config {
panic(err)
}
sanitizedConfig.HTTP.Auth.LDAP.bindPassword = "******"
sanitizedConfig.HTTP.Auth.LDAP.bindPassword = redactedSecret
}
// Sanitize OpenID client secrets
@@ -858,7 +866,7 @@ func (c *Config) Sanitize() *Config {
sanitizedConfig.HTTP.Auth.OpenID.Providers[provider] = OpenIDProviderConfig{
Name: config.Name,
ClientID: config.ClientID,
ClientSecret: "******",
ClientSecret: redactedSecret,
KeyPath: config.KeyPath,
Issuer: config.Issuer,
AuthURL: config.AuthURL,
@@ -870,6 +878,19 @@ func (c *Config) Sanitize() *Config {
}
}
redactSecretsInMap(sanitizedConfig.Storage.StorageDriver)
redactSecretsInMap(sanitizedConfig.Storage.CacheDriver)
for subPath, subPathConfig := range sanitizedConfig.Storage.SubPaths {
redactSecretsInMap(subPathConfig.StorageDriver)
redactSecretsInMap(subPathConfig.CacheDriver)
sanitizedConfig.Storage.SubPaths[subPath] = subPathConfig
}
if sanitizedConfig.Cluster != nil && sanitizedConfig.Cluster.HashKey != "" {
sanitizedConfig.Cluster.HashKey = redactedSecret
}
if c.Extensions.IsEventRecorderEnabled() {
for i, sink := range c.Extensions.Events.Sinks {
if sink.Credentials == nil {
@@ -880,13 +901,67 @@ func (c *Config) Sanitize() *Config {
panic(err)
}
sanitizedConfig.Extensions.Events.Sinks[i].Credentials.Password = "******"
sanitizedConfig.Extensions.Events.Sinks[i].Credentials.Password = redactedSecret
sanitizedConfig.Extensions.Events.Sinks[i].Credentials.Token = redactedSecret
}
}
return sanitizedConfig
}
func redactSecretsInMap(values map[string]any) {
for key, value := range values {
if isSensitiveFieldName(key) {
values[key] = redactedSecret
continue
}
switch typedValue := value.(type) {
case map[string]any:
redactSecretsInMap(typedValue)
case []any:
for _, element := range typedValue {
nestedMap, ok := element.(map[string]any)
if ok {
redactSecretsInMap(nestedMap)
}
}
case string:
values[key] = sanitizeURLPassword(typedValue)
}
}
}
func isSensitiveFieldName(fieldName string) bool {
normalized := strings.NewReplacer("_", "", "-", "").Replace(strings.ToLower(fieldName))
switch normalized {
case "accesskey", "secretkey", "clientsecret", "password", "token", "authorization",
"apikey", "sessionhashkey", "sessionencryptkey", "hashkey":
return true
default:
return false
}
}
func sanitizeURLPassword(rawURL string) string {
parsedURL, err := url.Parse(rawURL)
if err != nil || parsedURL.User == nil {
return rawURL
}
username := parsedURL.User.Username()
_, hasPassword := parsedURL.User.Password()
if !hasPassword {
return rawURL
}
parsedURL.User = url.UserPassword(username, redactedSecret)
return parsedURL.String()
}
// UpdateReloadableConfig updates only the fields that can be reloaded at runtime.
func (c *Config) UpdateReloadableConfig(newConfig *Config) {
if c == nil {
+89
View File
@@ -1,6 +1,8 @@
package config_test
import (
"net/url"
"strings"
"testing"
"time"
@@ -165,6 +167,7 @@ func TestConfig(t *testing.T) {
Credentials: &eventsconf.Credentials{
Username: "webhook-user",
Password: "webhook-password",
Token: "webhook-token",
},
},
{
@@ -173,6 +176,7 @@ func TestConfig(t *testing.T) {
Credentials: &eventsconf.Credentials{
Username: "nats-user",
Password: "nats-token",
Token: "nats-auth-token",
},
},
},
@@ -186,6 +190,8 @@ func TestConfig(t *testing.T) {
// Verify event sink credentials passwords are sanitized
So(sanitizedConf.Extensions.Events.Sinks[0].Credentials.Password, ShouldEqual, "******")
So(sanitizedConf.Extensions.Events.Sinks[1].Credentials.Password, ShouldEqual, "******")
So(sanitizedConf.Extensions.Events.Sinks[0].Credentials.Token, ShouldEqual, "******")
So(sanitizedConf.Extensions.Events.Sinks[1].Credentials.Token, ShouldEqual, "******")
// Verify other fields are preserved
So(sanitizedConf.Extensions.Events.Sinks[0].Credentials.Username, ShouldEqual, "webhook-user")
@@ -196,6 +202,8 @@ func TestConfig(t *testing.T) {
// Verify original config is not modified
So(conf.Extensions.Events.Sinks[0].Credentials.Password, ShouldEqual, "webhook-password")
So(conf.Extensions.Events.Sinks[1].Credentials.Password, ShouldEqual, "nats-token")
So(conf.Extensions.Events.Sinks[0].Credentials.Token, ShouldEqual, "webhook-token")
So(conf.Extensions.Events.Sinks[1].Credentials.Token, ShouldEqual, "nats-auth-token")
})
Convey("Test Sanitize() with Event sink credentials including nil credentials", func() {
@@ -312,6 +320,87 @@ func TestConfig(t *testing.T) {
So(sanitizedConf.Extensions.Events.Sinks[0].Type, ShouldEqual, eventsconf.HTTP)
})
Convey("Test Sanitize() redacts storage and auth driver secrets", func() {
conf := config.New()
So(conf, ShouldNotBeNil)
cacheURL := "redis://cache-user:" + "pwd123" + "@redis:6379/1"
subPathCacheURL := "redis://subpath-user:" + "pwd123" + "@redis:6379/2"
sessionURL := "redis://session-user:" + "pwd123" + "@redis:6379/3"
redactedURL := func(rawURL string) string {
parsedURL, err := url.Parse(rawURL)
So(err, ShouldBeNil)
parsedURL.User = url.UserPassword(parsedURL.User.Username(), strings.Repeat("*", 6))
return parsedURL.String()
}
redactedCacheURL := redactedURL(cacheURL)
redactedSubPathCacheURL := redactedURL(subPathCacheURL)
redactedSessionURL := redactedURL(sessionURL)
conf.Storage.StorageDriver = map[string]any{
"name": "s3",
"accesskey": "driver-access-key",
"secretkey": "driver-secret-key",
}
conf.Storage.CacheDriver = map[string]any{
"name": "redis",
"url": cacheURL,
"password": "cache-password",
}
conf.Storage.SubPaths = map[string]config.StorageConfig{
"/tenant": {
StorageDriver: map[string]any{
"accesskey": "subpath-access-key",
"secretkey": "subpath-secret-key",
},
CacheDriver: map[string]any{
"url": subPathCacheURL,
},
},
}
conf.HTTP.Auth = &config.AuthConfig{
SessionHashKey: []byte("hash-secret"),
SessionEncryptKey: []byte("encrypt-secret"),
SessionDriver: map[string]any{
"url": sessionURL,
"password": "session-password",
},
}
conf.Cluster = &config.ClusterConfig{HashKey: "cluster-hash-secret"}
So(func() { conf.Sanitize() }, ShouldNotPanic)
sanitizedConf := conf.Sanitize()
So(sanitizedConf.Storage.StorageDriver["accesskey"], ShouldEqual, "******")
So(sanitizedConf.Storage.StorageDriver["secretkey"], ShouldEqual, "******")
So(sanitizedConf.Storage.CacheDriver["password"], ShouldEqual, "******")
So(sanitizedConf.Storage.CacheDriver["url"], ShouldEqual, redactedCacheURL)
So(sanitizedConf.Storage.SubPaths["/tenant"].StorageDriver["accesskey"], ShouldEqual, "******")
So(sanitizedConf.Storage.SubPaths["/tenant"].StorageDriver["secretkey"], ShouldEqual, "******")
So(sanitizedConf.Storage.SubPaths["/tenant"].CacheDriver["url"], ShouldEqual,
redactedSubPathCacheURL)
So(sanitizedConf.HTTP.Auth.SessionDriver["password"], ShouldEqual, "******")
So(sanitizedConf.HTTP.Auth.SessionDriver["url"], ShouldEqual,
redactedSessionURL)
So(sanitizedConf.HTTP.Auth.SessionHashKey, ShouldBeNil)
So(sanitizedConf.HTTP.Auth.SessionEncryptKey, ShouldBeNil)
So(sanitizedConf.Cluster.HashKey, ShouldEqual, "******")
// Verify original config is not modified.
So(conf.Storage.StorageDriver["accesskey"], ShouldEqual, "driver-access-key")
So(conf.Storage.StorageDriver["secretkey"], ShouldEqual, "driver-secret-key")
So(conf.Storage.CacheDriver["url"], ShouldEqual, cacheURL)
So(conf.Storage.SubPaths["/tenant"].StorageDriver["secretkey"], ShouldEqual, "subpath-secret-key")
So(conf.HTTP.Auth.SessionDriver["url"], ShouldEqual, sessionURL)
So(string(conf.HTTP.Auth.SessionHashKey), ShouldEqual, "hash-secret")
So(string(conf.HTTP.Auth.SessionEncryptKey), ShouldEqual, "encrypt-secret")
So(conf.Cluster.HashKey, ShouldEqual, "cluster-hash-secret")
})
Convey("Test Sanitize() with nil sensitive data", func() {
conf := config.New()
So(conf, ShouldNotBeNil)