@usebruno/js 0.38.0 → 0.39.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebruno/js",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -17,7 +17,6 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@usebruno/common": "0.12.1",
20
- "@usebruno/crypto-js": "^3.1.9",
21
20
  "@usebruno/query": "0.1.0",
22
21
  "ajv": "^8.12.0",
23
22
  "ajv-formats": "^2.1.1",
@@ -27,7 +26,7 @@
27
26
  "chai": "^4.3.7",
28
27
  "chai-string": "^1.5.0",
29
28
  "cheerio": "^1.0.0",
30
- "crypto-js": "^4.1.1",
29
+ "crypto-js": "^4.2.0",
31
30
  "json-query": "^2.2.2",
32
31
  "lodash": "^4.17.21",
33
32
  "moment": "^2.29.4",
@@ -46,4 +45,4 @@
46
45
  "rollup": "3.29.5",
47
46
  "rollup-plugin-terser": "^7.0.2"
48
47
  }
49
- }
48
+ }
@@ -135,7 +135,8 @@ class TestRuntime {
135
135
  if (this.runtime === 'quickjs') {
136
136
  await executeQuickJsVmAsync({
137
137
  script: testsFile,
138
- context: context
138
+ context: context,
139
+ collectionPath
139
140
  });
140
141
  } else if (this.runtime === 'nodevm') {
141
142
  await runScriptInNodeVm({
@@ -151,6 +152,7 @@ class TestRuntime {
151
152
  require: {
152
153
  context: 'sandbox',
153
154
  external: true,
155
+ builtin: ['*'],
154
156
  root: [collectionPath, ...additionalContextRootsAbsolute],
155
157
  mock: {
156
158
  // node libs
@@ -11,7 +11,7 @@ const bundleLibraries = async () => {
11
11
  import moment from "moment";
12
12
  import btoa from "btoa";
13
13
  import atob from "atob";
14
- import * as CryptoJS from "@usebruno/crypto-js";
14
+ import * as cryptoJs from 'crypto-js';
15
15
  import tv4 from "tv4";
16
16
  globalThis.expect = expect;
17
17
  globalThis.assert = assert;
@@ -19,7 +19,6 @@ const bundleLibraries = async () => {
19
19
  globalThis.btoa = btoa;
20
20
  globalThis.atob = atob;
21
21
  globalThis.Buffer = Buffer;
22
- globalThis.CryptoJS = CryptoJS;
23
22
  globalThis.tv4 = tv4;
24
23
  globalThis.requireObject = {
25
24
  ...(globalThis.requireObject || {}),
@@ -28,7 +27,7 @@ const bundleLibraries = async () => {
28
27
  'buffer': { Buffer },
29
28
  'btoa': btoa,
30
29
  'atob': atob,
31
- 'crypto-js': CryptoJS,
30
+ 'crypto-js': cryptoJs,
32
31
  'tv4': tv4
33
32
  };
34
33
  `;
@@ -11,6 +11,7 @@ const { newQuickJSWASMModule, memoizePromiseFactory } = require('quickjs-emscrip
11
11
  const getBundledCode = require('../bundle-browser-rollup');
12
12
  const addPathShimToContext = require('./shims/lib/path');
13
13
  const { marshallToVm } = require('./utils');
14
+ const addCryptoUtilsShimToContext = require('./shims/lib/crypto-utils');
14
15
 
15
16
  let QuickJSSyncContext;
16
17
  const loader = memoizePromiseFactory(() => newQuickJSWASMModule());
@@ -98,6 +99,9 @@ const executeQuickJsVmAsync = async ({ script: externalScript, context: external
98
99
  const module = await newQuickJSWASMModule();
99
100
  const vm = module.newContext();
100
101
 
102
+ // add crypto utilities required by the crypto-js library in bundledCode
103
+ await addCryptoUtilsShimToContext(vm);
104
+
101
105
  const bundledCode = getBundledCode?.toString() || '';
102
106
  const moduleLoaderCode = function () {
103
107
  return `
@@ -0,0 +1,104 @@
1
+ const crypto = require('node:crypto');
2
+ const { marshallToVm } = require('../../utils');
3
+ const { serializeTypedArray, deserializeTypedArray } = require('./utils');
4
+
5
+ /**
6
+ * Node.js crypto module shim for QuickJS sandbox
7
+ * Implements crypto.randomBytes and crypto.getRandomValues functions
8
+ */
9
+ const addCryptoUtilsShimToContext = async (vm) => {
10
+ let randomBytesHandle = vm.newFunction('randomBytes', function (sizeHandle) {
11
+ try {
12
+ let size = vm.dump(sizeHandle);
13
+
14
+ if (typeof size !== 'number') {
15
+ throw new TypeError('The "size" argument must be of type number');
16
+ }
17
+
18
+ size = Math.trunc(size);
19
+
20
+ if (size < 0) {
21
+ throw new RangeError('The "size" argument must be >= 0');
22
+ }
23
+
24
+ if (size > 65536) { // 2^31 - 1 (max safe integer for practical use)
25
+ throw new RangeError('The "size" argument is too large');
26
+ }
27
+
28
+ if (size === 0) {
29
+ return marshallToVm([], vm);
30
+ }
31
+
32
+ const buffer = crypto.randomBytes(size);
33
+
34
+ const byteArray = Array.from(buffer);
35
+
36
+ return marshallToVm(byteArray, vm);
37
+
38
+ } catch (error) {
39
+ const vmError = vm.newError(error.message);
40
+ vm.setProp(vmError, 'name', vm.newString(error.name));
41
+
42
+ throw vmError;
43
+ }
44
+ });
45
+
46
+ let getRandomValuesHandle = vm.newFunction('getRandomValues', function (arrayHandle) {
47
+ try {
48
+ // Receive the serialized array data directly
49
+ const serializedArray = vm.dump(arrayHandle);
50
+ const typedArray = deserializeTypedArray(serializedArray);
51
+
52
+ if (typedArray.length === 0) {
53
+ return marshallToVm([], vm);
54
+ }
55
+
56
+ if (typedArray.length > 65536) {
57
+ throw new Error('getRandomValues: ArrayBufferView byte length exceeds 65536');
58
+ }
59
+
60
+ crypto.getRandomValues(typedArray);
61
+
62
+ const byteArray = Array.from(typedArray);
63
+
64
+ return marshallToVm(byteArray, vm);
65
+
66
+ } catch (error) {
67
+ const vmError = vm.newError(error.message);
68
+ vm.setProp(vmError, 'name', vm.newString(error.name));
69
+
70
+ throw vmError;
71
+ }
72
+ });
73
+
74
+ // Set the functions in global context
75
+ vm.setProp(vm.global, '__bruno__crypto__randomBytes', randomBytesHandle);
76
+ vm.setProp(vm.global, '__bruno__crypto__getRandomValues', getRandomValuesHandle);
77
+ randomBytesHandle.dispose();
78
+ getRandomValuesHandle.dispose();
79
+
80
+ vm.evalCode(`
81
+ // Helper function for typed array serialization
82
+ ${serializeTypedArray.toString()}
83
+
84
+ // Create crypto module object following Node.js specifications
85
+ const cryptoModule = {
86
+ // node.js crypto.randomBytes API
87
+ randomBytes: function(size) {
88
+ const byteArray = globalThis.__bruno__crypto__randomBytes(size);
89
+ return Buffer.from(Array.from(byteArray));
90
+ },
91
+ // node.js crypto.getRandomValues API
92
+ getRandomValues: function(typedArray) {
93
+ const serializedTypedArray = serializeTypedArray(typedArray);
94
+ typedArray.set(globalThis.__bruno__crypto__getRandomValues(serializedTypedArray));
95
+ return typedArray;
96
+ },
97
+ };
98
+
99
+ // Make crypto available globally
100
+ globalThis.crypto = cryptoModule;
101
+ `);
102
+ };
103
+
104
+ module.exports = addCryptoUtilsShimToContext;
@@ -0,0 +1,73 @@
1
+ const { describe, it, expect } = require('@jest/globals');
2
+ const { newQuickJSWASMModule } = require('quickjs-emscripten');
3
+ const addCryptoUtilsShimToContext = require('./crypto-utils');
4
+ const getBundledCode = require('../../../bundle-browser-rollup');
5
+
6
+ describe('crypto-utils shims tests', () => {
7
+ let vm, module;
8
+
9
+ beforeAll(async () => {
10
+ module = await newQuickJSWASMModule();
11
+ });
12
+
13
+ beforeEach(async () => {
14
+ vm = module.newContext();
15
+ await addCryptoUtilsShimToContext(vm);
16
+ // required for `Buffer` library usage
17
+ const bundledCode = getBundledCode?.toString() || '';
18
+ vm.evalCode(
19
+ `
20
+ (${bundledCode})()
21
+ `
22
+ );
23
+ });
24
+
25
+ it('should provide crypto.randomBytes function', async () => {
26
+ const result = vm.evalCode('typeof crypto.randomBytes');
27
+ const handle = vm.unwrapResult(result);
28
+ const type = vm.dump(handle);
29
+ handle.dispose();
30
+
31
+ expect(type).toBe('function');
32
+ });
33
+
34
+ it('should provide crypto.getRandomValues function', async () => {
35
+ const result = vm.evalCode('typeof crypto.getRandomValues');
36
+ const handle = vm.unwrapResult(result);
37
+ const type = vm.dump(handle);
38
+ handle.dispose();
39
+
40
+ expect(type).toBe('function');
41
+ });
42
+
43
+ it('should generate random bytes with correct length', async () => {
44
+ const result = vm.evalCode('crypto.randomBytes(8).length');
45
+ const handle = vm.unwrapResult(result);
46
+ const length = vm.dump(handle);
47
+ handle.dispose();
48
+
49
+ expect(length).toBe(8);
50
+ });
51
+
52
+ it('should convert random bytes to hex string', async () => {
53
+ const result = vm.evalCode('crypto.randomBytes(4).toString("hex").length');
54
+ const handle = vm.unwrapResult(result);
55
+ const hexLength = vm.dump(handle);
56
+ handle.dispose();
57
+
58
+ expect(hexLength).toBe(8); // 4 bytes = 8 hex chars
59
+ });
60
+
61
+ it('should fill Uint8Array with getRandomValues', async () => {
62
+ const result = vm.evalCode(`
63
+ const arr = new Uint8Array(5);
64
+ crypto.getRandomValues(arr);
65
+ arr.length;
66
+ `);
67
+ const handle = vm.unwrapResult(result);
68
+ const length = vm.dump(handle);
69
+ handle.dispose();
70
+
71
+ expect(length).toBe(5);
72
+ });
73
+ });
@@ -0,0 +1,48 @@
1
+ function serializeTypedArray(ta) {
2
+ return {
3
+ type: ta.constructor.name,
4
+ array: Array.from(ta),
5
+ length: ta.length
6
+ };
7
+ }
8
+
9
+ function deserializeTypedArray(obj) {
10
+ // Allowed typed array constructors for crypto operations
11
+ const allowedConstructors = new Set([
12
+ 'Int8Array',
13
+ 'Uint8Array',
14
+ 'Uint8ClampedArray',
15
+ 'Int16Array',
16
+ 'Uint16Array',
17
+ 'Int32Array',
18
+ 'Uint32Array',
19
+ 'Float32Array',
20
+ 'Float64Array',
21
+ 'BigInt64Array',
22
+ 'BigUint64Array'
23
+ ]);
24
+
25
+ if (!obj || typeof obj !== 'object') {
26
+ throw new TypeError('getRandomValues: Invalid typed array object');
27
+ }
28
+
29
+ if (typeof obj.type !== 'string' || !allowedConstructors.has(obj.type)) {
30
+ throw new TypeError(`getRandomValues: Invalid or unsupported typed array type: ${obj.type}`);
31
+ }
32
+
33
+ if (!obj.array || typeof obj.length !== 'number') {
34
+ throw new TypeError('getRandomValues: Invalid typed array properties');
35
+ }
36
+
37
+ const ctor = globalThis[obj.type];
38
+ if (typeof ctor !== 'function') {
39
+ throw new TypeError(`getRandomValues: Constructor ${obj.type} is not available`);
40
+ }
41
+
42
+ return new ctor(obj.array, 0, obj.length);
43
+ }
44
+
45
+ module.exports = {
46
+ serializeTypedArray,
47
+ deserializeTypedArray
48
+ }