devcompass 2.5.0 → 2.7.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 +400 -93
- package/data/known-malicious.json +57 -0
- package/package.json +13 -3
- package/src/alerts/github-tracker.js +53 -19
- package/src/alerts/predictive.js +10 -4
- package/src/analyzers/license-risk.js +225 -0
- package/src/analyzers/package-quality.js +368 -0
- package/src/analyzers/security-recommendations.js +274 -0
- package/src/analyzers/supply-chain.js +217 -0
- package/src/commands/analyze.js +466 -17
- package/src/utils/json-formatter.js +118 -28
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// src/analyzers/supply-chain.js
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Load known malicious packages database
|
|
7
|
+
*/
|
|
8
|
+
function loadMaliciousDatabase() {
|
|
9
|
+
try {
|
|
10
|
+
const dbPath = path.join(__dirname, '../../data/known-malicious.json');
|
|
11
|
+
const data = fs.readFileSync(dbPath, 'utf8');
|
|
12
|
+
return JSON.parse(data);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('Error loading malicious packages database:', error.message);
|
|
15
|
+
return {
|
|
16
|
+
malicious_packages: [],
|
|
17
|
+
typosquat_patterns: {},
|
|
18
|
+
suspicious_patterns: { install_scripts: [], suspicious_dependencies: [] }
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Calculate Levenshtein distance between two strings
|
|
25
|
+
*/
|
|
26
|
+
function levenshteinDistance(str1, str2) {
|
|
27
|
+
const matrix = [];
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i <= str2.length; i++) {
|
|
30
|
+
matrix[i] = [i];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (let j = 0; j <= str1.length; j++) {
|
|
34
|
+
matrix[0][j] = j;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let i = 1; i <= str2.length; i++) {
|
|
38
|
+
for (let j = 1; j <= str1.length; j++) {
|
|
39
|
+
if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
|
|
40
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
41
|
+
} else {
|
|
42
|
+
matrix[i][j] = Math.min(
|
|
43
|
+
matrix[i - 1][j - 1] + 1,
|
|
44
|
+
matrix[i][j - 1] + 1,
|
|
45
|
+
matrix[i - 1][j] + 1
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return matrix[str2.length][str1.length];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Detect typosquatting attempts
|
|
56
|
+
*/
|
|
57
|
+
function detectTyposquatting(packageName, database) {
|
|
58
|
+
const warnings = [];
|
|
59
|
+
|
|
60
|
+
// Check against known typosquat patterns
|
|
61
|
+
for (const [official, typos] of Object.entries(database.typosquat_patterns)) {
|
|
62
|
+
if (typos.includes(packageName.toLowerCase())) {
|
|
63
|
+
warnings.push({
|
|
64
|
+
package: packageName,
|
|
65
|
+
type: 'typosquatting',
|
|
66
|
+
severity: 'high',
|
|
67
|
+
official: official,
|
|
68
|
+
message: `Potential typosquatting: "${packageName}" is similar to official package "${official}"`,
|
|
69
|
+
recommendation: `Remove ${packageName} and install ${official} instead`
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check Levenshtein distance against popular packages
|
|
75
|
+
const popularPackages = Object.keys(database.typosquat_patterns);
|
|
76
|
+
for (const popular of popularPackages) {
|
|
77
|
+
const distance = levenshteinDistance(packageName.toLowerCase(), popular.toLowerCase());
|
|
78
|
+
|
|
79
|
+
// If distance is 1-2 characters and not already flagged
|
|
80
|
+
if (distance > 0 && distance <= 2 && packageName !== popular) {
|
|
81
|
+
const alreadyFlagged = warnings.some(w => w.package === packageName);
|
|
82
|
+
|
|
83
|
+
if (!alreadyFlagged) {
|
|
84
|
+
warnings.push({
|
|
85
|
+
package: packageName,
|
|
86
|
+
type: 'typosquatting_suspected',
|
|
87
|
+
severity: 'medium',
|
|
88
|
+
official: popular,
|
|
89
|
+
message: `Possible typosquatting: "${packageName}" is very similar to "${popular}"`,
|
|
90
|
+
recommendation: `Verify if you meant to install ${popular}`
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return warnings;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if package is known malicious
|
|
101
|
+
*/
|
|
102
|
+
function checkMaliciousPackage(packageName, database) {
|
|
103
|
+
if (database.malicious_packages.includes(packageName.toLowerCase())) {
|
|
104
|
+
return {
|
|
105
|
+
package: packageName,
|
|
106
|
+
type: 'malicious',
|
|
107
|
+
severity: 'critical',
|
|
108
|
+
message: `Known malicious package detected: ${packageName}`,
|
|
109
|
+
recommendation: 'Remove immediately - this package is known to be malicious'
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Analyze install scripts for suspicious patterns
|
|
117
|
+
*/
|
|
118
|
+
function analyzeInstallScripts(projectPath, dependencies) {
|
|
119
|
+
const warnings = [];
|
|
120
|
+
const database = loadMaliciousDatabase();
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
for (const dep of Object.keys(dependencies)) {
|
|
124
|
+
const packageJsonPath = path.join(projectPath, 'node_modules', dep, 'package.json');
|
|
125
|
+
|
|
126
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
131
|
+
const scripts = packageJson.scripts || {};
|
|
132
|
+
|
|
133
|
+
// Check for install hooks
|
|
134
|
+
const installHooks = ['preinstall', 'install', 'postinstall'];
|
|
135
|
+
|
|
136
|
+
for (const hook of installHooks) {
|
|
137
|
+
if (scripts[hook]) {
|
|
138
|
+
const script = scripts[hook].toLowerCase();
|
|
139
|
+
|
|
140
|
+
// Check for suspicious patterns
|
|
141
|
+
const suspiciousPatterns = database.suspicious_patterns.install_scripts;
|
|
142
|
+
const foundPatterns = suspiciousPatterns.filter(pattern =>
|
|
143
|
+
script.includes(pattern.toLowerCase())
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (foundPatterns.length > 0) {
|
|
147
|
+
warnings.push({
|
|
148
|
+
package: `${dep}@${packageJson.version}`,
|
|
149
|
+
type: 'install_script',
|
|
150
|
+
severity: foundPatterns.some(p => ['curl', 'wget', 'eval', 'exec'].includes(p)) ? 'high' : 'medium',
|
|
151
|
+
script: hook,
|
|
152
|
+
action: scripts[hook],
|
|
153
|
+
patterns: foundPatterns,
|
|
154
|
+
message: `Install script contains suspicious patterns: ${foundPatterns.join(', ')}`,
|
|
155
|
+
recommendation: 'Review the install script before deployment'
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('Error analyzing install scripts:', error.message);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return warnings;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Perform comprehensive supply chain security analysis
|
|
170
|
+
*/
|
|
171
|
+
async function analyzeSupplyChain(projectPath, dependencies) {
|
|
172
|
+
const warnings = [];
|
|
173
|
+
const database = loadMaliciousDatabase();
|
|
174
|
+
|
|
175
|
+
// Check each dependency
|
|
176
|
+
for (const [packageName, version] of Object.entries(dependencies)) {
|
|
177
|
+
// 1. Check if known malicious
|
|
178
|
+
const maliciousCheck = checkMaliciousPackage(packageName, database);
|
|
179
|
+
if (maliciousCheck) {
|
|
180
|
+
warnings.push(maliciousCheck);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 2. Detect typosquatting
|
|
184
|
+
const typosquatWarnings = detectTyposquatting(packageName, database);
|
|
185
|
+
warnings.push(...typosquatWarnings);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 3. Analyze install scripts
|
|
189
|
+
const scriptWarnings = analyzeInstallScripts(projectPath, dependencies);
|
|
190
|
+
warnings.push(...scriptWarnings);
|
|
191
|
+
|
|
192
|
+
return warnings;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get supply chain statistics
|
|
197
|
+
*/
|
|
198
|
+
function getSupplyChainStats(warnings) {
|
|
199
|
+
return {
|
|
200
|
+
total: warnings.length,
|
|
201
|
+
critical: warnings.filter(w => w.severity === 'critical').length,
|
|
202
|
+
high: warnings.filter(w => w.severity === 'high').length,
|
|
203
|
+
medium: warnings.filter(w => w.severity === 'medium').length,
|
|
204
|
+
malicious: warnings.filter(w => w.type === 'malicious').length,
|
|
205
|
+
typosquatting: warnings.filter(w => w.type === 'typosquatting' || w.type === 'typosquatting_suspected').length,
|
|
206
|
+
installScripts: warnings.filter(w => w.type === 'install_script').length
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = {
|
|
211
|
+
analyzeSupplyChain,
|
|
212
|
+
detectTyposquatting,
|
|
213
|
+
checkMaliciousPackage,
|
|
214
|
+
analyzeInstallScripts,
|
|
215
|
+
getSupplyChainStats,
|
|
216
|
+
loadMaliciousDatabase
|
|
217
|
+
};
|