Skip to content

Commit

Permalink
sandbox node js (#42)
Browse files Browse the repository at this point in the history
* v1 - sandbox node

* v1 - sandbox node

* v1 - sandbox node

* v1 - sandbox node

* v1 - sandbox node

---------

Co-authored-by: yatchiya <yarab@gmail.com>
  • Loading branch information
YatchiYa and yatchiya authored Sep 2, 2024
1 parent 1d39149 commit b77ddbf
Show file tree
Hide file tree
Showing 35 changed files with 1,638 additions and 0 deletions.
6 changes: 6 additions & 0 deletions packages/sandbox/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist ./dist
CMD ["node", "dist/index.js"]
130 changes: 130 additions & 0 deletions packages/sandbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# SecureNodeJsCodeEnclave

## Project Overview

**SecureNodeJsCodeEnclave** is an advanced system designed to create a secure execution environment for running untrusted JavaScript/TypeScript code in Node.js. It provides isolated execution of multiple files, allows for the installation of specified npm packages within a controlled context, and ensures robust security measures to prevent unauthorized access or malicious activities.

## Features

- **Isolated Execution:** Run untrusted JavaScript/TypeScript code securely in isolated environments.
- **Controlled NPM Package Installation:** Install and manage npm packages in a controlled, secure manner.
- **Security Measures:** Employs various security features to prevent unauthorized access and mitigate potential threats.
- **Extensible and Configurable:** Easily extendable to support additional tools and configurable to meet diverse security needs.

## Project Structure

```plaintext
secure-nodejs-code-enclave/
├── Dockerfile
├── examples
│ └── tools
│ ├── code_generator.json
│ └── text-summerize.json
├── jest.config.js
├── package.json
├── package-lock.json
├── README.md
├── src
│ ├── ai-tools
│ │ ├── code-generator.ts
│ │ └── text-summerize.ts
│ ├── core
│ │ ├── enclave.ts
│ │ ├── file-manager.ts
│ │ ├── package-manager.ts
│ │ └── virtual-fs.ts
│ ├── index.ts
│ ├── security
│ │ ├── resource-limiter.ts
│ │ └── sandbox.ts
│ ├── templates
│ │ ├── code_generator.yaml
│ │ └── text-summerize.yaml
│ ├── __tests__
│ │ ├── enclave.test.ts
│ │ ├── file-manager.test.ts
│ │ ├── package-manager.test.ts
│ │ └── sandbox.test.ts
│ ├── types.ts
│ └── utils
│ ├── error-handler.ts
│ ├── execute_tool_schema.ts
│ ├── logger.ts
│ ├── parse_argument.ts
│ ├── process_tool_schema.ts
│ └── tools.ts
├── test
│ ├── integration
│ │ └── enclave-workflow.test.ts
│ ├── security
│ │ └── sandbox-escape.test.ts
│ └── unit
│ ├── enclave.test.ts
│ ├── file-manager.test.ts
│ └── package-manager.test.ts
├── tsconfig.json
└── tree.txt
```

## Installation
To set up the project locally, follow these steps:

Clone the repository:
```
git clone https://github.com/yourusername/SecureNodeJsCodeEnclave.git
cd SecureNodeJsCodeEnclave
```

Install dependencies:
```
npm install
```

Build the project:

```
npm run build
```

Usage :

## Running code securely:

You can use the core enclave.ts to run untrusted JavaScript/TypeScript code securely by following the provided examples in the examples directory.

## Example commands:

To execute a template file securely :

```
npm run build
npm start -- --file "./examples/tools/text-summerize.json"
```
## Configuring security settings:

Modify the settings in src/security/sandbox.ts to adjust the security parameters such as memory limits, execution time, etc.


## Contributing
We welcome contributions! Please follow these steps:

Fork the repository.
Create a new branch:
```
git checkout -b feature/your-feature-name.
```
Make your changes and commit them:
```git commit -m 'Add some feature'.
Push to the branch: git push origin feature/your-feature-name
```.
Open a pull request.
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Acknowledgments
Special thanks to the contributors who have helped build this project.
8 changes: 8 additions & 0 deletions packages/sandbox/examples/tools/code_generator.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"tool": "python-code-generator",
"params": {
"language": "Python",
"task_description": "🐍 Generate a snake game",
"requirements": "🍎 The game should have a snake that moves around the screen and eats food"
}
}
7 changes: 7 additions & 0 deletions packages/sandbox/examples/tools/text-summerize.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"tool": "text-summerizer",
"params": {
"file_url": "https://www.quantalogic.app/blogs/introduction",
"max_words": 150
}
}
7 changes: 7 additions & 0 deletions packages/sandbox/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
moduleFileExtensions: ['ts', 'js', 'json', 'node'],
};
55 changes: 55 additions & 0 deletions packages/sandbox/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"name": "secure-nodejs-code-enclave",
"version": "1.0.0",
"description": "A secure execution environment for untrusted JavaScript/TypeScript code",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"start_example": "node dist/index_example.js",
"test": "jest --detectOpenHandles",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"start:server": "node dist/index.js --server"
},
"dependencies": {
"@types/js-yaml": "^4.0.9",
"@vitalets/google-translate-api": "^9.2.0",
"axios": "^1.7.5",
"body-parser": "^1.20.2",
"dotenv": "^16.4.5",
"escodegen": "^2.1.0",
"esprima": "^4.0.1",
"express": "^4.19.2",
"js-yaml": "^4.1.0",
"natural": "^8.0.1",
"openai": "^4.56.1",
"qllm-lib": "^3.2.1",
"uuid": "^8.3.2",
"vm2": "^3.9.11",
"yaml": "^2.5.0",
"yargs": "^17.5.1",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/body-parser": "^1.19.5",
"@types/escodegen": "^0.0.10",
"@types/esprima": "^4.0.6",
"@types/express": "^4.17.21",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.106",
"@types/uuid": "^8.3.4",
"@types/yargs": "^17.0.33",
"jest": "^27.5.1",
"ts-jest": "^27.1.5",
"typescript": "^4.9.5"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"testMatch": [
"**/__tests__/**/*.ts",
"**/?(*.)+(spec|test).ts"
]
}
}
41 changes: 41 additions & 0 deletions packages/sandbox/src/__tests__/enclave.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Enclave } from '../../src/core/enclave';
import { EnclaveConfig } from '../../src/types';

jest.setTimeout(30000); // Increase global timeout to 30 seconds

describe('Enclave', () => {
let enclave: Enclave;
const mockConfig: EnclaveConfig = {
cacheDir: './test-cache',
sandboxConfig: { rootDir: './test-sandbox' },
resourceLimits: { maxExecutionTime: 1000, maxMemory: 10 * 1024 * 1024 },
loggerConfig: { debugMode: false }
};

beforeEach(() => {
enclave = new Enclave(mockConfig);
});

afterEach(async () => {
await enclave.cleanup();
});

test('should initialize with correct status', () => {
expect(enclave.getStatus()).toBe('initialized');
});

test('should prepare files and packages', async () => {
const files = [{ name: 'test.js', content: 'console.log("Hello");' }];
const packages = ['lodash'];

await enclave.prepare(files, packages);
expect(enclave.getStatus()).toBe('prepared');
}, 30000); // Increase timeout for this specific test to 30 seconds

test('should execute code', async () => {
const files = [{ name: 'test.js', content: '__lastExpression = 2 + 2;' }];
await enclave.prepare(files, []);
const result = await enclave.execute('test.js');
expect(result).toBe(4);
});
});
26 changes: 26 additions & 0 deletions packages/sandbox/src/__tests__/file-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FileManager } from '../core/file-manager';
import * as fs from 'fs/promises';
import * as path from 'path';

describe('FileManager', () => {
let fileManager: FileManager;
const tempDir = './test-temp';

beforeEach(() => {
fileManager = new FileManager(tempDir);
});

afterEach(async () => {
await fs.rm(tempDir, { recursive: true, force: true });
});

test('should write and read file', async () => {
const fileName = 'test.js';
const content = 'console.log("Hello");';

await fileManager.writeFile(fileName, content);
const readContent = await fileManager.readFile(fileName);

expect(readContent).toBe(content);
});
});
25 changes: 25 additions & 0 deletions packages/sandbox/src/__tests__/package-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { PackageManager } from '../core/package-manager';
import * as fs from 'fs/promises';
import * as path from 'path';

describe('PackageManager', () => {
let packageManager: PackageManager;
const tempDir = './test-temp';
const cacheDir = './test-cache';

beforeEach(() => {
packageManager = new PackageManager(tempDir, cacheDir);
});

afterEach(async () => {
await fs.rm(tempDir, { recursive: true, force: true });
await fs.rm(cacheDir, { recursive: true, force: true });
});

test('should resolve package versions', async () => {
const packages = ['lodash'];
const versions = await packageManager.resolvePackageVersions(packages);
expect(versions).toHaveProperty('lodash');
expect(typeof versions.lodash).toBe('string');
}, 30000); // Add timeout here as well
});
37 changes: 37 additions & 0 deletions packages/sandbox/src/__tests__/sandbox.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Sandbox } from '../../src/security/sandbox';
import { SandboxConfig } from '../../src/types';

describe('Sandbox', () => {
let sandbox: Sandbox;

beforeEach(() => {
const config: SandboxConfig = { rootDir: './test-sandbox' };
sandbox = new Sandbox(config, './test-temp');
});

test('should run code in sandbox', async () => {
const result = await sandbox.run('__lastExpression = 2 + 2');
expect(result).toBe(4);
});

test('should handle multi-line code', async () => {
const code = `
let x = 10;
let y = 20;
__lastExpression = x + y;
`;
const result = await sandbox.run(code);
expect(result).toBe(30);
});

test('should handle async code', async () => {
const code = `
async function test() {
return new Promise(resolve => setTimeout(() => resolve(42), 100));
}
__lastExpression = await test();
`;
const result = await sandbox.run(code);
expect(result).toBe(42);
});
});
Loading

0 comments on commit b77ddbf

Please sign in to comment.