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.
@@ -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
+ };