fix(safety): eliminate TOCTOU race in readFile, guard WriteConfig, DRY getters

- readFile now opens the file first, then stats via the fd (no race
  between stat and open). Uses toml.NewDecoder instead of DecodeFile.
- WriteConfig returns an error if no config file has been set.
- YAML WriteConfig now calls enc.Close() to flush properly.
- Extract resolve() helper to deduplicate the combinedConfig→envConfig
  fallback pattern across all 9 getter methods.
This commit is contained in:
2026-03-01 23:44:16 +00:00
parent 5aadc84d50
commit b16df4e1a9
2 changed files with 53 additions and 60 deletions

View File

@@ -7,15 +7,24 @@ import (
"time"
)
// resolve looks up a key in combinedConfig, falling back to envConfig.
func (c *ConfigManager) resolve(key string) (ConfigMap, bool) {
lower := strings.ToLower(key)
if v, ok := c.combinedConfig[lower]; ok {
return v, true
}
if v, ok := c.envConfig[lower]; ok {
return v, true
}
return ConfigMap{}, false
}
func (c *ConfigManager) Get(key string) any {
c.mutex.RLock()
defer c.mutex.RUnlock()
v, ok := c.combinedConfig[strings.ToLower(key)]
v, ok := c.resolve(key)
if !ok {
v, ok = c.envConfig[strings.ToLower(key)]
if !ok {
return nil
}
return nil
}
return v.Value
}
@@ -23,12 +32,9 @@ func (c *ConfigManager) Get(key string) any {
func (c *ConfigManager) GetBool(key string) bool {
c.mutex.RLock()
defer c.mutex.RUnlock()
v, ok := c.combinedConfig[strings.ToLower(key)]
v, ok := c.resolve(key)
if !ok {
v, ok = c.envConfig[strings.ToLower(key)]
if !ok {
return false
}
return false
}
val := v.Value
switch val := val.(type) {
@@ -54,12 +60,9 @@ func (c *ConfigManager) GetBool(key string) bool {
func (c *ConfigManager) GetDuration(key string) time.Duration {
c.mutex.RLock()
defer c.mutex.RUnlock()
v, ok := c.combinedConfig[strings.ToLower(key)]
v, ok := c.resolve(key)
if !ok {
v, ok = c.envConfig[strings.ToLower(key)]
if !ok {
return 0
}
return 0
}
val := v.Value
switch val := val.(type) {
@@ -89,12 +92,9 @@ func (c *ConfigManager) GetDuration(key string) time.Duration {
func (c *ConfigManager) GetString(key string) string {
c.mutex.RLock()
defer c.mutex.RUnlock()
v, ok := c.combinedConfig[strings.ToLower(key)]
v, ok := c.resolve(key)
if !ok {
v, ok = c.envConfig[strings.ToLower(key)]
if !ok {
return ""
}
return ""
}
switch val := v.Value.(type) {
@@ -108,12 +108,9 @@ func (c *ConfigManager) GetString(key string) string {
func (c *ConfigManager) GetStringMap(key string) map[string]any {
c.mutex.RLock()
defer c.mutex.RUnlock()
v, ok := c.combinedConfig[strings.ToLower(key)]
v, ok := c.resolve(key)
if !ok {
v, ok = c.envConfig[strings.ToLower(key)]
if !ok {
return nil
}
return nil
}
switch val := v.Value.(type) {
case map[string]any:
@@ -126,12 +123,9 @@ func (c *ConfigManager) GetStringMap(key string) map[string]any {
func (c *ConfigManager) GetStringSlice(key string) []string {
c.mutex.RLock()
defer c.mutex.RUnlock()
v, ok := c.combinedConfig[strings.ToLower(key)]
v, ok := c.resolve(key)
if !ok {
v, ok = c.envConfig[strings.ToLower(key)]
if !ok {
return nil
}
return nil
}
switch val := v.Value.(type) {
case []string:
@@ -155,12 +149,9 @@ func (c *ConfigManager) GetStringSlice(key string) []string {
func (c *ConfigManager) GetInt(key string) int {
c.mutex.RLock()
defer c.mutex.RUnlock()
v, ok := c.combinedConfig[strings.ToLower(key)]
v, ok := c.resolve(key)
if !ok {
v, ok = c.envConfig[strings.ToLower(key)]
if !ok {
return 0
}
return 0
}
switch val := v.Value.(type) {
case int:
@@ -187,12 +178,9 @@ func (c *ConfigManager) GetInt(key string) int {
func (c *ConfigManager) GetIntSlice(key string) []int {
c.mutex.RLock()
defer c.mutex.RUnlock()
v, ok := c.combinedConfig[strings.ToLower(key)]
v, ok := c.resolve(key)
if !ok {
v, ok = c.envConfig[strings.ToLower(key)]
if !ok {
return nil
}
return nil
}
switch val := v.Value.(type) {
case []int: