router-for-me/CLIProxyAPI · error

home jwt enrollment_secret is required

Error message

home jwt enrollment_secret is required

What it means

Claim-level validation in parseHomeJWTClaims: the enrollment_secret claim is empty or whitespace-only. The secret authenticates the client's certificate-signing request to the home coordinator during enrollment, so the token is unusable without it.

Source

Thrown at internal/home/certificate.go:104

	}
	payload, errDecode := decodeJWTPart(parts[1])
	if errDecode != nil {
		return claims, errDecode
	}
	if errUnmarshal := json.Unmarshal(payload, &claims); errUnmarshal != nil {
		return claims, errUnmarshal
	}
	if strings.TrimSpace(claims.CertificateID) == "" {
		return claims, fmt.Errorf("home jwt certificate_id is required")
	}
	if strings.TrimSpace(claims.ClusterID) == "" {
		return claims, fmt.Errorf("home jwt cluster_id is required")
	}
	if normalizeFingerprint(claims.CAFingerprint) == "" {
		return claims, fmt.Errorf("home jwt ca_fingerprint is required")
	}
	if strings.TrimSpace(claims.EnrollmentSecret) == "" {
		return claims, fmt.Errorf("home jwt enrollment_secret is required")
	}
	if strings.TrimSpace(claims.IP) == "" || claims.Port <= 0 {
		return claims, fmt.Errorf("home jwt target address is invalid")
	}
	return claims, nil
}

func decodeJWTPart(part string) ([]byte, error) {
	if decoded, errDecode := base64.RawURLEncoding.DecodeString(part); errDecode == nil {
		return decoded, nil
	}
	return base64.URLEncoding.DecodeString(part)
}

func defaultCertificatePaths() (certificatePaths, error) {
	homeDir, errHome := os.UserHomeDir()
	if errHome != nil {
		return certificatePaths{}, errHome

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Request an unredacted enrollment token from the coordinator
  2. If the secret was removed for security when sharing, get a newly minted token instead of the sanitized copy
  3. Verify the claim exists by base64url-decoding the payload before starting the server

Example fix

// before (JWT payload)
{"certificate_id":"c","cluster_id":"k","ca_fingerprint":"AA","enrollment_secret":""}

// after (JWT payload)
{"certificate_id":"c","cluster_id":"k","ca_fingerprint":"AA","enrollment_secret":"s3cr3t"}
Defensive patterns

Strategy: validation

Validate before calling

// verify enrollment_secret claim is non-empty before use
payload, _ := base64.RawURLEncoding.DecodeString(strings.Split(tok, ".")[1])
var c map[string]any
_ = json.Unmarshal(payload, &c)
if s, ok := c["enrollment_secret"].(string); !ok || strings.TrimSpace(s) == "" {
    log.Fatal("enrollment token lacks enrollment_secret")
}

Prevention

When it happens

Trigger: The decoded JWT payload lacks enrollment_secret. Structural parsing and the earlier claim checks passed, so the token was minted incompletely.

Common situations: Token redaction (someone stripped the secret before sharing/logs), coordinator bug, or a token minted for a different flow that never includes the secret.

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/1ff93304bdc8d52d. Report an issue: GitHub.