@usebruno/js 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,47 +1,17 @@
1
- const { NodeVM } = require('@usebruno/vm2');
2
- const { runScriptInNodeVm } = require('../sandbox/node-vm');
3
1
  const chai = require('chai');
4
- const path = require('path');
5
- const http = require('http');
6
- const https = require('https');
7
- const stream = require('stream');
8
- const util = require('util');
9
- const zlib = require('zlib');
10
- const url = require('url');
11
- const punycode = require('punycode');
12
- const fs = require('fs');
13
- const { get } = require('lodash');
2
+ const uuid = require('uuid');
14
3
  const Bru = require('../bru');
15
4
  const BrunoRequest = require('../bruno-request');
16
5
  const BrunoResponse = require('../bruno-response');
17
- const Test = require('../test');
18
- const TestResults = require('../test-results');
19
6
  const { cleanJson } = require('../utils');
20
7
  const { createBruTestResultMethods } = require('../utils/results');
21
-
22
- // Inbuilt Library Support
23
- const ajv = require('ajv');
24
- const addFormats = require('ajv-formats');
25
- const atob = require('atob');
26
- const btoa = require('btoa');
27
- const lodash = require('lodash');
28
- const moment = require('moment');
29
- const uuid = require('uuid');
30
- const nanoid = require('nanoid');
31
- const axios = require('axios');
32
- const fetch = require('node-fetch');
33
- const CryptoJS = require('crypto-js');
34
- const NodeVault = require('node-vault');
35
- const xml2js = require('xml2js');
36
- const cheerio = require('cheerio');
37
- const tv4 = require('tv4');
8
+ const { runScriptInNodeVm } = require('../sandbox/node-vm');
38
9
  const jsonwebtoken = require('jsonwebtoken');
39
10
  const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
40
- const { mixinTypedArrays } = require('../sandbox/mixins/typed-arrays');
41
11
 
42
12
  class TestRuntime {
43
13
  constructor(props) {
44
- this.runtime = props?.runtime || 'vm2';
14
+ this.runtime = props?.runtime || 'quickjs';
45
15
  }
46
16
 
47
17
  async runTests(
@@ -67,27 +37,10 @@ class TestRuntime {
67
37
  const promptVariables = request?.promptVariables || {};
68
38
  const iterationDetails = request?.runnerIterationDetails || {};
69
39
  const assertionResults = request?.assertionResults || [];
70
- const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, undefined, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, collectionName, promptVariables);
40
+ const certsAndProxyConfig = request?.certsAndProxyConfig;
41
+ const bru = new Bru(this.runtime, envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, undefined, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, collectionName, promptVariables, certsAndProxyConfig);
71
42
  const req = new BrunoRequest(request, historyLogger);
72
43
  const res = new BrunoResponse(response);
73
- const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
74
- const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
75
- const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []);
76
- const additionalContextRootsAbsolute = lodash
77
- .chain(additionalContextRoots)
78
- .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr)))
79
- .value();
80
-
81
- const whitelistedModules = {};
82
-
83
- for (let module of moduleWhitelist) {
84
- try {
85
- whitelistedModules[module] = require(module);
86
- } catch (e) {
87
- // Ignore
88
- console.warn(e);
89
- }
90
- }
91
44
 
92
45
  // extend bru with result getter methods
93
46
  const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
@@ -114,10 +67,6 @@ class TestRuntime {
114
67
  jwt: jsonwebtoken
115
68
  };
116
69
 
117
- if (this.runtime === 'vm2') {
118
- mixinTypedArrays(context);
119
- }
120
-
121
70
  if (onConsoleLog && typeof onConsoleLog === 'function') {
122
71
  const customLogger = (type) => {
123
72
  return (...args) => {
@@ -133,20 +82,14 @@ class TestRuntime {
133
82
  };
134
83
  }
135
84
 
136
- if(runRequestByItemPathname) {
85
+ if (runRequestByItemPathname) {
137
86
  context.bru.runRequest = runRequestByItemPathname;
138
87
  }
139
88
 
140
89
  let scriptError = null;
141
90
 
142
91
  try {
143
- if (this.runtime === 'quickjs') {
144
- await executeQuickJsVmAsync({
145
- script: testsFile,
146
- context: context,
147
- collectionPath
148
- });
149
- } else if (this.runtime === 'nodevm') {
92
+ if (this.runtime === 'nodevm') {
150
93
  await runScriptInNodeVm({
151
94
  script: testsFile,
152
95
  context,
@@ -154,49 +97,12 @@ class TestRuntime {
154
97
  scriptingConfig
155
98
  });
156
99
  } else {
157
- // default runtime is vm2
158
- const vm = new NodeVM({
159
- sandbox: context,
160
- require: {
161
- context: 'sandbox',
162
- external: true,
163
- builtin: ['*'],
164
- root: [collectionPath, ...additionalContextRootsAbsolute],
165
- mock: {
166
- // node libs
167
- path,
168
- stream,
169
- util,
170
- url,
171
- http,
172
- https,
173
- punycode,
174
- zlib,
175
- // 3rd party libs
176
- ajv,
177
- 'ajv-formats': addFormats,
178
- btoa,
179
- atob,
180
- lodash,
181
- moment,
182
- uuid,
183
- nanoid,
184
- axios,
185
- chai,
186
- 'node-fetch': fetch,
187
- 'crypto-js': CryptoJS,
188
- 'xml2js': xml2js,
189
- cheerio,
190
- tv4,
191
- 'jsonwebtoken': jsonwebtoken,
192
- ...whitelistedModules,
193
- fs: allowScriptFilesystemAccess ? fs : undefined,
194
- 'node-vault': NodeVault
195
- }
196
- }
100
+ // default runtime is `quickjs`
101
+ await executeQuickJsVmAsync({
102
+ script: testsFile,
103
+ context: context,
104
+ collectionPath
197
105
  });
198
- const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
199
- await asyncVM();
200
106
  }
201
107
  } catch (error) {
202
108
  scriptError = error;
@@ -20,7 +20,7 @@ const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime, mode) => {
20
20
 
21
21
  class VarsRuntime {
22
22
  constructor(props) {
23
- this.runtime = props?.runtime || 'vm2';
23
+ this.runtime = props?.runtime || 'quickjs';
24
24
  this.mode = props?.mode || 'developer';
25
25
  }
26
26
 
@@ -37,7 +37,8 @@ class VarsRuntime {
37
37
  }
38
38
 
39
39
  const promptVariables = request?.promptVariables || {};
40
- const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, undefined, secretVars, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, undefined, promptVariables);
40
+ const certsAndProxyConfig = request?.certsAndProxyConfig;
41
+ const bru = new Bru(this.runtime, envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, undefined, secretVars, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, undefined, promptVariables, certsAndProxyConfig);
41
42
  const req = new BrunoRequest(request, historyLogger);
42
43
  const res = createResponseParser(response);
43
44
 
@@ -0,0 +1,369 @@
1
+ const vm = require('node:vm');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const nodeModule = require('node:module');
5
+
6
+ const { isBuiltinModule, isPathWithinAllowedRoots } = require('./utils');
7
+
8
+ /**
9
+ * Resolve a local module path, handling files and directories
10
+ * Follows Node.js resolution algorithm:
11
+ * 1. Exact path (with extension)
12
+ * 2. Path + .js extension
13
+ * 3. Directory with package.json (main field)
14
+ * 4. Directory with index.js
15
+ * @param {string} fromDir - Directory to resolve from
16
+ * @param {string} moduleName - Module name/path
17
+ * @returns {string} Resolved absolute path
18
+ */
19
+ function resolveLocalModulePath(fromDir, moduleName) {
20
+ const basePath = path.resolve(fromDir, moduleName);
21
+
22
+ // 1. If has extension, use as-is
23
+ if (path.extname(moduleName)) {
24
+ return path.normalize(basePath);
25
+ }
26
+
27
+ // 2. Try with .js extension
28
+ const withJs = basePath + '.js';
29
+ if (fs.existsSync(withJs)) {
30
+ return path.normalize(withJs);
31
+ }
32
+
33
+ // 3. Check if it's a directory
34
+ if (fs.existsSync(basePath) && fs.statSync(basePath).isDirectory()) {
35
+ // 3a. Check for package.json with main field
36
+ const pkgPath = path.join(basePath, 'package.json');
37
+ if (fs.existsSync(pkgPath)) {
38
+ try {
39
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
40
+ if (pkg.main) {
41
+ const mainPath = path.resolve(basePath, pkg.main);
42
+ if (fs.existsSync(mainPath)) {
43
+ return path.normalize(mainPath);
44
+ }
45
+ }
46
+ } catch {
47
+ // Ignore JSON parse errors, fall through to index.js
48
+ }
49
+ }
50
+
51
+ // 3b. Check for index.js
52
+ const indexPath = path.join(basePath, 'index.js');
53
+ if (fs.existsSync(indexPath)) {
54
+ return path.normalize(indexPath);
55
+ }
56
+ }
57
+
58
+ // 4. Fall back to original path (will likely fail with file not found)
59
+ return path.normalize(basePath);
60
+ }
61
+
62
+ /**
63
+ * Creates a custom require function with enhanced security and local module support
64
+ * @param {Object} options - Configuration options
65
+ * @param {string} options.collectionPath - Path to the collection directory
66
+ * @param {Object} options.isolatedContext - The VM isolated context created with vm.createContext()
67
+ * @param {string} options.currentModuleDir - Current module directory for resolving relative paths
68
+ * @param {Map} options.localModuleCache - Cache for loaded modules
69
+ * @param {string[]} options.additionalContextRootsAbsolute - Additional allowed root paths
70
+ * @returns {Function} Custom require function
71
+ */
72
+ function createCustomRequire({
73
+ collectionPath,
74
+ isolatedContext,
75
+ currentModuleDir = collectionPath,
76
+ localModuleCache = new Map(),
77
+ additionalContextRootsAbsolute = []
78
+ }) {
79
+ return (moduleName) => {
80
+ const normalizedModuleName = moduleName.replace(/\\/g, '/');
81
+
82
+ // 1. Handle local modules (./path, ../path)
83
+ if (normalizedModuleName.startsWith('./') || normalizedModuleName.startsWith('../')) {
84
+ return loadLocalModule({
85
+ moduleName: normalizedModuleName,
86
+ collectionPath,
87
+ isolatedContext,
88
+ localModuleCache,
89
+ currentModuleDir,
90
+ additionalContextRootsAbsolute
91
+ });
92
+ }
93
+
94
+ // 2. Handle absolute paths - route through local module security checks
95
+ // This prevents bypassing additionalContextRoots by using absolute paths
96
+ if (path.isAbsolute(normalizedModuleName)) {
97
+ return loadLocalModule({
98
+ moduleName: normalizedModuleName,
99
+ collectionPath,
100
+ isolatedContext,
101
+ localModuleCache,
102
+ currentModuleDir,
103
+ additionalContextRootsAbsolute
104
+ });
105
+ }
106
+
107
+ // 3. Handle Node.js builtin modules
108
+ // Note: Builtins are loaded via native require, bypassing VM isolation.
109
+ // This is intentional - [`developer` mode] node-vm isolation need not be strict for builtins.
110
+ if (isBuiltinModule(moduleName)) {
111
+ return require(moduleName);
112
+ }
113
+
114
+ // 4. Handle npm modules - load INTO vm context
115
+ return loadNpmModule({
116
+ moduleName,
117
+ collectionPath,
118
+ isolatedContext,
119
+ localModuleCache
120
+ });
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Loads a local module from the filesystem with security checks and caching
126
+ * @param {Object} options - Configuration options
127
+ * @returns {*} The exported content of the loaded module
128
+ * @throws {Error} When module is outside collection path or cannot be loaded
129
+ */
130
+ function loadLocalModule({
131
+ moduleName,
132
+ collectionPath,
133
+ isolatedContext,
134
+ localModuleCache,
135
+ currentModuleDir,
136
+ additionalContextRootsAbsolute = []
137
+ }) {
138
+ // Validate the raw module name doesn't try to escape allowed roots
139
+ const preliminaryPath = path.resolve(currentModuleDir, moduleName);
140
+ if (!isPathWithinAllowedRoots(path.normalize(preliminaryPath), additionalContextRootsAbsolute)) {
141
+ const allowedRootsDisplay = additionalContextRootsAbsolute.map((root) => ` - ${root}`).join('\n');
142
+ throw new Error(
143
+ `Access to files outside of the allowed context roots is not allowed: ${moduleName}\n\n`
144
+ + `Allowed context roots:\n${allowedRootsDisplay}`
145
+ );
146
+ }
147
+
148
+ // Resolve the module path, handling files and directories
149
+ const normalizedFilePath = resolveLocalModulePath(currentModuleDir, moduleName);
150
+
151
+ // Final security check after resolution
152
+ if (!isPathWithinAllowedRoots(normalizedFilePath, additionalContextRootsAbsolute)) {
153
+ const allowedRootsDisplay = additionalContextRootsAbsolute.map((root) => ` - ${root}`).join('\n');
154
+ throw new Error(
155
+ `Access to files outside of the allowed context roots is not allowed: ${moduleName}\n\n`
156
+ + `Allowed context roots:\n${allowedRootsDisplay}`
157
+ );
158
+ }
159
+
160
+ // Check cache - we cache moduleObj, return its exports
161
+ if (localModuleCache.has(normalizedFilePath)) {
162
+ return localModuleCache.get(normalizedFilePath).exports;
163
+ }
164
+
165
+ if (!fs.existsSync(normalizedFilePath)) {
166
+ throw new Error(`Cannot find module ${moduleName}`);
167
+ }
168
+
169
+ const moduleCode = fs.readFileSync(normalizedFilePath, 'utf8');
170
+ const moduleObj = { exports: {} };
171
+ const moduleDir = path.dirname(normalizedFilePath);
172
+
173
+ // Pre-populate cache with moduleObj BEFORE execution to handle circular dependencies
174
+ // This allows re-entrant requires to get partial exports (Node.js behavior)
175
+ // We cache moduleObj (not moduleObj.exports) so that module.exports reassignment works
176
+ localModuleCache.set(normalizedFilePath, moduleObj);
177
+
178
+ // Create require function for nested imports
179
+ const moduleRequire = createCustomRequire({
180
+ collectionPath,
181
+ isolatedContext,
182
+ currentModuleDir: moduleDir,
183
+ localModuleCache,
184
+ additionalContextRootsAbsolute
185
+ });
186
+
187
+ try {
188
+ // Wrap module code in a function that receives CJS parameters
189
+ const wrappedCode = `(function(module, exports, require, __filename, __dirname) {\n${moduleCode}\n})`;
190
+ const compiledScript = new vm.Script(wrappedCode, { filename: normalizedFilePath });
191
+ const moduleFunction = compiledScript.runInContext(isolatedContext);
192
+ moduleFunction(moduleObj, moduleObj.exports, moduleRequire, normalizedFilePath, moduleDir);
193
+ return moduleObj.exports;
194
+ } catch (error) {
195
+ // Remove failed module from cache to allow retry
196
+ localModuleCache.delete(normalizedFilePath);
197
+ throw new Error(`Error loading local module ${moduleName}: ${error.message}`);
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Executes a module in the VM context with caching and special file handling
203
+ * @param {Object} options - Configuration options
204
+ * @returns {*} The exported content of the loaded module
205
+ * @throws {Error} When module cannot be loaded
206
+ */
207
+ function executeModuleInVmContext({
208
+ resolvedPath,
209
+ moduleName,
210
+ isolatedContext,
211
+ collectionPath,
212
+ localModuleCache
213
+ }) {
214
+ // Check cache - we cache moduleObj, return its exports
215
+ if (localModuleCache.has(resolvedPath)) {
216
+ return localModuleCache.get(resolvedPath).exports;
217
+ }
218
+
219
+ // Native modules (.node files) - fall back to host require
220
+ // Note: This bypasses VM isolation for native addons.
221
+ // This is intentional - [`developer` mode] node-vm isolation need not be strict for native modules.
222
+ if (resolvedPath.endsWith('.node')) {
223
+ const result = require(resolvedPath);
224
+ // Wrap in moduleObj format for consistent cache retrieval
225
+ localModuleCache.set(resolvedPath, { exports: result });
226
+ return result;
227
+ }
228
+
229
+ // JSON files - parse directly
230
+ if (resolvedPath.endsWith('.json')) {
231
+ const jsonContent = fs.readFileSync(resolvedPath, 'utf8');
232
+ const result = JSON.parse(jsonContent);
233
+ // Wrap in moduleObj format for consistent cache retrieval
234
+ localModuleCache.set(resolvedPath, { exports: result });
235
+ return result;
236
+ }
237
+
238
+ // JavaScript files
239
+ const moduleSource = fs.readFileSync(resolvedPath, 'utf8');
240
+ const moduleDir = path.dirname(resolvedPath);
241
+ const moduleObj = { exports: {} };
242
+
243
+ // Pre-populate cache with moduleObj BEFORE execution to handle circular dependencies
244
+ // This allows re-entrant requires to get partial exports (Node.js behavior)
245
+ // We cache moduleObj (not moduleObj.exports) so that module.exports reassignment works
246
+ localModuleCache.set(resolvedPath, moduleObj);
247
+
248
+ const moduleRequire = createNpmModuleRequire({
249
+ collectionPath,
250
+ isolatedContext,
251
+ currentModuleDir: moduleDir,
252
+ localModuleCache
253
+ });
254
+
255
+ try {
256
+ // Wrap module code in a function that receives CJS parameters
257
+ const wrappedCode = `(function(module, exports, require, __filename, __dirname) {\n${moduleSource}\n})`;
258
+ const compiledScript = new vm.Script(wrappedCode, { filename: resolvedPath });
259
+ const moduleFunction = compiledScript.runInContext(isolatedContext);
260
+ moduleFunction(moduleObj, moduleObj.exports, moduleRequire, resolvedPath, moduleDir);
261
+ } catch (error) {
262
+ // Remove failed module from cache to allow retry
263
+ localModuleCache.delete(resolvedPath);
264
+ const stack = error.stack || '';
265
+ throw new Error(`Error loading module ${moduleName}: ${error.message}\nStack: ${stack}`);
266
+ }
267
+
268
+ return moduleObj.exports;
269
+ }
270
+
271
+ /**
272
+ * Loads an npm module into the vm context
273
+ * @param {Object} options - Configuration options
274
+ * @returns {*} The exported content of the loaded module
275
+ * @throws {Error} When module cannot be resolved or loaded
276
+ */
277
+ function loadNpmModule({
278
+ moduleName,
279
+ collectionPath,
280
+ isolatedContext,
281
+ localModuleCache
282
+ }) {
283
+ let resolvedPath;
284
+
285
+ // Module resolution order:
286
+ // 1. Collection's node_modules (user-installed packages for their collection)
287
+ // 2. Bruno's node_modules (fallback for built-in dependencies)
288
+ //
289
+ // This order ensures user packages take precedence, allowing users to:
290
+ // - Override Bruno's bundled package versions
291
+ // - Install collection-specific dependencies
292
+ if (collectionPath) {
293
+ try {
294
+ const collectionRequire = nodeModule.createRequire(path.join(collectionPath, 'package.json'));
295
+ resolvedPath = collectionRequire.resolve(moduleName);
296
+ } catch {
297
+ // Module not found in collection, continue to fallback
298
+ }
299
+ }
300
+
301
+ // Fall back to Bruno's node_modules
302
+ if (!resolvedPath) {
303
+ try {
304
+ resolvedPath = require.resolve(moduleName, { paths: module.paths });
305
+ } catch (mainError) {
306
+ throw new Error(
307
+ `Could not resolve module "${moduleName}": ${mainError.message}\n\n`
308
+ + `Install it with: npm install ${moduleName}`
309
+ );
310
+ }
311
+ }
312
+
313
+ return executeModuleInVmContext({
314
+ resolvedPath,
315
+ moduleName,
316
+ isolatedContext,
317
+ collectionPath,
318
+ localModuleCache
319
+ });
320
+ }
321
+
322
+ /**
323
+ * Creates require function for npm module dependencies
324
+ * @param {Object} options - Configuration options
325
+ * @returns {Function} Custom require function for npm module dependencies
326
+ */
327
+ function createNpmModuleRequire({
328
+ collectionPath,
329
+ isolatedContext,
330
+ currentModuleDir,
331
+ localModuleCache
332
+ }) {
333
+ const moduleRequire = nodeModule.createRequire(path.join(currentModuleDir, 'index.js'));
334
+
335
+ return (moduleName) => {
336
+ // Handle relative imports within npm module
337
+ if (moduleName.startsWith('./') || moduleName.startsWith('../')) {
338
+ const resolvedPath = moduleRequire.resolve(moduleName);
339
+ return executeModuleInVmContext({
340
+ resolvedPath,
341
+ moduleName,
342
+ isolatedContext,
343
+ collectionPath,
344
+ localModuleCache
345
+ });
346
+ }
347
+
348
+ // Handle builtins
349
+ // Note: Builtins are loaded via native require, bypassing VM isolation.
350
+ // This is intentional - [`developer` mode] node-vm isolation need not be strict for builtins.
351
+ if (isBuiltinModule(moduleName)) {
352
+ return require(moduleName);
353
+ }
354
+
355
+ // Handle npm dependencies - resolve from current module's directory
356
+ const resolvedPath = moduleRequire.resolve(moduleName);
357
+ return executeModuleInVmContext({
358
+ resolvedPath,
359
+ moduleName,
360
+ isolatedContext,
361
+ collectionPath,
362
+ localModuleCache
363
+ });
364
+ };
365
+ }
366
+
367
+ module.exports = {
368
+ createCustomRequire
369
+ };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Constants for the Node.js VM sandbox.
3
+ *
4
+ * ECMAScript built-ins (Object, Array, Function, etc.)
5
+ * are NOT passed from the host. The VM provides its own versions, ensuring
6
+ * consistent prototype chains for libraries that use introspection.
7
+ *
8
+ * Handled separately in index.js:
9
+ * - global/globalThis: Points to isolated context (not host)
10
+ * - require: createCustomRequire() (custom module loader)
11
+ */
12
+
13
+ /**
14
+ * Safe globals to pass from host to VM context.
15
+ *
16
+ * ECMAScript built-ins (Object, Array, Function, String, Number,
17
+ * Boolean, Symbol, Date, RegExp, Map, Set, Promise, JSON, Math,
18
+ * parseInt, etc.) are intentionally NOT included here.
19
+ *
20
+ * The VM context provides its own versions of these, which ensures consistent
21
+ * prototype chains. Passing host versions causes prototype mismatches.
22
+ *
23
+ * Only Node.js-specific and Web APIs that the VM doesn't provide are listed.
24
+ */
25
+ const safeGlobals = [
26
+ 'process',
27
+
28
+ // Node.js timers (not part of ECMAScript)
29
+ 'setTimeout',
30
+ 'setInterval',
31
+ 'clearTimeout',
32
+ 'clearInterval',
33
+ 'setImmediate',
34
+ 'clearImmediate',
35
+ 'queueMicrotask',
36
+
37
+ // Node.js globals
38
+ 'Buffer',
39
+
40
+ // Error types - needed for instanceof checks with errors from host APIs/modules
41
+ 'Error',
42
+ 'TypeError',
43
+ 'ReferenceError',
44
+ 'SyntaxError',
45
+ 'RangeError',
46
+ 'URIError',
47
+ 'EvalError',
48
+ 'AggregateError',
49
+
50
+ // URL APIs (WHATWG - not ECMAScript)
51
+ 'URL',
52
+ 'URLSearchParams',
53
+
54
+ // Encoding APIs
55
+ 'TextEncoder',
56
+ 'TextDecoder',
57
+ 'atob',
58
+ 'btoa',
59
+
60
+ // Fetch API (Node 18+)
61
+ 'fetch',
62
+ 'Request',
63
+ 'Response',
64
+ 'Headers',
65
+ 'FormData',
66
+ 'AbortController',
67
+ 'AbortSignal',
68
+ 'Blob',
69
+
70
+ // Streams API
71
+ 'ReadableStream',
72
+ 'WritableStream',
73
+ 'TransformStream',
74
+
75
+ // Internationalization (needs host's locale data)
76
+ 'Intl',
77
+
78
+ // Web Crypto API
79
+ 'crypto',
80
+
81
+ // WebAssembly
82
+ 'WebAssembly',
83
+
84
+ // Performance API
85
+ 'performance',
86
+
87
+ // Events API
88
+ 'Event',
89
+ 'EventTarget',
90
+ 'CustomEvent',
91
+
92
+ // Message passing
93
+ 'MessageChannel',
94
+ 'MessagePort'
95
+ ];
96
+
97
+ module.exports = {
98
+ safeGlobals
99
+ };