-
Notifications
You must be signed in to change notification settings - Fork 41
/
DepShield.js
148 lines (134 loc) Β· 4.61 KB
/
DepShield.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
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable no-plusplus */
/* eslint-disable @typescript-eslint/no-this-alias */
const path = require('path');
const extend = require('util')._extend;
const BASE_ERROR = 'Unauthorized dependency detected:\r\n';
const PluginTitle = 'DepShieldPlugin';
const reportedErrors = [];
class DepShieldPlugin {
constructor(options) {
this.options = extend(
{
failOnError: false,
onDetected: false,
cwd: process.cwd(),
},
options,
);
}
apply(compiler) {
const plugin = this;
compiler.hooks.compilation.tap(PluginTitle, (compilation) => {
compilation.hooks.optimizeModules.tap(PluginTitle, (modules) => {
if (plugin.options.onStart) {
plugin.options.onStart({ compilation });
}
for (const module of modules) {
const maybeImportingFromRoot = this.isImportingFromRoot(
module,
module,
{},
compilation,
);
if (maybeImportingFromRoot) {
// This is the target of the attack
const depPath = [maybeImportingFromRoot.slice(-1)[0]];
// This is the whole path from our files to the depenedency attempting to import
for (let i = maybeImportingFromRoot.length - 2; i >= 0; i--) {
const path = maybeImportingFromRoot[i];
depPath.push(path);
// Exit after finding the first non-node_modules path
if (!path.startsWith('node_modules')) {
break;
}
}
// mark warnings or errors on webpack compilation
const errorMsg = BASE_ERROR.concat(depPath.reverse().join(' -> '));
if (reportedErrors.indexOf(errorMsg) === -1) {
if (plugin.options.failOnError) {
compilation.errors.push(new Error(errorMsg));
} else {
compilation.warnings.push(new Error(errorMsg));
}
reportedErrors.push(errorMsg);
}
}
}
if (plugin.options.onEnd) {
plugin.options.onEnd({ compilation });
}
});
});
}
isImportingFromRoot(initialModule, currentModule, seenModules, compilation) {
const cwd = this.options.cwd;
// Add the current module to the seen modules cache
seenModules[currentModule.debugId] = true;
// If the modules aren't associated to resources
// it's not possible to display how they are cyclical
if (!currentModule.resource || !initialModule.resource) {
return false;
}
// Iterate over the current modules dependencies
for (const dependency of currentModule.dependencies) {
if (
dependency.constructor &&
dependency.constructor.name === 'CommonJsSelfReferenceDependency'
) {
continue;
}
let depModule = null;
if (compilation.moduleGraph) {
// handle getting a module for webpack 5
depModule = compilation.moduleGraph.getModule(dependency);
} else {
// handle getting a module for webpack 4
depModule = dependency.module;
}
if (!depModule) {
continue;
}
// ignore dependencies that don't have an associated resource
if (!depModule.resource) {
continue;
}
// the dependency was resolved to the current module due to how webpack internals
// setup dependencies like CommonJsSelfReferenceDependency and ModuleDecoratorDependency
if (currentModule === depModule) {
continue;
}
if (depModule.debugId in seenModules) {
// THIS IS EXACTLY WHAT WE DON'T WANT TO ALLOW:
// a module from `node_modules`
// requiring a module that is not from `node_modules` (i.e. a local file)
const relCurrentModule = path.relative(cwd, currentModule.resource);
const relDepModule = path.relative(cwd, depModule.resource);
if (
relCurrentModule.startsWith('node_modules/') &&
!relDepModule.startsWith('node_modules/')
) {
return [
path.relative(cwd, relCurrentModule),
path.relative(cwd, relDepModule),
];
}
continue;
}
const maybeImportingFromRoot = this.isImportingFromRoot(
initialModule,
depModule,
seenModules,
compilation,
);
if (maybeImportingFromRoot) {
maybeImportingFromRoot.unshift(
path.relative(cwd, currentModule.resource),
);
return maybeImportingFromRoot;
}
}
return false;
}
}
module.exports = DepShieldPlugin;