업데이트로 돌아가기
New releaseAug 15, 2026

vm2 v3.11.6

Proxy 기반 가로채기를 통해 내장 모듈 및 호스트 리소스에 대한 액세스를 제한하면서 신뢰할 수 없는 코드를 실행하는 Node.js용 격리 JavaScript 샌드박스

공유

vm2 [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![License][license-image]][license-url] Node.js CI [![Known Vulnerabilities][snyk-image]][snyk-url]

vm2는 허용 목록에 등록된 Node.js 내장 모듈로 신뢰할 수 없는 코드를 실행할 수 있는 샌드박스입니다.

중요 보안 고지

vm2를 사용하기 전에, vm2가 어떻게 작동하고 어떤 한계가 있는지 이해해야 합니다.

vm2는 신뢰할 수 없는 JavaScript 코드를 애플리케이션과 동일한 Node.js 프로세스 내에서 샌드박싱하려고 시도합니다. 이는 샌드박스와 호스트 환경 사이의 모든 상호작용을 가로채고 중재하는 복잡한 Proxy 네트워크를 통해 이루어집니다.

근본적인 과제

JavaScript는 매우 동적인 언어입니다. 객체는 프로토타입 체인을 통해 접근될 수 있고, 생성자는 오류 객체를 통해 도달될 수 있으며, 심볼은 프로토콜 훅을 제공하고, 비동기 실행은 타이밍 창을 만듭니다. JavaScript에서 한 객체에서 다른 객체로 이동할 수 있는 방법이 매우 많기 때문에 완벽하게 차단된 인프로세스 샌드박스를 구축하는 것은 극히 어렵습니다.

우리는 이러한 현실에 대해 정직합니다: 아무리 노력해도 연구자와 보안 전문가들은 vm2 샌드박스를 탈출할 수 있는 새로운 방법을 계속해서 발견하고 있습니다. 보고되는 취약점은 적극적으로 패치하고 있지만, 인프로세스 샌드박싱의 숨바꼭질 같은 특성상 다음을 의미합니다:

  1. 향후에도 새로운 우회 방법이 발견될 가능성이 높습니다. 알려진 취약점은 보안 권고에서 확인하세요.
  2. 최신 보안 수정 사항을 적용하려면 vm2를 계속 업데이트해야 합니다. 보안 권고를 구독하고 신속하게 업데이트하세요.
  3. vm2는 유일한 방어선이 되어서는 안 됩니다. 신뢰할 수 없는 코드를 실행할 때는 심층 방어(Defense in depth)가 필수적입니다.

더 강력한 대안

더 강력한 격리 보장이 필요하다면, 진정한 프로세스 또는 하드웨어 수준의 격리를 제공하는 다음 대안을 고려하세요:

솔루션접근 방식성능절충 사항
isolated-vm별도의 V8 isolate(별도의 V8 힙)빠름유지보수 모드; 수동 V8 업데이트 필요
별도 프로세스 / Worker권한이 제한된 child_process 또는 Worker 스레드중간높은 IPC 오버헤드; 데이터를 직렬화해야 함
컨테이너 / VMDocker, gVisor, Firecracker느림시작 오버헤드; 리소스 집약적
관리형 서비스클라우드 기반 코드 실행(예: AWS Lambda, Cloudflare Workers)가변적네트워크 지연; 외부 종속성

vm2가 여전히 적합할 수 있는 경우

vm2는 다음과 같은 경우에 적합할 수 있습니다:

  • 호스트 객체와의 긴밀한 통합과 빠른 동기식 통신이 필요할 때
  • 신뢰할 수 없는 코드가 비교적 신뢰할 수 있는 소스(예: 내부 도구, 검증된 작성자가 있는 플러그인 시스템)에서 온 경우
  • vm2를 다른 보안 계층(네트워크 격리, 파일시스템 제한, 리소스 제한)과 함께 사용할 때
  • 위험을 수용하고 보안 업데이트를 적극적으로 모니터링할 때

완전히 신뢰할 수 없는 소스(예: 임의의 사용자 제출물)의 코드를 실행하는 경우, 더 강력한 격리 보장을 제공하는 솔루션을 사용할 것을 강력히 권장합니다.

기능

  • 신뢰할 수 없는 코드를 단일 프로세스에서 여러분의 코드와 나란히 안전하게 실행
  • 샌드박스의 콘솔 출력을 완전히 제어
  • 샌드박스는 프로세스의 메서드에 제한적으로 접근 가능
  • 샌드박스에서 모듈(내장 및 외부)을 require할 수 있음
  • 특정(또는 모든) 내장 모듈에 대한 접근을 제한할 수 있음
  • 샌드박스 간에 메서드를 안전하게 호출하고 데이터와 콜백을 교환할 수 있음
  • 알려진 탈출 방법에 대한 패치로 적극적으로 유지보수됨 (보안 고지 참조)
  • 트랜스파일러 지원

작동 방식

  • 내부 VM 모듈을 사용하여 보안 컨텍스트를 생성합니다.
  • Proxy를 사용하여 샌드박스 탈출을 방지합니다.
  • 내장 require를 재정의하여 모듈 접근을 제어합니다.

vm2 내부 구조에 대한 자세한 내용은 CONTRIBUTING.md 파일을 참조하세요.

Node의 vm과 vm2의 차이점은 무엇인가요?

직접 실험해 보세요:```js import { runInNewContext } from "node:vm";

runInNewContext('this.constructor.constructor("return process")().exit()'); console.log('Never gets executed.');

I received no translatable content — the chunk body after "INPUT:" is empty. Please provide chunk 3 of 51 and I will translate it.```js
import { VM } from 'vm2';

new VM().run('this.constructor.constructor("return process")().exit()');
// Throws ReferenceError: process is not defined

설치```sh

npm install vm2

## 빠른 예제```js
import { VM } from 'vm2';

const vm = new VM();
vm.run(`process.exit()`); // TypeError: process.exit is not a function

입력 청크 내용이 비어 있습니다. 번역할 텍스트가 제공되지 않았습니다.```js import { NodeVM } from 'vm2';

const vm = new NodeVM({ require: { external: true, root: './', }, });

vm.run( var request = require('request'); request('http://www.google.com', function (error, response, body) { console.error(error); if (!error && response.statusCode == 200) { console.log(body); // Show the HTML for the Google homepage. } });, 'vm.js', );

## Documentation

-   [VM](#vm)
-   [NodeVM](#nodevm)
-   [VMScript](#vmscript)
-   [오류 처리](#error-handling)
-   [샌드박스 코드 디버깅](#debugging-a-sandboxed-code)
-   [읽기 전용 객체](#read-only-objects-experimental)
-   [보호된 객체](#protected-objects-experimental)
-   [샌드박스 간 관계](#cross-sandbox-relationships)
-   [CLI](#cli)
-   [2.x에서 3.x로의 변경 사항](https://github.com/patriksimek/vm2/wiki/2.x-to-3.x-changes)
-   [1.x 및 2.x 문서](https://github.com/patriksimek/vm2/wiki/1.x-and-2.x-docs)
-   [기여](https://github.com/patriksimek/vm2/wiki/Contributing)

## VM

VM은 `require` 기능 없이 신뢰할 수 없는 코드를 동기적으로 실행하기 위한 간단한 샌드박스입니다. JavaScript 내장 객체와 Node의 `Buffer`만 사용할 수 있습니다. 스케줄링 함수(`setInterval`, `setTimeout`, `setImmediate`)는 기본적으로 사용할 수 없습니다.

**옵션:**

-   `timeout` - 밀리초 단위의 스크립트 제한 시간. **경고**: 이 옵션은 `allowAsync=false`와 함께 사용하는 것이 좋습니다. 또한 샌드박스에서 반환된 객체를 조작하면 임의의 코드가 실행되어 제한 시간을 우회할 수 있습니다. 반환된 객체가 `typeof`로 원시 값인지 확인하고, 그렇지 않은 경우 해당 객체를 완전히 폐기해야 합니다(이러한 객체로 로깅을 하거나 오류 메시지를 생성하는 것도 다시 임의의 코드를 실행할 수 있습니다).
-   `sandbox` - VM의 전역 객체.
-   `compiler` - `javascript` (기본값), `typescript`, `coffeescript` 또는 사용자 정의 컴파일러 함수. 값이 `typescript` 또는 `coffeescript`로 설정된 경우 라이브러리는 컴파일러가 사전 설치되어 있을 것으로 기대합니다.
-   `eval` - `false`로 설정하면 `eval` 또는 함수 생성자(`Function`, `GeneratorFunction` 등)에 대한 모든 호출이 `EvalError`를 발생시킵니다(기본값: `true`).
-   `wasm` - `false`로 설정하면 WebAssembly 모듈을 컴파일하려는 모든 시도가 `WebAssembly.CompileError`를 발생시킵니다(기본값: `true`). 참고: 보안상의 이유로 `WebAssembly.JSTag`는 샌드박스 내부에서 제거되므로 wasm 코드는 JavaScript 예외를 catch할 수 없습니다.
-   `allowAsync` - `false`로 설정하면 `async`를 사용하여 코드를 실행하려는 모든 시도가 `VMError`를 발생시킵니다(기본값: `true`).
-   `bufferAllocLimit` - 샌드박스 내부에서 발생하는 단일 `Buffer.alloc` / `Buffer.allocUnsafe` / `Buffer.allocUnsafeSlow` / `Buffer(N)` / `new Buffer(N)` 요청에 대한 최대 크기(바이트)입니다. 이 상한을 초과하는 요청은 호스트 할당을 수행하지 않고 동기적으로 `RangeError`를 발생시킵니다. 기본값: `Infinity` (상한 없음, 완전히 하위 호환 가능). 메모리 제약 환경(Docker / Kubernetes / Lambda / serverless)에서 신뢰할 수 없는 코드를 실행하는 임베더는 `timeout`을 선택하는 것과 같은 방식으로 계층적 DoS 방어의 일환으로 유한한 상한(예: `32 * 1024 * 1024`)을 선택해야 합니다. 아래의 [강화 권장 사항](#hardening-recommendations)을 참조하세요.

**중요**: 제한 시간은 `run`을 통해 실행하는 동기 코드에만 적용됩니다. 제한 시간은 VM이 반환하는 어떤 메서드에도 **적용되지 않습니다**. 제한 시간이 작동하지 않는 상황이 있습니다 - [#244](https://github.com/patriksimek/vm2/pull/244)를 참조하세요.```js
import { VM } from 'vm2';

const vm = new VM({
	timeout: 1000,
	allowAsync: false,
	sandbox: {},
});

vm.run('process.exit()'); // throws ReferenceError: process is not defined

VM에서 값을 검색할 수도 있습니다.```js let number = vm.run('1337'); // returns 1337

**TIP**: 더 많은 사용 예시는 테스트를 참조하세요.

## NodeVM

`VM`과 달리 `NodeVM`을 사용하면 일반 Node의 컨텍스트에서와 같은 방식으로 모듈을 require할 수 있습니다.

**옵션:**

-   `console` - 콘솔을 활성화하려면 `inherit`, 이벤트로 리디렉션하려면 `redirect`, 콘솔을 비활성화하려면 `off` (기본값: `inherit`).
-   `sandbox` - VM의 전역 객체.
-   `compiler` - `javascript`(기본값), `typescript`, `coffeescript` 또는 사용자 정의 컴파일러 함수(코드와 해당 파일 경로를 전달받음). 값이 `typescript` 또는 `coffeescript`로 설정된 경우 라이브러리는 해당 컴파일러가 사전 설치되어 있을 것을 기대합니다.
-   `eval` - `false`로 설정하면 `eval` 또는 함수 생성자(`Function`, `GeneratorFunction` 등)에 대한 모든 호출이 `EvalError`를 던집니다 (기본값: `true`).
-   `wasm` - `false`로 설정하면 WebAssembly 모듈을 컴파일하려는 모든 시도가 `WebAssembly.CompileError`를 던집니다 (기본값: `true`). 참고: 보안상의 이유로 `WebAssembly.JSTag`는 샌드박스 내부에서 제거되므로 wasm 코드는 JavaScript 예외를 잡을 수 없습니다.
-   `bufferAllocLimit` - `VM`과 동일한 의미 — 샌드박스 내부에서 발생하는 단일 `Buffer.alloc` 계열 요청에 대한 최대 크기(바이트)입니다. 기본값: `Infinity`. [강화 권장 사항](#hardening-recommendations)을 참조하세요.
-   `sourceExtensions` - 소스 코드로 처리할 파일 확장자의 배열 (기본값: `['js']`).
-   `require` - `require` 메서드를 활성화하려면 `true`, 객체 또는 Resolver (기본값: `false`).
-   `require.external` - 값은 `true`, 허용된 외부 모듈의 배열, 또는 객체일 수 있습니다 (기본값: `false`). `/node_modules/${any_allowed_external_module}/(?!/node_modules/)`와 일치하는 모든 경로는 require가 허용됩니다.
-   `require.external.modules` - 허용된 외부 모듈의 배열. 와일드카드도 지원하므로, 예를 들어 `['@scope/*-ver-??]`를 지정하면 `@scope/something-ver-aa`, `@scope/other-ver-11` 등의 이름 형태를 가진 모든 모듈을 사용할 수 있습니다. `*` 와일드카드는 경로 구분 기호와 일치하지 않습니다.
-   `require.external.transitive` - 외부 모듈의 전이적 의존성이 허용되는지 여부를 나타내는 부울 값 (기본값: `false`). **경고**: 모듈이 전이적으로 require되면, 로드되기 전에는 불가능했더라도 이후에는 모든 모듈이 그 모듈을 정상적으로 require할 수 있게 됩니다.
-   `require.builtin` - 허용된 내장 모듈의 배열. 전체 허용 시 ["\*"]를 사용할 수 있습니다 (기본값: 없음). **경고**: "\*"는 새로운 내장 모듈이 추가될 수 있으므로 위험할 수 있습니다.
-   `require.root` - 로컬 모듈을 require할 수 있는 제한된 경로 (기본값: 모든 경로).
-   `require.mock` - 목(mock) 모듈 모음 (외부 및 내장 모두).
-   `require.context` - `host`(기본값)는 호스트에서 모듈을 require하고 샌드박스로 프록시합니다. `sandbox`는 샌드박스에서 모듈을 로드, 컴파일, require합니다. `callback(moduleFilename, ext)`는 모듈별로 컨텍스트를 동적으로 선택합니다. 아무것도 지정하지 않으면 기본값은 샌드박스입니다. `events`를 제외한 내장 모듈은 항상 호스트에서 require되어 샌드박스로 프록시됩니다.
-   `require.import` - 시작 시 NodeVM에 로드될 모듈의 배열.
-   `require.resolve` - 기존 node 조회 경로에서 모듈을 찾지 못한 경우 사용되는 추가 조회 함수.
-   `require.customRequire` - 호스트에서 모듈을 로드하기 위해 `require` 함수 대신 사용.
-   `require.strict` - require로 로드된 모듈에 엄격 모드를 강제하지 않으려면 `false` (기본값: `true`).
-   `require.fs` - 사용자 정의 파일 시스템 구현.
-   `nesting` - **경고**: 이 기능을 허용하면 스크립트가 모든 호스트 모듈을 require할 수 있는 NodeVM을 생성할 수 있으므로 보안 위험이 있습니다. VM 중첩을 활성화하려면 `true` (기본값: `false`).
-   `wrapper` - `commonjs`(기본값)는 스크립트를 CommonJS 래퍼로 감싸고, `none`은 스크립트가 반환한 값을 가져옵니다.
-   `argv` - `process.argv`에 전달할 배열.
-   `env` - `process.env`에 전달할 객체.
-   `strict` - `true`로 설정하면 모듈이 엄격 모드로 로드됩니다 (기본값: `false`).

**중요**: NodeVM에는 Timeout이 적용되지 않으므로 `while (true) {}` 또는 유사한 악성 코드에 면역이 아닙니다.

**기억하세요**: 허용하는 모듈이 많을수록 샌드박스는 더 취약해집니다.```js
import { NodeVM } from 'vm2';

const vm = new NodeVM({
	console: 'inherit',
	sandbox: {},
	require: {
		external: true,
		builtin: ['fs', 'path'],
		root: './',
		mock: {
			fs: {
				readFileSync: () => 'Nice try!',
			},
		},
	},
});

// Sync

let functionInSandbox = vm.run('module.exports = function(who) { console.log("hello "+ who); }');
functionInSandbox('world');

// Async

let functionWithCallbackInSandbox = vm.run('module.exports = function(who, callback) { callback("hello "+ who); }');
functionWithCallbackInSandbox('world', greeting => {
	console.log(greeting);
});

wrappernone으로 설정되면, NodeVM은 동기 코드에 대해 VM처럼 동작합니다.```js assert.ok(vm.run('return true') === true);

**팁**: 더 많은 사용 예시는 테스트를 참조하세요.

### 상대 경로로 모듈 로드하기

상대 경로로 모듈을 로드하려면, 스크립트가 문자열인 경우 실행 중인 스크립트의 전체 경로를 vm의 `run` 메서드의 두 번째 인자로 전달해야 합니다. 그러면 파일 이름이 스크립트에서 생성된 모든 스택 추적에 표시됩니다.```js
vm.run('require("foobar")', '/data/myvmscript.js');

실행 중인 스크립트가 VMScript인 경우 경로는 VMScript 생성자에 지정됩니다.```js const script = new VMScript('require("foobar")', { filename: '/data/myvmscript.js' }); vm.run(script);

### Resolver

Resolver는 `makeResolverFromLegacyOptions`를 통해 생성할 수 있으며 여러 `NodeVM` 인스턴스에서 사용하여 컴파일된 모듈 코드를 공유함으로써 로드 시간을 단축할 수 있습니다. `NodeVM`의 첫 번째 예제는 `makeResolverFromLegacyOptions`를 사용하여 다음과 같이 다시 작성할 수 있습니다.```js
const resolver = makeResolverFromLegacyOptions({
	external: true,
	builtin: ['fs', 'path'],
	root: './',
	mock: {
		fs: {
			readFileSync: () => 'Nice try!',
		},
	},
});
const vm = new NodeVM({
	console: 'inherit',
	sandbox: {},
	require: resolver,
});

VMScript

미리 컴파일된 스크립트를 사용하면 성능을 높일 수 있습니다. 미리 컴파일된 VMScript는 여러 번 실행할 수 있습니다. 중요한 점은 코드가 특정 VM(컨텍스트)에 바인딩되는 것이 아니라, 각 실행 직전에 해당 실행에 대해서만 바인딩된다는 것입니다.```js import { VM, VMScript } from 'vm2';

const vm = new VM(); const script = new VMScript('Math.random()'); console.log(vm.run(script)); console.log(vm.run(script));

`VM` 및 `NodeVM` 모두에서 작동합니다.```js
import { NodeVM, VMScript } from 'vm2';

const vm = new NodeVM();
const script = new VMScript('module.exports = Math.random()');
console.log(vm.run(script));
console.log(vm.run(script));

코드는 처음 실행될 때 자동으로 컴파일됩니다. script.compile()을 사용하면 언제든지 코드를 컴파일할 수 있습니다. 코드가 컴파일된 후에는 이 메서드가 아무 효과가 없습니다.

오류 처리

코드 컴파일 및 동기 코드 실행 중 발생하는 오류는 try-catch로 처리할 수 있습니다. 비동기 코드 실행 중 발생하는 오류는 Node의 processuncaughtException 이벤트 핸들러를 연결하여 처리할 수 있습니다.```js try { var script = new VMScript('Math.random()').compile(); } catch (err) { console.error('Failed to compile script.', err); }

try { vm.run(script); } catch (err) { console.error('Failed to execute script.', err); }

process.on('uncaughtException', err => { console.error('Asynchronous error caught.', err); });

## 샌드박스 코드 디버깅

샌드박스에서 실행 중인 코드를 일반 프로세스에서 실행되는 것처럼 디버그하거나 검사할 수 있습니다.

-   중단점을 사용할 수 있습니다(스크립트 파일 이름을 지정해야 함)
-   `debugger` 키워드를 사용할 수 있습니다.
-   step-in을 사용하여 샌드박스에서 실행 중인 코드 내부로 들어갈 수 있습니다.

### 예제

/tmp/main.js:```js
import { VM, VMScript } from 'vm2';
import { readFileSync } from 'node:fs';

const file = `${__dirname}/sandbox.js`;

// By providing a file name as second argument you enable breakpoints
const script = new VMScript(readFileSync(file), file);

new VM().run(script);

/tmp/sandbox.js```js const foo = 'ahoj';

// The debugger keyword works just fine everywhere. // Even without specifying a file name to the VMScript object. debugger;

## 읽기 전용 객체 (실험적)

샌드박스 스크립트가 프록시된 객체의 속성을 추가, 변경 또는 삭제하지 못하도록 하려면 `freeze` 메서드를 사용하여 객체를 읽기 전용으로 만들 수 있습니다. 이는 VM 내에서만 유효합니다. 동결된 객체는 깊게 영향을 받습니다. 기본 타입은 동결할 수 없습니다.

**`freeze`를 사용하지 않은 예시:**```js
const util = {
	add: (a, b) => a + b,
};

const vm = new VM({
	sandbox: { util },
});

vm.run('util.add = (a, b) => a - b');
console.log(util.add(1, 1)); // returns 0

freeze를 사용한 예제:```js const vm = new VM(); // Objects specified in the sandbox cannot be frozen. vm.freeze(util, 'util'); // Second argument adds object to global.

vm.run('util.add = (a, b) => a - b'); // Fails silently when not in strict mode. console.log(util.add(1, 1)); // returns 2

**중요:** 이미 VM으로 프록시된 객체는 동결(freeze)할 수 없습니다.

## 보호된 객체(실험적)

`freeze`와 달리 이 메서드는 샌드박스 처리된 스크립트가 객체에 속성을 추가, 변경 또는 삭제할 수 있게 하며, 한 가지 예외가 있습니다. 즉, 함수를 연결하는 것은 불가능합니다. 따라서 샌드박스 처리된 스크립트는 `toJSON`, `toString` 또는 `inspect`와 같은 메서드를 수정할 수 없습니다.

**중요:** 이미 VM으로 프록시된 객체는 보호할 수 없습니다.

## 교차 샌드박스 관계```js
const assert = require('assert');
const { VM } = require('vm2');

const sandbox = {
	object: new Object(),
	func: new Function(),
	buffer: new Buffer([0x01, 0x05]),
};

const vm = new VM({ sandbox });

assert.ok(vm.run(`object`) === sandbox.object);
assert.ok(vm.run(`object instanceof Object`));
assert.ok(vm.run(`object`) instanceof Object);
assert.ok(vm.run(`object.__proto__ === Object.prototype`));
assert.ok(vm.run(`object`).__proto__ === Object.prototype);

assert.ok(vm.run(`func`) === sandbox.func);
assert.ok(vm.run(`func instanceof Function`));
assert.ok(vm.run(`func`) instanceof Function);
assert.ok(vm.run(`func.__proto__ === Function.prototype`));
assert.ok(vm.run(`func`).__proto__ === Function.prototype);

assert.ok(vm.run(`new func() instanceof func`));
assert.ok(vm.run(`new func()`) instanceof sandbox.func);
assert.ok(vm.run(`new func().__proto__ === func.prototype`));
assert.ok(vm.run(`new func()`).__proto__ === sandbox.func.prototype);

assert.ok(vm.run(`buffer`) === sandbox.buffer);
assert.ok(vm.run(`buffer instanceof Buffer`));
assert.ok(vm.run(`buffer`) instanceof Buffer);
assert.ok(vm.run(`buffer.__proto__ === Buffer.prototype`));
assert.ok(vm.run(`buffer`).__proto__ === Buffer.prototype);
assert.ok(vm.run(`buffer.slice(0, 1) instanceof Buffer`));
assert.ok(vm.run(`buffer.slice(0, 1)`) instanceof Buffer);

CLI

명령줄에서 vm2를 사용하기 전에 npm install vm2 -g로 전역 설치하세요.```sh vm2 ./script.js

## 보안 강화 권장사항

vm2는 샌드박스 탈출(신뢰할 수 없는 코드가 호스트 영역(realm)에 접근하는 것)을 방지합니다. 그러나 자체적으로는 모든 형태의 리소스 고갈이나 서비스 거부(DoS)를 막지 **않습니다**. 신뢰할 수 없는 코드를 실행하는 임베더(embedder)는 샌드박스 주변에 다음과 같은 다계층 방어를 추가해야 합니다.

### 1. `bufferAllocLimit`으로 메모리 할당 제한

공격자가 제어하는 `N` 값을 사용한 단일 `Buffer.alloc(N)` 호출은 V8의 `timeout`으로 중단할 수 없는 동기식 호스트 C++ 할당으로 실행됩니다. 메모리가 제한된 환경에서는 약 100바이트의 샌드박스 페이로드가 100MB 이상의 호스트 RSS 급증을 유발하고 OOM으로 호스트 프로세스를 중단시킬 수 있습니다. 개별 할당을 제한하려면 `bufferAllocLimit`(예: `32 * 1024 * 1024`)을 설정하세요:```js
const vm = new VM({
	timeout: 1000,
	bufferAllocLimit: 32 * 1024 * 1024,
	allowAsync: false,
});

이 상한은 더 이상 사용되지 않는 Buffer(N)new Buffer(N) 경로에도 적용됩니다. 집계 소진(작은 할당이 많은 경우, Buffer.concat, Uint8Array, String.repeat, Array(n).fill() 등)은 이 상한으로 다루어지지 않습니다 — 전체 범위를 위해서는 호스트 측 메모리 제한(--max-old-space-size, 컨테이너 제한, cgroup)과 결합하십시오.

2. 호스트 측 unhandledRejection 핸들러 설치

호스트 프로세스를 중단시키는 DoS의 한 유형이 있습니다. 샌드박스 코드가 async function, async function* 또는 await using을 생성하고, 그 본문이 스택 형식화 중 호스트 영역 오류를 유발하는 값을 던질 때 발생합니다(예: e.name = Symbol(); e.stack). V8은 영역의 내장 Promise를 통해 거부 프로미스를 생성하므로 vm2의 Promise 하위 클래스 래핑을 우회하며, 그 결과 거부는 unhandledRejection으로 호스트에 전달됩니다. Node 15 이상에서는 기본 동작이 프로세스를 종료하는 것입니다.

이를 해결하려면 관찰 가능한 호스트 동작을 변경해야 하므로 vm2는 기본적으로 수정 사항을 제공하지 않습니다. 임베더는 샌드박스에서 발생한 거부를 삼키거나 기록하는 프로세스 수준 핸들러를 설치해야 합니다:```js // Recommended: filter rejections that originated inside vm2 and swallow them, // while letting your own host-side rejections propagate. process.on('unhandledRejection', (reason, promise) => { // Heuristic: rejections from the sandbox frequently surface as values // without proper Error semantics, or with stacks pointing at vm.js. // Adjust the predicate to match your application. if (looksLikeSandboxOrigin(reason)) { return; // swallow — don't terminate the process } // Otherwise: handle (or rethrow) as normal for your host code. yourLogger.error('unhandled rejection', reason); });

애플리케이션에 처리되지 않은 거부(unhandled rejections)의 다른 출처가 없다면, 전체를 삼키고 로그를 남기는 방식이 허용됩니다:```js
process.on('unhandledRejection', reason => {
	yourLogger.warn('swallowed sandbox rejection', reason);
});

A scoped fix may ship behind an opt-in swallowSandboxUnhandledRejections flag in a future minor release; until then, the host-side handler is the recommended mitigation.

3. 프로세스 수준 메모리 상한으로 실행

bufferAllocLimit을 설정하더라도, 호스트 프로세스를 워크로드에 맞는 --max-old-space-size(또는 이에 상응하는 컨테이너 메모리 한도)로 실행하세요. 상한은 단일 할당 프리미티브로부터 보호하고, OS 수준 한도는 누적 소진 및 vm2가 아직 제한하지 않은 향후 할당 프리미티브로부터 보호합니다.

4. require.builtin: ['*']을 비샌드박스 구성으로 취급

'*' 와일드카드는 child_process, fs, dgram, net, http, dns를 포함한 대부분의 Node 내장 모듈로 확장됩니다. 이들은 완전한 호스트 기능 프리미티브입니다. '*'에서는 샌드박스에서 require('child_process').execSync('id')에 도달할 수 있습니다. vm2의 '*' 의미론은 의도적입니다(일부 임베더는 신뢰할 수 있지만 격리된 코드를 실행합니다). 그러나 신뢰할 수 없는 코드의 기본값으로 사용해서는 안 됩니다. 샌드박스에 실제로 필요한 가장 작은 모듈 집합의 명시적 허용 목록을 선호하세요.

5. nesting: true는 탈출구입니다

nesting: true를 사용하면 샌드박스 코드가 require('vm2')를 호출하고 중첩된 NodeVM을 구성할 수 있습니다. 중첩된 VM의 require 구성은 이를 구성하는 샌드박스 코드가 선택하며, 외부 VM에 의해 제한되지 않습니다. 구체적으로:```js const vm = new NodeVM({ nesting: true, require: { builtin: [] } }); vm.run( const { NodeVM: NVM } = require('vm2'); // Inner VM's config is whatever the sandbox writes here: const inner = new NVM({ require: { builtin: ['child_process'] } }); inner.run('require("child_process").execSync("id")'); // RCE);

`nesting: true`를 설정하면 실질적으로 샌드박스에 자신과 동일한 신뢰 수준을 부여하게 됩니다. **신뢰할 수 없는 코드에는 `nesting: true`를 활성화하지 마십시오.** 샌드박스화된 코드 자체는 신뢰하지만 보안 이외의 이유로 VM 스타일 실행 의미(새로운 전역 객체, 제어된 타임아웃)를 원하는 경우에만 사용하십시오.

`nesting: true`는 **명시적인 `require` 구성 객체가 필요합니다** (예: `require: { builtin: [] }` 또는 `require: {}`). 그 외의 모든 형태 — `require: false`, `require: undefined`, `require: null` 또는 `require`를 완전히 생략하는 경우 — 는 생성 시 `VMError`를 발생시킵니다 (GHSA-m4wx-m65x-ghrr, GHSA-8hg8-63c5-gwmx를 대체). 이러한 모든 형태는 NESTING_OVERRIDE 전용 리졸버를 생성합니다. 즉, 샌드박스는 `require('vm2')`만 할 수 있고 그 외에는 아무것도 할 수 없으며, 이는 합법적인 용도가 없는 순수한 탈출 프리미티브입니다. 모든 require를 거부하려면 `nesting: true`를 제거하십시오. 중첩 VM을 허용하려면 명시적인 `require` 구성을 제공하여 호출 지점에서 트레이드오프가 보이도록 하십시오.

## 알려진 문제

-   프록시된 클래스를 상속하는 클래스를 정의할 수 없습니다. 여기에는 `Object.create`에서 프록시된 클래스를 사용하는 경우도 포함됩니다.
-   직접 eval은 작동하지 않습니다.
-   샌드박스 배열을 로깅하면 속성에 배열 부분이 반복됩니다.
-   소스 코드 변환으로 인해 함수의 소스 문자열이 달라질 수 있습니다.
-   샌드박스 내부에서 node 프로세스를 충돌시킬 수 있는 방법이 있습니다. [강화 권장 사항](#hardening-recommendations)을 참조하십시오.

[npm-image]: https://img.shields.io/npm/v/vm2.svg
[npm-url]: https://www.npmjs.com/package/vm2
[license-image]: https://img.shields.io/npm/l/vm2.svg
[license-url]: https://raw.githubusercontent.com/patriksimek/vm2/resurrection/LICENSE.md
[downloads-image]: https://img.shields.io/npm/dm/vm2.svg
[downloads-url]: https://www.npmjs.com/package/vm2
[snyk-image]: https://snyk.io/test/github/patriksimek/vm2/badge.svg
[snyk-url]: https://snyk.io/test/github/patriksimek/vm2

카테고리