devcompass 2.6.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,274 @@
1
+ // src/analyzers/security-recommendations.js
2
+
3
+ /**
4
+ * Priority levels for recommendations
5
+ */
6
+ const PRIORITY = {
7
+ CRITICAL: { level: 1, label: 'CRITICAL', color: 'red', emoji: '🔴' },
8
+ HIGH: { level: 2, label: 'HIGH', color: 'orange', emoji: '🟠' },
9
+ MEDIUM: { level: 3, label: 'MEDIUM', color: 'yellow', emoji: '🟡' },
10
+ LOW: { level: 4, label: 'LOW', color: 'gray', emoji: '⚪' }
11
+ };
12
+
13
+ /**
14
+ * Generate actionable recommendations from all security findings
15
+ */
16
+ function generateSecurityRecommendations(analysisResults) {
17
+ const recommendations = [];
18
+
19
+ const {
20
+ supplyChainWarnings = [],
21
+ licenseWarnings = [],
22
+ qualityResults = [],
23
+ securityVulnerabilities = [],
24
+ ecosystemAlerts = [],
25
+ unusedDeps = [],
26
+ outdatedPackages = []
27
+ } = analysisResults;
28
+
29
+ // 1. Supply Chain Issues (CRITICAL/HIGH)
30
+ for (const warning of supplyChainWarnings) {
31
+ if (warning.type === 'malicious' || warning.severity === 'critical') {
32
+ recommendations.push({
33
+ priority: PRIORITY.CRITICAL,
34
+ category: 'supply_chain',
35
+ package: warning.package,
36
+ issue: warning.message,
37
+ action: `Remove ${warning.package} immediately`,
38
+ command: `npm uninstall ${warning.package}`,
39
+ impact: 'Eliminates critical security risk',
40
+ reason: 'Known malicious package detected'
41
+ });
42
+ } else if (warning.type === 'typosquatting') {
43
+ recommendations.push({
44
+ priority: PRIORITY.HIGH,
45
+ category: 'supply_chain',
46
+ package: warning.package,
47
+ issue: warning.message,
48
+ action: warning.recommendation,
49
+ command: `npm uninstall ${warning.package} && npm install ${warning.official}`,
50
+ impact: 'Prevents potential supply chain attack',
51
+ reason: 'Typosquatting attempt detected'
52
+ });
53
+ } else if (warning.type === 'install_script' && warning.severity === 'high') {
54
+ recommendations.push({
55
+ priority: PRIORITY.HIGH,
56
+ category: 'supply_chain',
57
+ package: warning.package,
58
+ issue: warning.message,
59
+ action: 'Review install script before deployment',
60
+ command: `cat node_modules/${warning.package.split('@')[0]}/package.json`,
61
+ impact: 'Prevents malicious code execution',
62
+ reason: 'Suspicious install script detected'
63
+ });
64
+ }
65
+ }
66
+
67
+ // 2. License Compliance (HIGH/MEDIUM)
68
+ for (const warning of licenseWarnings) {
69
+ if (warning.severity === 'critical' || warning.severity === 'high') {
70
+ recommendations.push({
71
+ priority: warning.severity === 'critical' ? PRIORITY.CRITICAL : PRIORITY.HIGH,
72
+ category: 'license',
73
+ package: warning.package,
74
+ issue: `${warning.license}: ${warning.message}`,
75
+ action: 'Replace with permissive alternative',
76
+ command: `npm uninstall ${warning.package}`,
77
+ impact: 'Ensures license compliance',
78
+ reason: 'High-risk license detected',
79
+ alternative: 'Search npm for MIT/Apache alternatives'
80
+ });
81
+ }
82
+ }
83
+
84
+ // 3. Security Vulnerabilities (CRITICAL/HIGH)
85
+ if (securityVulnerabilities.critical > 0 || securityVulnerabilities.high > 0) {
86
+ recommendations.push({
87
+ priority: securityVulnerabilities.critical > 0 ? PRIORITY.CRITICAL : PRIORITY.HIGH,
88
+ category: 'security',
89
+ issue: `${securityVulnerabilities.total} security vulnerabilities detected`,
90
+ action: 'Run npm audit fix to resolve vulnerabilities',
91
+ command: 'npm audit fix',
92
+ impact: `Resolves ${securityVulnerabilities.total} known vulnerabilities`,
93
+ reason: 'Security vulnerabilities in dependencies'
94
+ });
95
+ }
96
+
97
+ // 4. Ecosystem Alerts (varies by severity)
98
+ for (const alert of ecosystemAlerts) {
99
+ if (alert.severity === 'critical' || alert.severity === 'high') {
100
+ const priority = alert.severity === 'critical' ? PRIORITY.CRITICAL : PRIORITY.HIGH;
101
+
102
+ recommendations.push({
103
+ priority: priority,
104
+ category: 'ecosystem',
105
+ package: `${alert.package}@${alert.version}`,
106
+ issue: alert.title,
107
+ action: `Upgrade to ${alert.fix}`,
108
+ command: `npm install ${alert.package}@${alert.fix}`,
109
+ impact: 'Resolves known stability/security issue',
110
+ reason: alert.source
111
+ });
112
+ }
113
+ }
114
+
115
+ // 5. Package Quality Issues (MEDIUM/LOW)
116
+ for (const pkg of qualityResults) {
117
+ if (pkg.status === 'deprecated') {
118
+ recommendations.push({
119
+ priority: PRIORITY.CRITICAL,
120
+ category: 'quality',
121
+ package: pkg.package,
122
+ issue: 'Package is deprecated',
123
+ action: 'Find actively maintained alternative',
124
+ command: `npm uninstall ${pkg.package}`,
125
+ impact: 'Prevents future breaking changes',
126
+ reason: 'Package is no longer maintained',
127
+ healthScore: pkg.healthScore
128
+ });
129
+ } else if (pkg.status === 'abandoned') {
130
+ recommendations.push({
131
+ priority: PRIORITY.HIGH,
132
+ category: 'quality',
133
+ package: pkg.package,
134
+ issue: `Last updated ${Math.floor(pkg.daysSincePublish / 365)} years ago`,
135
+ action: 'Migrate to actively maintained alternative',
136
+ command: `npm uninstall ${pkg.package}`,
137
+ impact: 'Improves long-term stability',
138
+ reason: 'Package appears abandoned',
139
+ healthScore: pkg.healthScore
140
+ });
141
+ } else if (pkg.status === 'stale' && pkg.healthScore < 5) {
142
+ recommendations.push({
143
+ priority: PRIORITY.MEDIUM,
144
+ category: 'quality',
145
+ package: pkg.package,
146
+ issue: `Health score: ${pkg.healthScore}/10`,
147
+ action: 'Consider more actively maintained alternative',
148
+ impact: 'Improves package quality',
149
+ reason: 'Low health score',
150
+ healthScore: pkg.healthScore
151
+ });
152
+ }
153
+ }
154
+
155
+ // 6. Unused Dependencies (MEDIUM)
156
+ if (unusedDeps.length > 0) {
157
+ const packageList = unusedDeps.map(d => d.name).join(' ');
158
+ recommendations.push({
159
+ priority: PRIORITY.MEDIUM,
160
+ category: 'cleanup',
161
+ issue: `${unusedDeps.length} unused dependencies detected`,
162
+ action: 'Remove unused packages',
163
+ command: `npm uninstall ${packageList}`,
164
+ impact: `Reduces node_modules size, improves security surface`,
165
+ reason: 'Unused dependencies increase attack surface'
166
+ });
167
+ }
168
+
169
+ // 7. Outdated Packages (LOW)
170
+ const criticalOutdated = outdatedPackages.filter(p =>
171
+ p.updateType === 'major update' && p.current.startsWith('0.')
172
+ );
173
+
174
+ if (criticalOutdated.length > 0) {
175
+ for (const pkg of criticalOutdated.slice(0, 3)) { // Top 3
176
+ recommendations.push({
177
+ priority: PRIORITY.MEDIUM,
178
+ category: 'updates',
179
+ package: pkg.name,
180
+ issue: `Version ${pkg.current} is pre-1.0 and outdated`,
181
+ action: `Update to ${pkg.latest}`,
182
+ command: `npm install ${pkg.name}@latest`,
183
+ impact: 'Gets bug fixes and improvements',
184
+ reason: 'Pre-1.0 packages change rapidly'
185
+ });
186
+ }
187
+ }
188
+
189
+ // Sort by priority
190
+ recommendations.sort((a, b) => a.priority.level - b.priority.level);
191
+
192
+ return recommendations;
193
+ }
194
+
195
+ /**
196
+ * Group recommendations by priority
197
+ */
198
+ function groupByPriority(recommendations) {
199
+ return {
200
+ critical: recommendations.filter(r => r.priority.level === 1),
201
+ high: recommendations.filter(r => r.priority.level === 2),
202
+ medium: recommendations.filter(r => r.priority.level === 3),
203
+ low: recommendations.filter(r => r.priority.level === 4)
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Calculate expected impact of following recommendations
209
+ */
210
+ function calculateExpectedImpact(recommendations, currentScore) {
211
+ let improvement = 0;
212
+
213
+ for (const rec of recommendations) {
214
+ switch (rec.priority.level) {
215
+ case 1: // CRITICAL
216
+ improvement += 2.0;
217
+ break;
218
+ case 2: // HIGH
219
+ improvement += 1.5;
220
+ break;
221
+ case 3: // MEDIUM
222
+ improvement += 0.5;
223
+ break;
224
+ case 4: // LOW
225
+ improvement += 0.2;
226
+ break;
227
+ }
228
+ }
229
+
230
+ const newScore = Math.min(10, currentScore + improvement);
231
+ const percentageIncrease = ((newScore - currentScore) / 10) * 100;
232
+
233
+ return {
234
+ currentScore,
235
+ expectedScore: Number(newScore.toFixed(1)),
236
+ improvement: Number(improvement.toFixed(1)),
237
+ percentageIncrease: Number(percentageIncrease.toFixed(1)),
238
+ critical: recommendations.filter(r => r.priority.level === 1).length,
239
+ high: recommendations.filter(r => r.priority.level === 2).length,
240
+ medium: recommendations.filter(r => r.priority.level === 3).length,
241
+ low: recommendations.filter(r => r.priority.level === 4).length
242
+ };
243
+ }
244
+
245
+ /**
246
+ * Get top N recommendations
247
+ */
248
+ function getTopRecommendations(recommendations, n = 5) {
249
+ return recommendations.slice(0, n);
250
+ }
251
+
252
+ /**
253
+ * Get recommendations by category
254
+ */
255
+ function getRecommendationsByCategory(recommendations) {
256
+ return {
257
+ supply_chain: recommendations.filter(r => r.category === 'supply_chain'),
258
+ license: recommendations.filter(r => r.category === 'license'),
259
+ security: recommendations.filter(r => r.category === 'security'),
260
+ ecosystem: recommendations.filter(r => r.category === 'ecosystem'),
261
+ quality: recommendations.filter(r => r.category === 'quality'),
262
+ cleanup: recommendations.filter(r => r.category === 'cleanup'),
263
+ updates: recommendations.filter(r => r.category === 'updates')
264
+ };
265
+ }
266
+
267
+ module.exports = {
268
+ generateSecurityRecommendations,
269
+ groupByPriority,
270
+ calculateExpectedImpact,
271
+ getTopRecommendations,
272
+ getRecommendationsByCategory,
273
+ PRIORITY
274
+ };
@@ -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
+ };