Tencent/WeKnora · critical

failed to generate OIDC state signing key: %v

Error message

failed to generate OIDC state signing key: %v

What it means

OIDC state signing key initialization mirrors the JWT secret path: it uses JWT_SECRET if set, otherwise generates 32 random bytes with crypto/rand. If rand.Read fails, the code panics because OIDC state parameters cannot be signed without a key, breaking CSRF protection for the OIDC flow.

Source

Thrown at internal/utils/oidc_state.go:40

	Nonce       string `json:"nonce"`
	RedirectURI string `json:"redirect_uri,omitempty"`
	IssuedAt    int64  `json:"iat"`
}

var (
	oidcStateSecretOnce sync.Once
	oidcStateSecret     string
)

func oidcStateSigningKey() string {
	oidcStateSecretOnce.Do(func() {
		if envSecret := strings.TrimSpace(os.Getenv("JWT_SECRET")); envSecret != "" {
			oidcStateSecret = envSecret
			return
		}
		randomBytes := make([]byte, 32)
		if _, err := rand.Read(randomBytes); err != nil {
			panic(fmt.Sprintf("failed to generate OIDC state signing key: %v", err))
		}
		oidcStateSecret = base64.StdEncoding.EncodeToString(randomBytes)
	})
	return oidcStateSecret
}

// SignOIDCState returns a tamper-evident state token: base64url(payload).base64url(hmac).
func SignOIDCState(payload *OIDCStatePayload) (string, error) {
	if payload == nil {
		return "", errors.New("oidc state payload is required")
	}
	if strings.TrimSpace(payload.Nonce) == "" {
		return "", errors.New("oidc state nonce is required")
	}
	if strings.TrimSpace(payload.RedirectURI) == "" {
		return "", errors.New("oidc state redirect_uri is required")
	}
	if payload.IssuedAt == 0 {

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Set JWT_SECRET so the env-var branch is taken and rand is never needed
  2. Adjust the sandbox/seccomp policy to permit getrandom(2) or ensure /dev/urandom exists
  3. Restart the process — the failure is often transient during early boot
  4. Provision the key from a file or secret manager instead of generating at runtime

Example fix

// before
randomBytes := make([]byte, 32)
if _, err := rand.Read(randomBytes); err != nil { panic(...) }
// after
# deployment fix
JWT_SECRET=<base64 32-byte secret>  # set in environment, skipping rand fallback
Defensive patterns

Strategy: fallback

Validate before calling

if strings.TrimSpace(os.Getenv("JWT_SECRET")) == "" {
    if _, err := rand.Read(make([]byte, 32)); err != nil {
        return fmt.Errorf("cannot obtain entropy for OIDC state key: %w", err)
    }
}

Type guard

func oidcStateKeyAvailable() bool {
    return strings.TrimSpace(os.Getenv("JWT_SECRET")) != ""
}

Try / catch

func oidcStateSecretSafe() (s string, err error) {
    defer func() { if r := recover(); r != nil { err = fmt.Errorf("oidc state key panicked: %v", r) } }()
    return getOIDCStateSecret(), nil
}

Prevention

When it happens

Trigger: First use of the OIDC state singleton when JWT_SECRET is unset and the OS entropy source fails (getrandom syscall blocked by seccomp, missing /dev/urandom, early-boot kernel without random ready).

Common situations: Hardened container seccomp profiles blocking getrandom; minimal/embedded runtimes; same root cause as the JWT secret panic — environments where crypto/rand cannot read 32 bytes.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/9c1747bb47587401. Report an issue: GitHub.