Billionmail/BillionMail · critical

no default jwt secret found

Error message

no default jwt secret found

What it means

newJWTService builds the JWT signing secret from config, falling back to a default derived from the REDISPASS environment variable. If both the configured secret and the env-derived default are empty, it panics with 'no default jwt secret found' because issuing JWTs with an empty secret would be insecure. This is fail-fast startup behavior.

Source

Thrown at core/internal/service/rbac/jwt.go:48

	Secret        string        // Secret key for signing JWT
	AccessExpiry  time.Duration // Duration for access token expiry
	RefreshExpiry time.Duration // Duration for refresh token expiry
}

// newJWTService creates a new JWTService instance
func newJWTService() *JWTService {
	defaultJwtSecret := ""

	if dbpass, err := public.DockerEnv("DBPASS"); err == nil {
		defaultJwtSecret += dbpass
	}

	if redispass, err := public.DockerEnv("REDISPASS"); err == nil {
		defaultJwtSecret += redispass
	}

	if defaultJwtSecret == "" {
		panic("no default jwt secret found")
	}

	return &JWTService{
		Secret:        g.Cfg().MustGet(context.Background(), "jwt.secret", defaultJwtSecret).String(),
		AccessExpiry:  time.Duration(g.Cfg().MustGet(context.Background(), "jwt.accessExpiry", 86400).Int()) * time.Second,
		RefreshExpiry: time.Duration(g.Cfg().MustGet(context.Background(), "jwt.refreshExpiry", 86400*7).Int()) * time.Second,
	}
}

// GenerateToken generates a new JWT token
func (s *JWTService) GenerateToken(accountId int64, username string, roles []string) (string, int64, error) {
	expiryTime := time.Now().Add(s.AccessExpiry)
	claims := &JWTCustomClaims{
		AccountId: accountId,
		Username:  username,
		Roles:     roles,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(expiryTime),

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Set the REDISPASS environment variable (as docker-compose.yml does) before starting the service.
  2. Explicitly configure jwt.secret in the GoFrame config file.
  3. Run the service via docker compose so environment variables are injected.
  4. Check .env is loaded in non-Docker dev environments (source it or use a dotenv loader).

Example fix

// before
export RUN_MODE=dev   # REDISPASS missing
./billionmail
// after
export REDISPASS=$(grep REDISPASS .env | cut -d= -f2)
./billionmail
Defensive patterns

Strategy: validation

Validate before calling

// run before starting the app
if os.Getenv("REDISPASS") == "" {
    cfgSecret := readConfig("jwt.secret")
    if cfgSecret == "" {
        log.Fatal("REDISPASS or jwt.secret must be set")
    }
}

Try / catch

func() {
    defer func() {
        if r := recover(); r != nil {
            if strings.Contains(fmt.Sprint(r), "no default jwt secret found") {
                log.Fatal("startup misconfiguration: set REDISPASS or jwt.secret")
            }
            panic(r)
        }
    }()
    service.JWT()
}()

Prevention

When it happens

Trigger: Starting the app without REDISPASS set in the environment and without jwt.secret in config — typically running the binary outside the Docker Compose environment.

Common situations: Local development without the .env file / docker compose env; REDISPASS renamed or removed in a custom deployment; config file missing the jwt.secret key.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/893d6c39e7cbf3c2. Report an issue: GitHub.