@usebruno/js 0.38.0 → 0.40.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/bruno-request.js +9 -0
- package/src/runtime/test-runtime.js +3 -1
- package/src/sandbox/bundle-browser-rollup.js +11 -11
- package/src/sandbox/bundle-libraries.js +2 -3
- package/src/sandbox/quickjs/index.js +4 -0
- package/src/sandbox/quickjs/shims/bruno-request.js +9 -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
|
@@ -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 `
|
|
@@ -9,6 +9,7 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
9
9
|
const body = marshallToVm(req.getBody(), vm);
|
|
10
10
|
const timeout = marshallToVm(req.getTimeout(), vm);
|
|
11
11
|
const name = marshallToVm(req.getName(), vm);
|
|
12
|
+
const tags = marshallToVm(req.getTags(), vm);
|
|
12
13
|
|
|
13
14
|
vm.setProp(reqObject, 'url', url);
|
|
14
15
|
vm.setProp(reqObject, 'method', method);
|
|
@@ -16,6 +17,7 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
16
17
|
vm.setProp(reqObject, 'body', body);
|
|
17
18
|
vm.setProp(reqObject, 'timeout', timeout);
|
|
18
19
|
vm.setProp(reqObject, 'name', name);
|
|
20
|
+
vm.setProp(reqObject, 'tags', tags);
|
|
19
21
|
|
|
20
22
|
url.dispose();
|
|
21
23
|
method.dispose();
|
|
@@ -23,6 +25,7 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
23
25
|
body.dispose();
|
|
24
26
|
timeout.dispose();
|
|
25
27
|
name.dispose();
|
|
28
|
+
tags.dispose();
|
|
26
29
|
|
|
27
30
|
let getUrl = vm.newFunction('getUrl', function () {
|
|
28
31
|
return marshallToVm(req.getUrl(), vm);
|
|
@@ -126,6 +129,12 @@ const addBrunoRequestShimToContext = (vm, req) => {
|
|
|
126
129
|
vm.setProp(reqObject, 'getExecutionMode', getExecutionMode);
|
|
127
130
|
getExecutionMode.dispose();
|
|
128
131
|
|
|
132
|
+
let getTags = vm.newFunction('getTags', function () {
|
|
133
|
+
return marshallToVm(req.getTags(), vm);
|
|
134
|
+
});
|
|
135
|
+
vm.setProp(reqObject, 'getTags', getTags);
|
|
136
|
+
getTags.dispose();
|
|
137
|
+
|
|
129
138
|
vm.setProp(vm.global, 'req', reqObject);
|
|
130
139
|
reqObject.dispose();
|
|
131
140
|
};
|
|
@@ -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
|
+
}
|