VM module trong Node.js - Sức mạnh bị lãng quên

VM module trong Node.js - Sức mạnh bị lãng quên

Tin ngắn hàng ngày dành cho bạn
  • Lại có thêm một công cụ hỗ trợ tìm kiếm nhanh lịch sử gõ lệnh nè mọi người: atuinsh/atuin.

    Điều thú vị là nó dùng SQLite để lưu trữ. Ngoài ra còn cung cấp tính năng đồng bộ hóa (mã hóa) hoàn toàn lịch sử giữa các máy với nhau nữa. Hay ghê 🤓

    » Xem thêm
  • Mình thấy ấn tượng với mô hình gemma-3n-E4B của nhà Google ghê. Đây là một trong những mô hình hứa hẹn mang các mô hình ngôn ngữ lớn xuống chạy trên thiết bị di dộng hoặc web hoặc nhúng (embedded)...

    Cảm giác nó hiểu lời nhắc hơn á, tại vì mình thử nhiều mô hình ít tham số mà nó hay lơ đi lời nhắc của mình. Ví dụ bảo: "Chỉ trả về câu trả lời, không cần giải thích gì thêm" thì rất nhiều cái vẫn cứ phải chêm vào câu mở đầu, giải thích... còn với gemma-3n thì trả lời rất đúng trọng tâm.

    » Xem thêm
  • Trước là sử dụng CSS để tạo hiệu ứng "loading", còn giờ có thể chỉ cần mỗi 1 tệp svg cũng "loading" được luôn: svg-loaders

    » Xem thêm

Vấn đề

Nhớ lại trước đây hồi mới vào nghề, điều mà tôi ám ảnh không kém đó chính là thiết kế động. Nghĩa là từ một tính năng này, đầu vào thế này, đầu ra thế kia, nhưng trước khi làm phải vắt tay lên trán suy nghĩ xem mình có thể bao quát được các trường hợp trong tương lai hay không. Ví dụ dễ thấy nhất là làm khung nhập dữ liệu động trong hệ thống quản trị phần mềm - CMS.

Dynamic Form

Khung nhập động có nghĩa là cho người dùng nhiều quyền hơn trong việc thiết lập dữ liệu của họ. Ví dụ một hệ thống cung cấp giải pháp thu thập thông tin liên hệ của người dùng. Quản trị viên tạo ra các khung, gắn lên trang web của họ để hiển thị một màn hình yêu cầu người dùng nhập tên, tuổi, số điện thoại... Nếu chỉ đơn giản là các ô nhập liệu văn bản thì không nói, trong thực tế dữ liệu thu thập rất đa dạng, chưa kể đến việc họ còn muốn khả năng kiểm soát, kiểm tra tính hợp lệ của dữ liệu mà người dùng nhập vào.

Khi làm việc trong dự án liên quan đến minigame - một thể loại web game được thiết kế cho các trò chơi như quay vòng quay may mắn, hoặc mở một hộp quà bất kỳ để nhận phần thưởng mà chắc hẳn bạn đã từng chơi. Nhiều người sẽ nghĩ phần thiết lập kịch bản để rơi quà tương đối đơn giản. Chỉ cần thiết lập tỉ lệ rơi quà cho mỗi món quà: quà giá trị cao thì thường đặt tỉ lệ rất thấp trong khi quà đại trà thì tỉ lệ cao hơn. Thế nhưng trong thực tế thì câu chuyện không đơn giản như vậy. Rất nhiều yêu cầu thêm tính năng để cấu hình cho phần tỉ lệ rơi quà này, như đảm bảo rằng trong x ngày đầu các quà có giá trị cao không bao giờ được rơi ra hoặc trong vòng bao nhiêu lâu thì mới được rơi một món quà có giá trị...

Lucky Draw

Lúc đó tôi chợt nghĩ phải chăng có một cách nào đó để cho người dùng "lập trình" được kịch bản mà họ muốn. Có thể diễn giải bằng lời nói hoặc cấp một bộ các quy tắc để cho họ tự xào nấu kịch bản. Lúc đó nhiều khả năng chúng ta lại quay về với bài toán thiết kế động. Làm thế nào để cho người dùng tự lập trình được kịch bản mà họ mong muốn?

Mãi sau này tôi mới biết, hoá ra trong Node.js có một cách để giúp mình hiện thực hoá được khả năng viết kịch bản động cho mỗi người.

VM module

VM là một API mạnh mẽ cho phép thực thi mã JavaScript trong một bối cảnh máy ảo V8. Tuy vậy VM không phải là một cơ chế bảo mật cho nên không bao giờ sử dụng nó để chạy mã không đáng tin cậy.

Nói tóm lại thì VM giúp chúng ta chạy mã JavaScript ở dạng văn bản thuần tuý.

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

Ở trong ví dụ trên, tạo ra một ngữ cảnh (context) chứa một giá trị x là 2, context có vai trò như một biến toàn cục trong một ngữ cảnh mới để đến khi thực thi đoạn mã trong code nó vẫn tính ra được giá trị của x + 40 = 42.

Như vậy nếu có thể kết hợp khéo léo giữa mã JS và kịch bản người dùng thì có thể trao nhiều quyền viết kịch bản rơi quà hơn cho người dùng phải không?

Ứng dụng

Dễ thấy ứng dụng đầu tiên của vm là tạo ra một môi trường sandbox để chạy mã JavaScript tuỳ chỉnh mà không liên quan đến luồng chính. Nhờ đó có thể viết các plugin hoặc chạy các biểu thức động viết bằng JavaScript thay vì tạo ra logic phức tạp trong mã.

Tuy nhiên như đã cảnh báo vm không phải là một giải pháp bảo mật cho nên không thực thi mã JavaScript không đáng tin cậy. Ví dụ như chạy trực tiếp mã do người dùng nhập vào mà không qua bước xem xét. vm có thể truy cập được nhiều API của Node.js nếu không kiểm soát chặt chẽ context. Từ đó có thể dẫn đến rủi ro bảo mật.

Mặc dù Node.js đã nỗ lực rất nhiều trong việc ngăn chặn các đoạn mã nguy hiểm trong vm, nhưng chắc chắn không ai muốn cho người lạ vào nhà và thực hiện những hành động kỳ quái mà không thể đoán trước. Vì vậy cách tốt nhất là luôn đề phòng, chỉ chạy các mã tin tưởng trong vm. Cẩn thận hơn hoặc đối với mã người dùng thì nên chạy trong một tiến trình sử dụng vm khác như Docker container. Mục đích là cách ly nó với luồng chính và cả với hệ điều hành.

Còn không thì sử dụng thư viện đáng tin cậy hơn: isolated-vm.

Sử dụng thư viện isolated-vm

isolated-vm là một thư viện cho Node.js, cho phép bạn truy cập vào V8. Điều này cũng cho phép tạo các môi trường JavaScript hoàn toàn tách biệt với nhau. Đây có thể là một công cụ mạnh mẽ để chạy mã trong môi trường JavaScript hoàn toàn biệt lập.

Dưới đây là một ví dụ sử dụng isolated-vm để thực thi mã JavaScript.

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

Có thể thấy cách dùng khá tương đồng với vm trong Node.js. Với isolated-vm, có khả năng cô lập nhiều hơn vm. Từ đó giúp bạn đọc chạy mã một cách an toàn hơn. Tuy nhiên tác giả vẫn khuyến khích nên áp dụng một số quy trình bảo mật như cô lập hoàn toàn hoặc chạy trong một container riêng biệt để tránh các cuộc tấn công chưa ngờ tới.

Quay trở lại với bài toán kịch bản rơi quà. Lúc này chúng ta hoàn toàn có thể cho phép người dùng viết kịch bản rơi quà của riêng họ và đưa nó vào môi trường chạy mã JS sandbox như trên. Một cái gì đó trông giống như là:

const userScript = `
  const filteredGifts = gifts.filter(g => {
    if (daysSinceJoin <= 7) {
      // Trong 7 ngày đầu, chỉ chọn quà có giá trị <= 10
      return g.value <= 10;
    }
    return true; // Sau 7 ngày, cho tất cả quà tham gia
  });

  // Chọn ngẫu nhiên 1 món từ danh sách đã lọc
  const index = Math.floor(Math.random() * filteredGifts.length);
  return filteredGifts[index];
`;

Tất nhiên người dùng không phải ai cũng biết lập trình, thế nên cần phải làm cho nó trở nên đơn giản hơn. Giải pháp là tạo một lớp ảo hoá giữa giao diện người dùng và mã tạo ra khi họ thiết lập xong.

Ảo hoá script

Bằng cách này chúng ta có thể tận dụng được sức mạnh của JS mà vẫn giữ được sự đơn giản. Nếu người dùng biết lập trình hoặc đòi hỏi trường hợp kịch bản phức tạp, chúng ta hoàn toàn có thể hỗ trợ họ viết mà không cần phải thay đổi hệ thống.

Một điều quan trọng là không nên chạy mã trong cùng một tiến trình chính. Tốt hơn hết là thiết lập một máy chủ riêng chỉ nhận đầu vào và đầu ra để tránh rủi ro bảo mật.

Sandbox script

Tổng kết

VM module trong Node.js là một công cụ mạnh mẽ, mang đến khả năng chạy mã JavaScript trong một môi trường máy ảo V8. Tuy nhiên, VM không phải là giải pháp bảo mật hoàn hảo, vì vậy không nên chạy mã không đáng tin cậy trực tiếp mà không qua kiểm duyệt. Để tăng cường an toàn, thư viện isolated-vm được khuyến nghị sử dụng để tạo môi trường JavaScript biệt lập, hạn chế rủi ro bảo mật và cách ly mã khỏi luồng chính hoặc hệ điều hành.

Cao cấp
Hello

Bí mật ngăn xếp của Blog

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (0)

Nội dung bình luận...