From 19bdc5a4cbf45193e9c3714eec356bf847600ba5 Mon Sep 17 00:00:00 2001 From: Yuzhong Zhang Date: Wed, 26 Aug 2026 17:50:23 +0000 Subject: [PATCH] fix: refuse to start when JWT secret is shorter than 32 bytes Default deployments used a predictable short secret and only warned. Panic on weak secrets so admin JWTs cannot be forged offline. --- utils/bootstrap.go | 13 +++++++++---- utils/bootstrap_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 utils/bootstrap_test.go diff --git a/utils/bootstrap.go b/utils/bootstrap.go index 48a36c7e..4b22b9c9 100644 --- a/utils/bootstrap.go +++ b/utils/bootstrap.go @@ -10,6 +10,13 @@ import ( "github.com/spf13/viper" ) +func validateSecret(secret string) error { + if len(secret) < 32 { + return fmt.Errorf("[service] invalid secret length: got %d, expected at least 32 bytes; please set a stronger `secret` in config or environment", len(secret)) + } + return nil +} + func ReadConf() { viper.SetConfigFile(configFile) @@ -27,10 +34,8 @@ func ReadConf() { viper.AutomaticEnv() viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - secret := viper.GetString("secret") - if len(secret) < 32 { - globals.Warn(fmt.Sprintf("[service] invalid secret length: got %d, expected at least 32 bytes; starting in 10 seconds, please set a stronger `secret` in config or environment; future versions may panic on weak secrets", len(secret))) - time.Sleep(10 * time.Second) + if err := validateSecret(viper.GetString("secret")); err != nil { + panic(err) } if timeout := viper.GetInt("max_timeout"); timeout > 0 { diff --git a/utils/bootstrap_test.go b/utils/bootstrap_test.go new file mode 100644 index 00000000..fc3c6e5c --- /dev/null +++ b/utils/bootstrap_test.go @@ -0,0 +1,29 @@ +package utils + +import ( + "strings" + "testing" +) + +func TestValidateSecret(t *testing.T) { + tests := []struct { + name string + secret string + wantErr bool + }{ + {name: "empty", secret: "", wantErr: true}, + {name: "default example secret", secret: "secret", wantErr: true}, + {name: "31 bytes", secret: strings.Repeat("a", 31), wantErr: true}, + {name: "32 bytes", secret: strings.Repeat("a", 32), wantErr: false}, + {name: "longer than 32", secret: strings.Repeat("b", 64), wantErr: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateSecret(tt.secret) + if (err != nil) != tt.wantErr { + t.Fatalf("validateSecret(%q) error = %v, wantErr %v", tt.secret, err, tt.wantErr) + } + }) + } +}