We often find ourselves stuck in situations where it becomes impossible to store the code in our code file. This occurs because the code that we want to run is dynamic in nature and is generated at the runtime. In such a situation, we need techniques to be able to execute code dynamically.
There are multiple ways to insert the code into a running application at run time. One such is by executing the expressions on the fly. But, we must be very cautious while attempting these solutions. In this blog, I am giving some solutions to insert the code successfully without facing any bottlenecks. Let’s explore the solutions as given below.
1.Eval
eval() is the property of the global object. It accepts string as the parameter and evaluates the string as a JavaScript expression. For example, if we need to store SUM of two numbers as string like “2+2”, and evaluate this expression in our code at a point, we simply call eval(“2+2”) and the result is 4.
eval() is executed in the same scope as the function is getting executed, hence eval() has access to the variables. For example, if we execute the code given below:
The result is 21. As eval will be able to access the property x.
When we execute eval directly, it executes in local scope. For example,
And, when we execute eval indirectly, it executes in global scope. For example,
One must be very cautious using eval(). It executes code on behalf of the caller and has access to the execution scope. If it really needs to be used, make sure it is fully under control by having fixed set of expressions that gets evaluated in the eval().
2.New Function()
The syntax to create a function dynamically is
Let’s understand this with an example. Consider we want to create a function which adds two numbers, so the syntax would be as mentioned below:
Now, we can use sum as
Here, the function can be created at run time using just a string. Now, let’s understand this with the help of a use case.
Assume that we have an application, where a bank needs to sanction loan to its customers. But, before the loan is sanctioned, bank wants to execute a rule and then decide if loan can be sanctioned. Below is a sample rule:
If “previous loan amount” is greater than 0, do not sanction else sanction the loan.
In terms of code, it would look like:
But, why do we need this? Why cannot we write it directly in our code? Well, we can write it in our code, but what happens when the rule changes?
We would need to re-write our code, redeploy, which might be tedious and this function might change frequently.
Whereas, if we store this rule function as a string in the server or database which can be changed by the admin, we can quickly absorb changes in the rule at any moment.
Now, let’s discuss about the scope in which this function gets executed. Check out the example mentioned below:
The new function was unable to detect the variable “value”. This is because when we create a function using new parameter, it’s not created in the local scope but in the global scope. There is a reason for this behaviour as well. If the new function had access to the local scope, then, the function would have accessed the local variables too. But, when we minify a js file, the variable names are replaced with characters like “a”, “b”, etc. And, our function would never be able to map “a” to the original variable name, and would break. So, if we need to pass data to a function, we have to use arguments as explained in the rule example above.
This is precisely the reason for eval() being slow and new Function()being fast in execution. Because, eval while executing needs to invoke the JavaScript interpreter to lookup the variables used in the code.
3.Dynamic Require
In JavaScript a file is required inside a function. This solves a very unique use case. For example, we want to upload a certain function exposed as API in our server at run time. We can have those functions written in a file and upload that file to our server via an API.
In that API we would require the uploaded file and iterate over the functions present in that file, and then expose them as API’s. This behaves like a serverless architecture where we can deploy the function dynamically and expose API at run time. Caution needs to be taken while accessing such endpoint as well as while unloading such functions when not in use in the server.
But do these solutions have security-related risks? Let me explain those in the next section.
Security Risks
All the above-mentioned solutions have security risks but we can use them with certain restrictions and limitations. These must be used only by specific set of users only to solve specific use cases that cannot be solved otherwise. Open access to everyone in the code for these methods can lead to system hacking that can further result in data deletion, data stealing, and security tokens stealing. This can lead to RCE and XSS attacks in the system too.
The matrix below illustrates the difference between the above three techniques.
The data can be stored in a database or sometimes in memory (when persistence is not important).
File system is not a good option as we might face issues while managing the file. But, if we need to choose between SQL and NoSQL, I think NoSQL is better for storing this data, as this data itself is dynamic. We can have multiple expressions stored in NoSQL as json or as separate keys if needed.
Things We Need To Ensure
Updating data for eval() is simple, as the expressions are executed each time the code is invoked. So, we update the expressions in our storage and the job is done.
But for a new Function(), we just not need to update the data, but also make sure that a new function is generated using the new data in the running code.
For dynamic requirement as well, we need to make sure that the new file is uploaded and the code from old file is unloaded from the running application.
Practical Examples
- Assume a banking application scenario where the user needs to create/update a formula for interest calculation on the principal amount. In this case we can store the formula as an expression in database and on each end user request calculate the latest interest applicable.
- Assume an application where we have analytics in place for the system data. But the analytics data needs to be manipulated and the logic keeps changing or is user specific. In this case, we can store the manipulation logic in a database and execute when the analytics summary is requested.
Conclusion
Although use of above-mentioned techniques is discouraged, but certain use cases allow us to use them. Whenever we use it, we must test the access control for these executions and must test all the security threats. Consider these as last options in a design. Avoid it as long as you can.
Use it with responsibility. Happy Coding!!