@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 +3 -4
- package/src/runtime/test-runtime.js +3 -1
- package/src/sandbox/bundle-libraries.js +2 -3
- package/src/sandbox/quickjs/index.js +4 -0
- package/src/sandbox/quickjs/shims/lib/crypto-utils.js +104 -0
- package/src/sandbox/quickjs/shims/lib/crypto-utils.spec.js +73 -0
- package/src/sandbox/quickjs/shims/lib/utils.js +48 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usebruno/js",
|
|
3
|
-
"version": "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.
|
|
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
|
|
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':
|
|
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
|
+
}
|