@jsreport/jsreport-core 3.11.4 → 3.12.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/README.md +6 -0
- package/lib/main/optionsSchema.js +1 -0
- package/lib/main/reporter.js +4 -0
- package/lib/worker/sandbox/createSandbox.js +138 -641
- package/lib/worker/sandbox/isolatedRequire.js +462 -0
- package/lib/worker/sandbox/propertiesSandbox.js +521 -0
- package/lib/worker/sandbox/requireSandbox.js +117 -0
- package/lib/worker/sandbox/runInSandbox.js +257 -255
- package/package.json +4 -3
- package/test/extensions/validExtensions/listeners/worker.js +6 -0
|
@@ -1,255 +1,257 @@
|
|
|
1
|
-
const LRU = require('lru-cache')
|
|
2
|
-
const stackTrace = require('stack-trace')
|
|
3
|
-
const { customAlphabet } = require('nanoid')
|
|
4
|
-
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
|
|
5
|
-
const createSandbox = require('./createSandbox')
|
|
6
|
-
const normalizeError = require('../../shared/normalizeError')
|
|
7
|
-
|
|
8
|
-
module.exports = (reporter)
|
|
9
|
-
const functionsCache = LRU(reporter.options.sandbox.cache)
|
|
10
|
-
|
|
11
|
-
return async ({
|
|
12
|
-
manager = {},
|
|
13
|
-
context,
|
|
14
|
-
userCode,
|
|
15
|
-
initFn,
|
|
16
|
-
executionFn,
|
|
17
|
-
currentPath,
|
|
18
|
-
onRequire,
|
|
19
|
-
propertiesConfig,
|
|
20
|
-
errorLineNumberOffset = 0
|
|
21
|
-
}, req)
|
|
22
|
-
let jsreportProxy = null
|
|
23
|
-
|
|
24
|
-
// we use dynamic name because of the potential nested vm2 execution in the jsreportProxy.assets.require
|
|
25
|
-
// it may turn out it is a bad approach in assets so we gonna delete it here
|
|
26
|
-
const executionFnName = `${nanoid()}_executionFn`
|
|
27
|
-
|
|
28
|
-
context[executionFnName] = executionFn
|
|
29
|
-
context.__appDirectory = reporter.options.appDirectory
|
|
30
|
-
context.__rootDirectory = reporter.options.rootDirectory
|
|
31
|
-
context.__parentModuleDirectory = reporter.options.parentModuleDirectory
|
|
32
|
-
context.setTimeout = setTimeout
|
|
33
|
-
context.__topLevelFunctions = {}
|
|
34
|
-
context.__handleError = (err) => handleError(reporter, err)
|
|
35
|
-
|
|
36
|
-
const { sourceFilesInfo, run, compileScript, restore, sandbox, sandboxRequire } = createSandbox(context, {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
reporter.options.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
//
|
|
141
|
-
// be
|
|
142
|
-
manager
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
'
|
|
232
|
-
'
|
|
233
|
-
'
|
|
234
|
-
'
|
|
235
|
-
'
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
traverse
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
1
|
+
const LRU = require('lru-cache')
|
|
2
|
+
const stackTrace = require('stack-trace')
|
|
3
|
+
const { customAlphabet } = require('nanoid')
|
|
4
|
+
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
|
|
5
|
+
const createSandbox = require('./createSandbox')
|
|
6
|
+
const normalizeError = require('../../shared/normalizeError')
|
|
7
|
+
|
|
8
|
+
module.exports = function createRunInSandbox (reporter) {
|
|
9
|
+
const functionsCache = LRU(reporter.options.sandbox.cache)
|
|
10
|
+
|
|
11
|
+
return async function runInSandbox ({
|
|
12
|
+
manager = {},
|
|
13
|
+
context,
|
|
14
|
+
userCode,
|
|
15
|
+
initFn,
|
|
16
|
+
executionFn,
|
|
17
|
+
currentPath,
|
|
18
|
+
onRequire,
|
|
19
|
+
propertiesConfig,
|
|
20
|
+
errorLineNumberOffset = 0
|
|
21
|
+
}, req) {
|
|
22
|
+
let jsreportProxy = null
|
|
23
|
+
|
|
24
|
+
// we use dynamic name because of the potential nested vm2 execution in the jsreportProxy.assets.require
|
|
25
|
+
// it may turn out it is a bad approach in assets so we gonna delete it here
|
|
26
|
+
const executionFnName = `${nanoid()}_executionFn`
|
|
27
|
+
|
|
28
|
+
context[executionFnName] = executionFn
|
|
29
|
+
context.__appDirectory = reporter.options.appDirectory
|
|
30
|
+
context.__rootDirectory = reporter.options.rootDirectory
|
|
31
|
+
context.__parentModuleDirectory = reporter.options.parentModuleDirectory
|
|
32
|
+
context.setTimeout = setTimeout
|
|
33
|
+
context.__topLevelFunctions = {}
|
|
34
|
+
context.__handleError = (err) => handleError(reporter, err)
|
|
35
|
+
|
|
36
|
+
const { sourceFilesInfo, run, compileScript, restore, sandbox, sandboxRequire } = createSandbox(context, {
|
|
37
|
+
rootDirectory: reporter.options.rootDirectory,
|
|
38
|
+
onLog: (log) => {
|
|
39
|
+
// we mark any log done in sandbox as userLevel: true, this allows us to detect which logs belongs to user
|
|
40
|
+
// and can potentially contain sensitive information
|
|
41
|
+
|
|
42
|
+
let consoleType = log.level
|
|
43
|
+
|
|
44
|
+
if (consoleType === 'debug') {
|
|
45
|
+
consoleType = 'log'
|
|
46
|
+
} else if (consoleType === 'warn') {
|
|
47
|
+
consoleType = 'warning'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
reporter.logger.debug(`(console:${consoleType}) ${log.message}`, { ...req, timestamp: log.timestamp, userLevel: true })
|
|
51
|
+
},
|
|
52
|
+
formatError: (error, moduleName) => {
|
|
53
|
+
error.message += ` To be able to require custom modules you need to add to configuration { "trustUserCode": true } or enable just specific module using { sandbox: { allowedModules": ["${moduleName}"] }`
|
|
54
|
+
},
|
|
55
|
+
safeExecution: reporter.options.trustUserCode === false,
|
|
56
|
+
isolateModules: reporter.options.sandbox.isolateModules !== false,
|
|
57
|
+
modulesCache: reporter.requestModulesCache.get(req.context.rootId),
|
|
58
|
+
globalModules: reporter.options.sandbox.nativeModules || [],
|
|
59
|
+
allowedModules: reporter.options.sandbox.allowedModules,
|
|
60
|
+
propertiesConfig,
|
|
61
|
+
requirePaths: [
|
|
62
|
+
reporter.options.rootDirectory,
|
|
63
|
+
reporter.options.appDirectory,
|
|
64
|
+
reporter.options.parentModuleDirectory
|
|
65
|
+
],
|
|
66
|
+
requireMap: (moduleName) => {
|
|
67
|
+
const m = reporter.options.sandbox.modules.find((m) => m.alias === moduleName || m.path === moduleName)
|
|
68
|
+
|
|
69
|
+
if (m) {
|
|
70
|
+
return require(m.path)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (moduleName === 'jsreport-proxy') {
|
|
74
|
+
return jsreportProxy
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (onRequire) {
|
|
78
|
+
return onRequire(moduleName, { context })
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const _getTopLevelFunctions = function _getTopLevelFunctions (code) {
|
|
84
|
+
return getTopLevelFunctions(functionsCache, code)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
jsreportProxy = reporter.createProxy({
|
|
88
|
+
req,
|
|
89
|
+
runInSandbox: run,
|
|
90
|
+
context: sandbox,
|
|
91
|
+
getTopLevelFunctions: _getTopLevelFunctions,
|
|
92
|
+
sandboxRequire
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
jsreportProxy.currentPath = async function getCurrentPath () {
|
|
96
|
+
// we get the current path by throwing an error, which give us a stack trace
|
|
97
|
+
// which we analyze and see if some source file is associated to an entity
|
|
98
|
+
// if it is then we can properly get the path associated to it, if not we
|
|
99
|
+
// fallback to the current path passed as options
|
|
100
|
+
const filesCount = sourceFilesInfo.size
|
|
101
|
+
let resolvedPath = currentPath
|
|
102
|
+
|
|
103
|
+
if (filesCount > 0) {
|
|
104
|
+
const err = new Error('get me stack trace please')
|
|
105
|
+
const trace = stackTrace.parse(err)
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < trace.length; i++) {
|
|
108
|
+
const current = trace[i]
|
|
109
|
+
|
|
110
|
+
if (sourceFilesInfo.has(current.getFileName())) {
|
|
111
|
+
const { entity, entitySet } = sourceFilesInfo.get(current.getFileName())
|
|
112
|
+
|
|
113
|
+
if (entity != null && entitySet != null) {
|
|
114
|
+
resolvedPath = await reporter.folders.resolveEntityPath(entity, entitySet, req)
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return resolvedPath
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
jsreportProxy.currentDirectoryPath = async function getCurrentDirectoryPath () {
|
|
125
|
+
const currentPath = await jsreportProxy.currentPath()
|
|
126
|
+
|
|
127
|
+
if (currentPath != null) {
|
|
128
|
+
const localPath = currentPath.substring(0, currentPath.lastIndexOf('/'))
|
|
129
|
+
|
|
130
|
+
if (localPath === '') {
|
|
131
|
+
return '/'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return localPath
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return currentPath
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// NOTE: it is important that cleanup, restore methods are not called from a function attached to the
|
|
141
|
+
// sandbox, because the arguments and return value of such function call will be sandboxed again, to solve this
|
|
142
|
+
// we don't attach these methods to the sandbox, and instead share them through a "manager" object that should
|
|
143
|
+
// be passed in options
|
|
144
|
+
manager.restore = restore
|
|
145
|
+
|
|
146
|
+
if (typeof initFn === 'function') {
|
|
147
|
+
const initScriptInfo = await initFn(_getTopLevelFunctions, compileScript)
|
|
148
|
+
|
|
149
|
+
if (initScriptInfo) {
|
|
150
|
+
try {
|
|
151
|
+
await run(initScriptInfo.script, {
|
|
152
|
+
filename: initScriptInfo.filename || 'sandbox-init.js',
|
|
153
|
+
source: initScriptInfo.source
|
|
154
|
+
})
|
|
155
|
+
} catch (e) {
|
|
156
|
+
handleError(reporter, e)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const functionNames = getTopLevelFunctions(functionsCache, userCode)
|
|
162
|
+
const functionsCode = `return {${functionNames.map(h => `"${h}": ${h}`).join(',')}}`
|
|
163
|
+
const executionCode = `;(async () => { ${userCode} \n\n;${functionsCode} })()
|
|
164
|
+
.then((topLevelFunctions) => {
|
|
165
|
+
const mergedTopLevelFunctions = { ...topLevelFunctions, ...__topLevelFunctions }
|
|
166
|
+
|
|
167
|
+
// expose top level functions to the sandbox context
|
|
168
|
+
// so helpers can call other helpers (from shared asset helpers, or .registerHelpers call from proxy)
|
|
169
|
+
for (const [topLevelFnName, topLevelFn] of Object.entries(mergedTopLevelFunctions)) {
|
|
170
|
+
this[topLevelFnName] = topLevelFn
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return ${executionFnName}({
|
|
174
|
+
topLevelFunctions: mergedTopLevelFunctions,
|
|
175
|
+
require,
|
|
176
|
+
console,
|
|
177
|
+
context: this
|
|
178
|
+
})
|
|
179
|
+
}).catch(__handleError);`
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
return await run(executionCode, {
|
|
183
|
+
filename: 'sandbox.js',
|
|
184
|
+
source: userCode,
|
|
185
|
+
errorLineNumberOffset
|
|
186
|
+
})
|
|
187
|
+
} catch (e) {
|
|
188
|
+
handleError(reporter, e)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function handleError (reporter, errValue) {
|
|
194
|
+
let newError = normalizeError(errValue)
|
|
195
|
+
|
|
196
|
+
if (newError === errValue) {
|
|
197
|
+
// here it means the original error was valid in the first place,
|
|
198
|
+
// so it was not normalized
|
|
199
|
+
newError = new Error(errValue.message)
|
|
200
|
+
Object.assign(newError, errValue)
|
|
201
|
+
|
|
202
|
+
if (errValue.stack) {
|
|
203
|
+
newError.stack = errValue.stack
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
throw reporter.createError(null, {
|
|
208
|
+
original: newError,
|
|
209
|
+
statusCode: 400
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getTopLevelFunctions (cache, code) {
|
|
214
|
+
const key = `functions:${code}`
|
|
215
|
+
|
|
216
|
+
if (cache.has(key)) {
|
|
217
|
+
return cache.get(key)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// lazy load to speed up boot
|
|
221
|
+
const parser = require('@babel/parser')
|
|
222
|
+
const traverse = require('@babel/traverse').default
|
|
223
|
+
|
|
224
|
+
const names = []
|
|
225
|
+
try {
|
|
226
|
+
const ast = parser.parse(code, {
|
|
227
|
+
sourceType: 'script',
|
|
228
|
+
allowReturnOutsideFunction: false,
|
|
229
|
+
allowAwaitOutsideFunction: true,
|
|
230
|
+
plugins: [
|
|
231
|
+
'classProperties',
|
|
232
|
+
'classPrivateProperties',
|
|
233
|
+
'classPrivateMethods',
|
|
234
|
+
'doExpressions',
|
|
235
|
+
'functionBind',
|
|
236
|
+
'throwExpressions',
|
|
237
|
+
'topLevelAwait'
|
|
238
|
+
]
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// traverse only function declaration that are defined
|
|
242
|
+
// at the top level of program
|
|
243
|
+
traverse(ast, {
|
|
244
|
+
FunctionDeclaration: (path) => {
|
|
245
|
+
if (path.parent.type === 'Program') {
|
|
246
|
+
names.push(path.node.id.name)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
} catch (e) {
|
|
251
|
+
// we let the error handling for later eval
|
|
252
|
+
return []
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
cache.set(key, names)
|
|
256
|
+
return names
|
|
257
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsreport/jsreport-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "javascript based business reporting",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"report",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@babel/parser": "7.14.4",
|
|
41
41
|
"@babel/traverse": "7.12.9",
|
|
42
42
|
"@colors/colors": "1.5.0",
|
|
43
|
-
"@jsreport/advanced-workers": "1.
|
|
43
|
+
"@jsreport/advanced-workers": "1.3.0",
|
|
44
44
|
"@jsreport/mingo": "2.4.1",
|
|
45
45
|
"@jsreport/reap": "0.1.0",
|
|
46
46
|
"ajv": "6.12.6",
|
|
@@ -71,12 +71,13 @@
|
|
|
71
71
|
"triple-beam": "1.3.0",
|
|
72
72
|
"unset-value": "2.0.1",
|
|
73
73
|
"uuid": "8.3.2",
|
|
74
|
-
"vm2": "3.9.
|
|
74
|
+
"vm2": "3.9.19",
|
|
75
75
|
"winston": "3.8.1",
|
|
76
76
|
"winston-transport": "4.5.0",
|
|
77
77
|
"yieldable-json": "2.0.1"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
+
"@node-rs/jsonwebtoken": "0.2.0",
|
|
80
81
|
"mocha": "9.2.2",
|
|
81
82
|
"should": "13.2.3",
|
|
82
83
|
"standard": "16.0.4",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const extend = require('node.extend.without.arrays')
|
|
2
2
|
const vm = require('vm')
|
|
3
|
+
const Module = require('module')
|
|
3
4
|
const path = require('path')
|
|
4
5
|
const process = require('process')
|
|
5
6
|
|
|
@@ -39,7 +40,12 @@ module.exports = (reporter, definition) => {
|
|
|
39
40
|
return script.runInThisContext({
|
|
40
41
|
displayErrors: true
|
|
41
42
|
})(req, res, {
|
|
43
|
+
mainModuleFilename: require.main.filename,
|
|
42
44
|
require: (m) => {
|
|
45
|
+
if (Module.builtinModules.includes(m)) {
|
|
46
|
+
return require(m)
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
try {
|
|
44
50
|
return require(path.join(process.cwd(), 'node_modules', m))
|
|
45
51
|
} catch (e) {
|