devcompass 2.6.0 → 2.7.1

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,223 @@
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, packageVersion, maliciousDb) {
58
+ const warnings = [];
59
+
60
+ // Whitelist of legitimate popular packages that might have similar names
61
+ // These should NEVER be flagged as typosquatting
62
+ const LEGITIMATE_PACKAGES = [
63
+ 'chalk', 'chai', 'ora', 'yargs', 'meow', 'execa', 'globby', 'del', 'make-dir',
64
+ 'p-map', 'p-limit', 'p-queue', 'got', 'ky', 'node-fetch', 'cross-fetch',
65
+ 'uuid', 'nanoid', 'cuid', 'luxon', 'date-fns', 'ms', 'bytes', 'filesize',
66
+ 'fast-glob', 'chokidar', 'picomatch', 'micromatch', 'anymatch',
67
+ 'semver', 'commander', 'yargs', 'inquirer', 'prompts', 'enquirer',
68
+ 'debug', 'pino', 'winston', 'bunyan', 'signale'
69
+ ];
70
+
71
+ // Skip whitelist packages
72
+ if (LEGITIMATE_PACKAGES.includes(packageName)) {
73
+ return warnings;
74
+ }
75
+
76
+ // Check against known typosquat patterns
77
+ // typosquat_patterns is an object: { "express": ["epress", "expres"], ... }
78
+ const patterns = maliciousDb.typosquat_patterns || {};
79
+
80
+ for (const officialName of Object.keys(patterns)) {
81
+ // Skip if the official package is also whitelisted (both are legitimate)
82
+ // This prevents false positives like "chalk" vs "chai"
83
+ if (LEGITIMATE_PACKAGES.includes(officialName)) {
84
+ continue;
85
+ }
86
+
87
+ const distance = levenshteinDistance(packageName, officialName);
88
+
89
+ // Flag if 1-2 character difference (typosquatting likely)
90
+ if (distance > 0 && distance <= 2 && packageName !== officialName) {
91
+ warnings.push({
92
+ package: `${packageName}@${packageVersion}`,
93
+ type: 'typosquatting',
94
+ severity: distance === 1 ? 'high' : 'medium',
95
+ message: `Similar to: ${officialName} (official package)`,
96
+ recommendation: `Verify if you meant to install ${officialName}`,
97
+ official: officialName
98
+ });
99
+ }
100
+ }
101
+
102
+ return warnings;
103
+ }
104
+
105
+ /**
106
+ * Check if package is known malicious
107
+ */
108
+ function checkMaliciousPackage(packageName, database) {
109
+ if (database.malicious_packages.includes(packageName.toLowerCase())) {
110
+ return {
111
+ package: packageName,
112
+ type: 'malicious',
113
+ severity: 'critical',
114
+ message: `Known malicious package detected: ${packageName}`,
115
+ recommendation: 'Remove immediately - this package is known to be malicious'
116
+ };
117
+ }
118
+ return null;
119
+ }
120
+
121
+ /**
122
+ * Analyze install scripts for suspicious patterns
123
+ */
124
+ function analyzeInstallScripts(projectPath, dependencies) {
125
+ const warnings = [];
126
+ const database = loadMaliciousDatabase();
127
+
128
+ try {
129
+ for (const dep of Object.keys(dependencies)) {
130
+ const packageJsonPath = path.join(projectPath, 'node_modules', dep, 'package.json');
131
+
132
+ if (!fs.existsSync(packageJsonPath)) {
133
+ continue;
134
+ }
135
+
136
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
137
+ const scripts = packageJson.scripts || {};
138
+
139
+ // Check for install hooks
140
+ const installHooks = ['preinstall', 'install', 'postinstall'];
141
+
142
+ for (const hook of installHooks) {
143
+ if (scripts[hook]) {
144
+ const script = scripts[hook].toLowerCase();
145
+
146
+ // Check for suspicious patterns
147
+ const suspiciousPatterns = database.suspicious_patterns.install_scripts;
148
+ const foundPatterns = suspiciousPatterns.filter(pattern =>
149
+ script.includes(pattern.toLowerCase())
150
+ );
151
+
152
+ if (foundPatterns.length > 0) {
153
+ warnings.push({
154
+ package: `${dep}@${packageJson.version}`,
155
+ type: 'install_script',
156
+ severity: foundPatterns.some(p => ['curl', 'wget', 'eval', 'exec'].includes(p)) ? 'high' : 'medium',
157
+ script: hook,
158
+ action: scripts[hook],
159
+ patterns: foundPatterns,
160
+ message: `Install script contains suspicious patterns: ${foundPatterns.join(', ')}`,
161
+ recommendation: 'Review the install script before deployment'
162
+ });
163
+ }
164
+ }
165
+ }
166
+ }
167
+ } catch (error) {
168
+ console.error('Error analyzing install scripts:', error.message);
169
+ }
170
+
171
+ return warnings;
172
+ }
173
+
174
+ /**
175
+ * Perform comprehensive supply chain security analysis
176
+ */
177
+ async function analyzeSupplyChain(projectPath, dependencies) {
178
+ const warnings = [];
179
+ const database = loadMaliciousDatabase();
180
+
181
+ // Check each dependency
182
+ for (const [packageName, version] of Object.entries(dependencies)) {
183
+ // 1. Check if known malicious
184
+ const maliciousCheck = checkMaliciousPackage(packageName, database);
185
+ if (maliciousCheck) {
186
+ warnings.push(maliciousCheck);
187
+ }
188
+
189
+ // 2. Detect typosquatting
190
+ const typosquatWarnings = detectTyposquatting(packageName, version, database);
191
+ warnings.push(...typosquatWarnings);
192
+ }
193
+
194
+ // 3. Analyze install scripts
195
+ const scriptWarnings = analyzeInstallScripts(projectPath, dependencies);
196
+ warnings.push(...scriptWarnings);
197
+
198
+ return warnings;
199
+ }
200
+
201
+ /**
202
+ * Get supply chain statistics
203
+ */
204
+ function getSupplyChainStats(warnings) {
205
+ return {
206
+ total: warnings.length,
207
+ critical: warnings.filter(w => w.severity === 'critical').length,
208
+ high: warnings.filter(w => w.severity === 'high').length,
209
+ medium: warnings.filter(w => w.severity === 'medium').length,
210
+ malicious: warnings.filter(w => w.type === 'malicious').length,
211
+ typosquatting: warnings.filter(w => w.type === 'typosquatting' || w.type === 'typosquatting_suspected').length,
212
+ installScripts: warnings.filter(w => w.type === 'install_script').length
213
+ };
214
+ }
215
+
216
+ module.exports = {
217
+ analyzeSupplyChain,
218
+ detectTyposquatting,
219
+ checkMaliciousPackage,
220
+ analyzeInstallScripts,
221
+ getSupplyChainStats,
222
+ loadMaliciousDatabase
223
+ };