Node.js vm モジュール
Node.js のvmモジュールは JavaScript の仮想マシンモジュールであり、V8 仮想マシンのコンテキストでコードをコンパイルおよび実行できます。このモジュールは、分離されたコンテキストで JavaScript コードを実行する方法を提供し、現在のプロセスから分離されていますが、特定のコンテキストを使用できます。
主な特徴
- 分離された実行環境:メインプログラムから分離されたサンドボックス環境を作成できます
- 制御可能なコンテキスト:グローバルオブジェクトとコンテキストをカスタマイズできます
- 安全な実行:信頼できないコードがメインプログラムに与える影響を軽減します
- パフォーマンス最適化:スクリプトを事前にコンパイルして、繰り返し実行の効率を向上できます
コア API の紹介
vm.Script クラス
vm.Scriptこのクラスはコードをコンパイルしますが実行はせず、コンパイルされたスクリプトは複数回実行できます。
例
const vm = require('vm');
const script = new vm.Script('x + y', {
filename: 'add.vm',
lineOffset: 0,
displayErrors: true
});
const script = new vm.Script('x + y', {
filename: 'add.vm',
lineOffset: 0,
displayErrors: true
});
パラメータの説明
code:コンパイルする JavaScript コードの文字列options(オプション):filename:スタックトレースに使用するファイル名lineOffset:スクリプトの1行目の行番号のオフセットcolumnOffset:スクリプトの1列目の列番号のオフセットdisplayErrors:エラー発生時にエラーを stderr に出力するかどうかtimeout:実行タイムアウト時間(ミリ秒)cachedData:オプションの V8 コードキャッシュデータが含まれます
vm.createContext([contextObject])
新しいコンテキストオブジェクトを作成し、オプションで既存のオブジェクトを使用して初期化します。
例
const context = vm.createContext({
x: 10,
y: 20
});
x: 10,
y: 20
});
script.runInContext(contextifiedObject[, options])
指定されたコンテキストでコンパイル済みスクリプトを実行します。
例
const result = script.runInContext(context);
console.log(result); // 30 を出力
console.log(result); // 30 を出力
使用シナリオ
1. 信頼できないコードを安全に実行する
例
const vm = require('vm');
const untrustedCode = `
process.exit(1); // 悪意のあるコード
`;
try {
const script = new vm.Script(untrustedCode);
const context = vm.createContext({});
script.runInContext(context);
} catch (err) {
console.log('安全にブロック:', err.message);
}
const untrustedCode = `
process.exit(1); // 悪意のあるコード
`;
try {
const script = new vm.Script(untrustedCode);
const context = vm.createContext({});
script.runInContext(context);
} catch (err) {
console.log('安全にブロック:', err.message);
}
2. 分離されたテスト環境の作成
例
const vm = require('vm');
const testCode = `
function add(a, b) {
return a + b;
}
add(2, 3);
`;
const context = vm.createContext({});
const result = vm.runInContext(testCode, context);
console.log('テスト結果:', result); // 5 を出力
const testCode = `
function add(a, b) {
return a + b;
}
add(2, 3);
`;
const context = vm.createContext({});
const result = vm.runInContext(testCode, context);
console.log('テスト結果:', result); // 5 を出力
3. テンプレートエンジンの実装
例
const vm = require('vm');
function render(template, data) {
const code = `\`${template}\``;
const context = vm.createContext(data);
return vm.runInContext(code, context);
}
const template = 'Hello, ${name}! You are ${age} years old.';
const result = render(template, { name: 'Alice', age: 25 });
console.log(result); // "Hello, Alice! You are 25 years old." を出力
function render(template, data) {
const code = `\`${template}\``;
const context = vm.createContext(data);
return vm.runInContext(code, context);
}
const template = 'Hello, ${name}! You are ${age} years old.';
const result = render(template, { name: 'Alice', age: 25 });
console.log(result); // "Hello, Alice! You are 25 years old." を出力
セキュリティ上の注意点
とはいえvmモジュールはある程度の分離を提供しますが、完全に安全なサンドボックスではありません:
- メモリ制限:悪意のあるコードによってメモリが枯渇する可能性があります
- 同期操作:無限ループはイベントループをブロックします
- コンテキストエスケープ:特定の状況ではグローバルオブジェクトにアクセスできます
より高いセキュリティが必要なシナリオでは、以下を検討することをお勧めします:
- Docker コンテナなどの OS レベルの分離を使用する
- 専用のサンドボックスソリューションを使用する(例:
sandboxモジュール - 実行時間とリソース使用を制限する
パフォーマンス最適化のヒント
1. コンパイル済みスクリプトの再利用
例
const vm = require('vm');
const script = new vm.Script('x * y');
// 同じコンパイル済みスクリプトを複数回実行する
for (let i = 0; i < 100; i++) {
const context = vm.createContext({ x: i, y: 2 });
console.log(script.runInContext(context));
}
const script = new vm.Script('x * y');
// 同じコンパイル済みスクリプトを複数回実行する
for (let i = 0; i < 100; i++) {
const context = vm.createContext({ x: i, y: 2 });
console.log(script.runInContext(context));
}
2. cachedData を使用してコンパイルを高速化する
例
const vm = require('vm');
// 最初にコンパイルしてキャッシュデータを取得する
const script1 = new vm.Script('x + y');
const cachedData = script1.createCachedData();
// 後続ではキャッシュデータを使用してコンパイルを高速化する
const script2 = new vm.Script('x + y', { cachedData });
// 最初にコンパイルしてキャッシュデータを取得する
const script1 = new vm.Script('x + y');
const cachedData = script1.createCachedData();
// 後続ではキャッシュデータを使用してコンパイルを高速化する
const script2 = new vm.Script('x + y', { cachedData });
3. timeout を適切に設定する
例
const script = new vm.Script('while(true) {}', { timeout: 100 });
try {
script.runInContext(vm.createContext({}));
} catch (err) {
console.log('実行タイムアウト:', err.message);
}
try {
script.runInContext(vm.createContext({}));
} catch (err) {
console.log('実行タイムアウト:', err.message);
}
eval との違い
| 特徴 | vm モジュール | eval |
|---|---|---|
| 実行環境 | 分離されたコンテキストを作成できる | 現在のスコープを使用する |
| セキュリティ | 比較的高い | 低い |
| パフォーマンス | 事前コンパイル可能で、繰り返し実行の効率が高い | 毎回解析が必要 |
| デバッグサポート | ファイル名と行番号のマッピングをサポート | サポートしない |
| リソース制御 | タイムアウトなどの制限を設定できる | 制御なし |
まとめ
Node.js のvmモジュールは強力なツールであり、JavaScript コードを分離して実行する必要があるシナリオに特に適しています。完全に安全なサンドボックスソリューションではありませんが、多くの場合に十分なセキュリティ分離とパフォーマンス最適化機能を提供します。正しく使用すると、アプリケーションのセキュリティと柔軟性を大幅に向上できます。
Node.js 組み込みモジュール