I remember back when I first started my career, one thing that haunted me was dynamic design. It meant from one feature, this input, that output, but before doing it, I had to think hard about whether I could cover all future scenarios. The easiest example is creating dynamic input forms in a content management system - CMS.
A dynamic input form means giving users more control in setting up their data. For example, a system provides a solution for collecting user contact information. The administrator creates forms, attaches them to their website to display a screen requesting users to input their name, age, phone number... If it's just simple text input fields, that's one thing, but in reality, the data collected is very diverse, not to mention they want the ability to control and validate the data that users input.
When working on a project related to minigames - a genre of web games designed for games like spinning a lucky wheel or opening a random gift box to receive rewards that you have probably played. Many people would think that setting up the scenario for dropping gifts is relatively simple. Just set the drop rate for each gift: high-value gifts usually have a very low drop rate while common gifts have a higher rate. However, in reality, it's not that simple. There are many additional requirements for configuring this drop rate, such as ensuring that for the first x days, high-value gifts never drop or that a high-value gift can only drop after a certain period...
At that moment, I wondered if there was a way for users to "program" their desired scenarios. It could be expressed in words or provided with a set of rules for them to create their own scripts. It seemed we were returning to the dynamic design issue. How could we allow users to program the scenarios they want?
It wasn't until later that I found out there is a way in Node.js to help realize the ability to write dynamic scripts for each person.
VM is a powerful API that allows the execution of JavaScript code in a V8 virtual machine context. However, VM is not a security mechanism, so never use it to run untrusted code.
In summary, VM helps us run JavaScript code in plain text form.
const vm = require('node:vm');
const x = 1;
const context = { x: 2 };
vm.createContext(context);
const code = 'x += 40; var y = 17;';
vm.runInContext(code, context);
console.log(context.x); // 42
console.log(context.y); // 17
In the above example, a context (context) is created containing a value x
of 2, where context
acts as a global variable in a new context so that when executing the code in code
, it can still calculate the value of x
+ 40 = 42.
Thus, if we can cleverly combine JS code and user scripts, we can grant users more rights to write their drop scripts, right?
The most obvious application of vm
is to create a sandbox environment to run custom JavaScript code without affecting the main flow. This allows for writing plugins or executing dynamic expressions written in JavaScript instead of creating complex logic in code.
However, as previously warned, vm
is not a security solution, so do not execute untrusted JavaScript code. For example, running code directly inputted by users without review. vm
can access many Node.js APIs if the context
is not tightly controlled. This can lead to security risks.
Although Node.js has made significant efforts to prevent dangerous code snippets in vm
, no one wants to let strangers into their home to perform unpredictable actions. Therefore, the best practice is to always be cautious and only run trusted code in vm
. For added safety, or for user code, it is advisable to run it in a separate process using vm
such as a Docker container. The goal is to isolate it from the main flow and the operating system.
Otherwise, use a more reliable library: isolated-vm.
isolated-vm
is a library for Node.js that allows you to access V8. This also allows for the creation of completely isolated JavaScript environments. This can be a powerful tool for running code in a completely segregated JavaScript environment.
Here is an example using isolated-vm to execute JavaScript code.
const ivm = require('isolated-vm');
const isolate = new ivm.Isolate({ memoryLimit: 128 });
const context = isolate.createContextSync();
const jail = context.global;
jail.setSync('global', jail.derefInto());
jail.setSync('log', function(...args) {
console.log(...args);
});
context.evalSync('log("hello world")'); // hello world
It can be seen that the usage is quite similar to vm
in Node.js. With isolated-vm, there is more isolation than vm
. Thus, it helps you run code more safely. However, the author still encourages applying some security practices such as complete isolation or running in a separate container to avoid unforeseen attacks.
Returning to the gift drop script issue. At this point, we can completely allow users to write their own drop scripts and input them into a JS sandbox execution environment as shown above. Something like:
const userScript = `
const filteredGifts = gifts.filter(g => {
if (daysSinceJoin <= 7) {
// In the first 7 days, only select gifts with value <= 10
return g.value <= 10;
}
return true; // After 7 days, allow all gifts to participate
});
// Randomly select one item from the filtered list
const index = Math.floor(Math.random() * filteredGifts.length);
return filteredGifts[index];
`;
Of course, not every user knows how to program, so it is necessary to make it simpler. The solution is to create an abstraction layer between the user interface and the code generated when they finish setting it up.
By doing this, we can leverage the power of JS while maintaining simplicity. If users know how to program or require complex script scenarios, we can fully support them in writing without needing to change the system.
An important point is to avoid running code in the same main process. It is better to set up a separate server that only receives input and output to mitigate security risks.
In summary, the VM module in Node.js is a powerful tool that allows running JavaScript code in a V8 virtual machine environment. However, VM is not a foolproof security solution, so untrusted code should not be run directly without review. To enhance security, the isolated-vm
library is recommended for creating a isolated JavaScript environment, limiting security risks and isolating code from the main thread or operating system.
5 profound lessons
Every product comes with stories. The success of others is an inspiration for many to follow. 5 lessons learned have changed me forever. How about you? Click now!
Subscribe to receive new article notifications
Comments (0)