From 5a9c2a2fa99be8316381c348fd70644ceb0eeaa1 Mon Sep 17 00:00:00 2001 From: T3ST3ST3R0N Date: Wed, 7 Oct 2026 04:09:57 +0330 Subject: [PATCH 1/2] fix(config): refuse to start without a valid API_KEY When API_KEY was unset, not a UUID, or the all-zero UUID, Load only logged an error and kept ApiKey = uuid.Nil. Both transports compare the request's x-api-key against that value, so any client sending 00000000-0000-0000-0000-000000000000 was authenticated and could control the node. Load now returns an error in those cases, so the node exits at startup with "invalid API_KEY: ...". The config is still returned fully populated so NewTestConfig, which sets its own key, keeps working. --- config/config.go | 17 +++++++++++++---- config/config_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 config/config_test.go diff --git a/config/config.go b/config/config.go index 6dd5be84..a012e25c 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,8 @@ package config import ( + "errors" + "fmt" "log" "os" "regexp" @@ -68,9 +70,16 @@ func Load() (*Config, error) { cfg.LogBufferSize = 1 } - cfg.ApiKey, err = GetEnvAsUUID("API_KEY") - if err != nil { - log.Printf("[Error] Failed to load API Key, error: %v", err) + // The API key is the node's only authentication. A missing, malformed or all-zero key + // would leave the node accepting the all-zero UUID, so Load reports it as an error + // (main exits). cfg is still returned complete for NewTestConfig, which sets its own key. + var apiKeyErr error + cfg.ApiKey, apiKeyErr = GetEnvAsUUID("API_KEY") + if apiKeyErr == nil && cfg.ApiKey == uuid.Nil { + apiKeyErr = errors.New("must not be the all-zero UUID") + } + if apiKeyErr != nil { + apiKeyErr = fmt.Errorf("invalid API_KEY: %w", apiKeyErr) } nodeHostStr := GetEnv("NODE_HOST", "0.0.0.0") @@ -84,7 +93,7 @@ func Load() (*Config, error) { cfg.NodeHost = "127.0.0.1" } - return cfg, nil + return cfg, apiKeyErr } // NewTestConfig creates a config for testing diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 00000000..670db68b --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,33 @@ +package config + +import "testing" + +func TestLoadRejectsMissingOrInvalidAPIKey(t *testing.T) { + cases := map[string]string{ + "missing": "", + "not a uuid": "not-a-uuid", + "all zero": "00000000-0000-0000-0000-000000000000", + } + for name, value := range cases { + t.Run(name, func(t *testing.T) { + t.Setenv("API_KEY", value) + + if _, err := Load(); err == nil { + t.Fatalf("expected Load to fail for API_KEY=%q", value) + } + }) + } +} + +func TestLoadAcceptsValidAPIKey(t *testing.T) { + const key = "3fa85f64-5717-4562-b3fc-2c963f66afa6" + t.Setenv("API_KEY", key) + + cfg, err := Load() + if err != nil { + t.Fatalf("Load failed: %v", err) + } + if cfg.ApiKey.String() != key { + t.Fatalf("expected ApiKey %s, got %s", key, cfg.ApiKey) + } +} From 75be984a3e75cba2facc547f072118442e52b805 Mon Sep 17 00:00:00 2001 From: T3ST3ST3R0N Date: Wed, 7 Oct 2026 05:33:34 +0330 Subject: [PATCH 2/2] fix(config): report an unset API_KEY explicitly An unset or empty API_KEY used to surface as "invalid API_KEY: invalid UUID length: 0". It now fails with "invalid API_KEY: API_KEY is not set". The tests now really unset the variable for the missing case, and check that Load still returns the config on error, which NewTestConfig relies on. --- config/config.go | 6 +++++- config/config_test.go | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/config/config.go b/config/config.go index a012e25c..0ec5b6bc 100644 --- a/config/config.go +++ b/config/config.go @@ -74,7 +74,11 @@ func Load() (*Config, error) { // would leave the node accepting the all-zero UUID, so Load reports it as an error // (main exits). cfg is still returned complete for NewTestConfig, which sets its own key. var apiKeyErr error - cfg.ApiKey, apiKeyErr = GetEnvAsUUID("API_KEY") + if GetEnv("API_KEY", "") == "" { + apiKeyErr = errors.New("API_KEY is not set") + } else { + cfg.ApiKey, apiKeyErr = GetEnvAsUUID("API_KEY") + } if apiKeyErr == nil && cfg.ApiKey == uuid.Nil { apiKeyErr = errors.New("must not be the all-zero UUID") } diff --git a/config/config_test.go b/config/config_test.go index 670db68b..adb12bb1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,10 +1,35 @@ package config -import "testing" +import ( + "os" + "strings" + "testing" +) -func TestLoadRejectsMissingOrInvalidAPIKey(t *testing.T) { +// unsetEnv removes name for the duration of the test (t.Setenv can only set, not unset). +func unsetEnv(t *testing.T, name string) { + t.Helper() + t.Setenv(name, "") // registers the restore of the original value + if err := os.Unsetenv(name); err != nil { + t.Fatalf("unset %s: %v", name, err) + } +} + +func TestLoadRejectsMissingAPIKey(t *testing.T) { + unsetEnv(t, "API_KEY") + + cfg, err := Load() + if err == nil || !strings.Contains(err.Error(), "API_KEY is not set") { + t.Fatalf("expected a not-set error, got %v", err) + } + if cfg == nil { + t.Fatal("Load must still return the config (NewTestConfig relies on it)") + } +} + +func TestLoadRejectsInvalidAPIKey(t *testing.T) { cases := map[string]string{ - "missing": "", + "empty": "", "not a uuid": "not-a-uuid", "all zero": "00000000-0000-0000-0000-000000000000", } @@ -12,9 +37,13 @@ func TestLoadRejectsMissingOrInvalidAPIKey(t *testing.T) { t.Run(name, func(t *testing.T) { t.Setenv("API_KEY", value) - if _, err := Load(); err == nil { + cfg, err := Load() + if err == nil { t.Fatalf("expected Load to fail for API_KEY=%q", value) } + if cfg == nil { + t.Fatal("Load must still return the config (NewTestConfig relies on it)") + } }) } }