From 7e17e59ad131627fd480838c95664688d3980cd0 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 11 May 2026 12:26:47 +0800 Subject: [PATCH] feat: strict config parsing in production mode with mustGetEnvInt/Bool --- internal/config/config.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index 0d1be22..ec0315c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -144,6 +144,17 @@ func Load() (*Config, error) { if cfg.Runtime.Env == "production" && strings.TrimSpace(cfg.Webhook.Secret) == "" { return nil, fmt.Errorf("AI_CS_WEBHOOK_SECRET must not be empty in production") } + if cfg.Runtime.Env == "production" { + if _, err := mustGetEnvInt("AI_CS_WEBHOOK_MAX_SKEW_SECONDS"); err != nil { + return nil, err + } + if _, err := mustGetEnvInt("AI_CS_POSTGRES_MAX_OPEN_CONNS"); err != nil { + return nil, err + } + if _, err := mustGetEnvInt("AI_CS_PLATFORM_SUB2API_CALLBACK_TIMEOUT_MS"); err != nil { + return nil, err + } + } return cfg, nil } @@ -253,3 +264,30 @@ func getEnvIntList(key string, fallback []int) []int { } return result } + +func mustGetEnvInt(key string) (int, error) { + value := strings.TrimSpace(os.Getenv(key)) + if value == "" { + return 0, nil + } + parsed, err := strconv.Atoi(value) + if err != nil { + return 0, fmt.Errorf("%s must be a valid integer, got %q", key, value) + } + return parsed, nil +} + +func mustGetEnvBool(key string) (bool, error) { + value := strings.TrimSpace(strings.ToLower(os.Getenv(key))) + if value == "" { + return false, nil + } + switch value { + case "1", "true", "yes", "on": + return true, nil + case "0", "false", "no", "off": + return false, nil + default: + return false, fmt.Errorf("%s must be a valid boolean, got %q", key, value) + } +}