vxcontrol/pentagi · error

token is malformed

Error message

token is malformed

What it means

ValidateAPIToken parses an API-token JWT (HS256, keyed by MakeJWTSigningKey(globalSalt)). When jwt/v5 parsing fails with jwt.ErrTokenMalformed the function re-wraps it as "token is malformed", meaning the string itself is not a decodable JWT (bad compact JWS structure, invalid base64, unparsable JSON header/payload).

Source

Thrown at backend/pkg/server/auth/api_token_jwt.go:49

			ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(ttl) * time.Second)),
			IssuedAt:  jwt.NewNumericDate(now),
			Subject:   "api_token",
		},
	}
}

func ValidateAPIToken(tokenString, globalSalt string) (*models.APITokenClaims, error) {
	var claims models.APITokenClaims
	token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (any, error) {
		// verify signing algorithm to prevent "alg: none"
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return MakeJWTSigningKey(globalSalt), nil
	})
	if err != nil {
		if errors.Is(err, jwt.ErrTokenMalformed) {
			return nil, fmt.Errorf("token is malformed")
		} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
			return nil, fmt.Errorf("token is either expired or not active yet")
		} else {
			return nil, fmt.Errorf("token invalid: %w", err)
		}
	}

	if !token.Valid {
		return nil, fmt.Errorf("token is invalid")
	}

	return &claims, nil
}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Fix the client to send the exact token string returned by the token-creation API (MakeAPIToken) in the Authorization: Bearer header
  2. Log the received Authorization value (length/segment count) and compare against the stored token to spot truncation or corruption
  3. Check for middleware/proxies that rewrite or trim the Authorization header
  4. Ensure the client does not double-encode (e.g. base64 the token again before sending)

Example fix

// before
req.Header.Set("Authorization", "Bearer " + base64.StdEncoding.EncodeToString([]byte(token)))
// after
req.Header.Set("Authorization", "Bearer " + token)
Defensive patterns

Strategy: validation

Validate before calling

// before sending
parts := strings.Split(token, ".")
if len(parts) != 3 || token == "" {
    return fmt.Errorf("token must be a 3-segment JWT, got %d segments", len(parts))
}

Type guard

func isJWTShape(s string) bool {
    parts := strings.Split(s, ".")
    return len(parts) == 3 && parts[0] != "" && parts[1] != ""
}

Prevention

When it happens

Trigger: Calling ValidateAPIToken (via bearer-token auth in tryProtoTokenAuthentication) with a string that is empty, truncated, has wrong number of dot-separated segments, contains non-base64 characters, or whose payload/header is not valid JSON.

Common situations: Client sends a session cookie or other non-JWT string in the Authorization header; token truncated by an HTTP proxy or copied with missing characters; client encodes token with different padding/URL-safe base64 variant; tests passing placeholder strings like "test-token".

Understand the failure class

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/764f1565fb15fb39. Report an issue: GitHub.