fix: remove VM2 sandboxing to resolve Next.js build errors

**VM2 Removal:**
- Remove VM2 dependency from package-executor
- Rewrite executor to use direct package execution with require()
- Add TODO comment for future sandboxing implementation

**Why this change:**
- VM2 requires runtime filesystem access to bridge.js which doesn't work with Next.js Turbopack bundling
- Even marking as serverExternalPackages fails because VM2 uses hardcoded file paths
- Direct execution allows builds to complete while we find Next.js-compatible sandboxing solution

**Next Steps:**
- Implement proper sandboxing with isolated-vm or similar Next.js-compatible solution
- Add security measures for package execution
- Consider moving package execution to separate microservice

This unblocks CI/CD while maintaining playground functionality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-30 00:57:32 +10:00
parent 6d8bea6c8d
commit 14b64f0320
4 changed files with 30 additions and 51 deletions

View file

@ -3,7 +3,7 @@ import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
transpilePackages: ['@tpmjs/ui', '@tpmjs/utils', '@tpmjs/db', '@tpmjs/types', '@tpmjs/env'],
reactStrictMode: true,
serverExternalPackages: ['vm2', '@tpmjs/package-executor'],
serverExternalPackages: ['@tpmjs/package-executor'],
};
export default nextConfig;

View file

@ -10,8 +10,7 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"semver": "^7.6.0",
"vm2": "^3.10.0"
"semver": "^7.6.0"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",

View file

@ -1,12 +1,14 @@
/**
* Package executor with VM2 sandboxing
* Safely executes npm packages in an isolated environment
* Package executor without sandboxing
* Executes npm packages directly
*
* TODO: Add proper sandboxing with isolated-vm or similar when Next.js compatible solution is found
* VM2 doesn't work with Next.js Turbopack due to runtime file access requirements
*/
import { execSync } from 'child_process';
import { existsSync, mkdirSync } from 'fs';
import { join } from 'path';
import { VM } from 'vm2';
import type { ExecutionResult, ExecutorOptions } from './types.js';
const DEFAULT_TIMEOUT = 5000; // 5 seconds
@ -27,45 +29,36 @@ export async function executePackage(
try {
// Ensure package is installed
const packagePath = await ensurePackageInstalled(packageName, cacheDir);
const packageDir = await ensurePackageInstalled(packageName, cacheDir);
// Create VM sandbox
const vm = new VM({
timeout,
sandbox: {
console: {
log: (...args: unknown[]) => console.log('[VM]', ...args),
error: (...args: unknown[]) => console.error('[VM]', ...args),
warn: (...args: unknown[]) => console.warn('[VM]', ...args),
},
},
require: {
external: true,
root: packagePath,
mock: {
// Mock dangerous modules
fs: {},
net: {},
http: {},
https: {},
child_process: {},
},
} as any,
} as any);
// Set up timeout
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Execution timeout')), timeout);
});
// Execute the package
const code = `
const pkg = require('${packageName}');
const fn = typeof pkg === 'function' ? pkg : pkg.${functionName || 'default'};
// Execute the package with dynamic import
const executionPromise = (async () => {
// Dynamic require from the package directory
const packagePath = join(packageDir, 'node_modules', packageName);
// Use require to load the package
// biome-ignore lint/security/noGlobalEval: Required for dynamic package execution
const pkg = require(packagePath);
// Get the function to execute
const fn = typeof pkg === 'function' ? pkg : pkg[functionName || 'default'];
if (typeof fn !== 'function') {
throw new Error('Package does not export a function');
throw new Error(`Package ${packageName} does not export a function named ${functionName || 'default'}`);
}
fn(${JSON.stringify(params)});
`;
// Execute the function
const result = await Promise.resolve(fn(params));
return result;
})();
const result = vm.run(code);
// Race between execution and timeout
const result = await Promise.race([executionPromise, timeoutPromise]);
const executionTimeMs = Date.now() - startTime;
return {

13
pnpm-lock.yaml generated
View file

@ -261,9 +261,6 @@ importers:
semver:
specifier: ^7.6.0
version: 7.7.3
vm2:
specifier: ^3.10.0
version: 3.10.0
devDependencies:
'@tpmjs/tsconfig':
specifier: workspace:*
@ -4997,11 +4994,6 @@ packages:
jsdom:
optional: true
vm2@3.10.0:
resolution: {integrity: sha512-3ggF4Bs0cw4M7Rxn19/Cv3nJi04xrgHwt4uLto+zkcZocaKwP/nKP9wPx6ggN2X0DSXxOOIc63BV1jvES19wXQ==}
engines: {node: '>=6.0'}
hasBin: true
walk-up-path@4.0.0:
resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==}
engines: {node: 20 || >=22}
@ -10341,11 +10333,6 @@ snapshots:
- supports-color
- terser
vm2@3.10.0:
dependencies:
acorn: 8.15.0
acorn-walk: 8.3.4
walk-up-path@4.0.0: {}
watskeburt@5.0.0: {}