/
kernel.js
232 lines (218 loc) · 7.43 KB
/
kernel.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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
223
224
225
226
227
228
229
230
231
232
/* eslint no-eval: 0 */
const fs = require('node:fs')
const path = require('node:path')
const resolve = require('resolve')
const { sanitize } = require('htmlescape')
const {
generateKernel,
applySourceTransforms,
makeInitStatsHook,
} = require('lavamoat-core')
const { getPackageNameForModulePath } = require('@lavamoat/aa')
const { checkForResolutionOverride } = require('./resolutions')
const { resolutionOmittedExtensions } = require('./parseForPolicy')
const { createFreshRealmCompartment } = require('./freshRealmCompartment')
const noop = () => {}
const nativeRequire = require
module.exports = { createKernel }
function createKernel({
projectRoot,
lavamoatPolicy,
canonicalNameMap,
debugMode,
statsMode,
scuttleGlobalThis,
scuttleGlobalThisExceptions,
}) {
if (scuttleGlobalThisExceptions) {
console.warn(
'Lavamoat - "scuttleGlobalThisExceptions" is deprecated. Use "scuttleGlobalThis.exceptions" instead.'
)
if (scuttleGlobalThis === true) {
scuttleGlobalThis = { enabled: true }
}
scuttleGlobalThis = Object.assign({}, scuttleGlobalThis)
scuttleGlobalThis.exceptions =
scuttleGlobalThis?.exceptions || scuttleGlobalThisExceptions
}
const { resolutions } = lavamoatPolicy
const getRelativeModuleId = createModuleResolver({
projectRoot,
resolutions,
canonicalNameMap,
})
const loadModuleData = createModuleLoader({ canonicalNameMap })
const kernelSrc = generateKernel({ debugMode, scuttleGlobalThis })
const createKernel = evaluateWithSourceUrl('LavaMoat/node/kernel', kernelSrc)
const reportStatsHook = statsMode ? makeInitStatsHook({ onStatsReady }) : noop
const kernel = createKernel({
lavamoatConfig: lavamoatPolicy,
loadModuleData,
getRelativeModuleId,
prepareModuleInitializerArgs,
getExternalCompartment,
globalThisRefs: ['global', 'globalThis'],
reportStatsHook,
})
return kernel
}
function getExternalCompartment(packageName, packagePolicy) {
const envPolicy = packagePolicy.env
if (packagePolicy.env === 'unfrozen') {
return createFreshRealmCompartment()
}
throw new Error(
`LavaMoat/node - unrecognized "env" policy setting for package "${packageName}", "${envPolicy}"`
)
}
function prepareModuleInitializerArgs(
requireRelativeWithContext,
moduleObj,
moduleData
) {
const require = requireRelativeWithContext
const module = moduleObj
const exports = moduleObj.exports
const __filename = moduleData.file
const __dirname = path.dirname(__filename)
require.resolve = (requestedName) => {
return resolve.sync(requestedName, { basedir: __dirname })
}
return [exports, require, module, __filename, __dirname]
}
function createModuleResolver({ projectRoot, resolutions, canonicalNameMap }) {
return function getRelativeModuleId(parentAbsolutePath, requestedName) {
// handle resolution overrides
let parentDir = path.dirname(parentAbsolutePath)
const parentPackageName = getPackageNameForModulePath(
canonicalNameMap,
parentDir
)
const result = checkForResolutionOverride(
resolutions,
parentPackageName,
requestedName
)
if (result) {
requestedName = result
// if path is a relative path, it should be relative to the projectRoot
if (!path.isAbsolute(result)) {
parentDir = projectRoot
}
}
// resolve normally
const resolved = resolve.sync(requestedName, {
basedir: parentDir,
extensions: resolutionOmittedExtensions,
})
return resolved
}
}
function createModuleLoader({ canonicalNameMap }) {
return function loadModuleData(absolutePath) {
// load builtin modules (eg "fs")
if (resolve.isCore(absolutePath)) {
return {
type: 'builtin',
file: absolutePath,
package: absolutePath,
id: absolutePath,
// wrapper around unprotected "require"
moduleInitializer: (exports, require, module) => {
module.exports = nativeRequire(absolutePath)
},
}
// load compiled native module
} else if (isNativeModule(absolutePath)) {
const packageName = getPackageNameForModulePath(
canonicalNameMap,
absolutePath
)
return {
type: 'native',
file: absolutePath,
package: packageName,
id: absolutePath,
// wrapper around unprotected "require"
moduleInitializer: (exports, require, module) => {
module.exports = nativeRequire(absolutePath)
},
}
// load normal user-space module
} else {
const moduleContent = fs.readFileSync(absolutePath, 'utf8')
// apply source transforms
let transformedContent = moduleContent
// hash bang
const contentLines = transformedContent.split('\n')
if (contentLines[0].startsWith('#!')) {
transformedContent = contentLines.slice(1).join('\n')
}
transformedContent = applySourceTransforms(transformedContent)
const isJSON = /\.json$/.test(absolutePath)
// wrap json modules (borrowed from browserify)
if (isJSON) {
const sanitizedString = sanitize(transformedContent)
try {
// check json validity
JSON.parse(sanitizedString)
transformedContent = 'module.exports=' + sanitizedString
} catch (err) {
err.message = `While parsing ${absolutePath}: ${err.message}`
throw err
}
}
// ses needs to take a fucking chill pill
if (isJSON) {
transformedContent = transformedContent
.split('-import-')
.join('-imp ort-')
} else {
transformedContent = transformedContent
.split('"babel-plugin-dynamic-import-node')
.join('"babel-plugin-dynamic-imp" + "ort-node')
.split('"@babel/plugin-proposal-dynamic-import')
.join('"@babel/plugin-proposal-dynamic-imp" + "ort')
.split('// Re-export lib/utils, so that consumers can import')
.join('// Re-export lib/utils, so that consumers can imp_ort')
.split('// babel-plugin-dynamic-import')
.join('// babel-plugin-dynamic-imp ort')
.split('// eslint-disable-next-line import/no-unresolved')
.join('// eslint-disable-next-line imp_ort/no-unresolved')
}
// wrap in moducreateModuleResolverleInitializer
// security: ensure module path does not inject code
if (absolutePath.includes('\n')) {
throw new Error('invalid newline in module source path')
}
const wrappedContent = `(function(exports, require, module, __filename, __dirname){\n${transformedContent}\n})`
const packageName = getPackageNameForModulePath(
canonicalNameMap,
absolutePath
)
return {
type: 'js',
file: absolutePath,
package: packageName,
source: wrappedContent,
id: absolutePath,
}
}
}
}
function isNativeModule(filename) {
const fileExtension = filename.split('.').pop()
return fileExtension === 'node'
}
function evaluateWithSourceUrl(filename, content) {
return eval(`${content}\n//# sourceURL=${filename}`)
}
function onStatsReady(moduleGraphStatsObj) {
const graphId = Date.now()
console.warn(
`completed module graph init "${graphId}" in ${moduleGraphStatsObj.value}ms ("${moduleGraphStatsObj.name}")`
)
const statsFilePath = `./lavamoat-flame-${graphId}.json`
console.warn(`wrote stats file to "${statsFilePath}"`)
fs.writeFileSync(statsFilePath, JSON.stringify(moduleGraphStatsObj, null, 2))
}