web3crit-scanner 7.0.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.
Files changed (42) hide show
  1. package/README.md +685 -0
  2. package/bin/web3crit +10 -0
  3. package/package.json +59 -0
  4. package/src/analyzers/control-flow.js +256 -0
  5. package/src/analyzers/data-flow.js +720 -0
  6. package/src/analyzers/exploit-chain.js +751 -0
  7. package/src/analyzers/immunefi-classifier.js +515 -0
  8. package/src/analyzers/poc-validator.js +396 -0
  9. package/src/analyzers/solodit-enricher.js +1122 -0
  10. package/src/cli.js +546 -0
  11. package/src/detectors/access-control-enhanced.js +458 -0
  12. package/src/detectors/base-detector.js +213 -0
  13. package/src/detectors/callback-reentrancy.js +362 -0
  14. package/src/detectors/cross-contract-reentrancy.js +697 -0
  15. package/src/detectors/delegatecall.js +167 -0
  16. package/src/detectors/deprecated-functions.js +62 -0
  17. package/src/detectors/flash-loan.js +408 -0
  18. package/src/detectors/frontrunning.js +553 -0
  19. package/src/detectors/gas-griefing.js +701 -0
  20. package/src/detectors/governance-attacks.js +366 -0
  21. package/src/detectors/integer-overflow.js +487 -0
  22. package/src/detectors/oracle-manipulation.js +524 -0
  23. package/src/detectors/permit-exploits.js +368 -0
  24. package/src/detectors/precision-loss.js +408 -0
  25. package/src/detectors/price-manipulation-advanced.js +548 -0
  26. package/src/detectors/proxy-vulnerabilities.js +651 -0
  27. package/src/detectors/readonly-reentrancy.js +473 -0
  28. package/src/detectors/rebasing-token-vault.js +416 -0
  29. package/src/detectors/reentrancy-enhanced.js +359 -0
  30. package/src/detectors/selfdestruct.js +259 -0
  31. package/src/detectors/share-manipulation.js +412 -0
  32. package/src/detectors/signature-replay.js +409 -0
  33. package/src/detectors/storage-collision.js +446 -0
  34. package/src/detectors/timestamp-dependence.js +494 -0
  35. package/src/detectors/toctou.js +427 -0
  36. package/src/detectors/token-standard-compliance.js +465 -0
  37. package/src/detectors/unchecked-call.js +214 -0
  38. package/src/detectors/vault-inflation.js +421 -0
  39. package/src/index.js +42 -0
  40. package/src/package-lock.json +2874 -0
  41. package/src/package.json +39 -0
  42. package/src/scanner-enhanced.js +816 -0
package/src/cli.js ADDED
@@ -0,0 +1,546 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const chalk = require('chalk');
5
+ const ora = require('ora');
6
+ const boxen = require('boxen');
7
+ const Table = require('cli-table3');
8
+ const gradient = require('gradient-string');
9
+ const figures = require('figures');
10
+ const fs = require('fs').promises;
11
+ const path = require('path');
12
+ const Web3CRITScanner = require('./scanner-enhanced');
13
+ const pkg = require('../package.json');
14
+
15
+ const program = new Command();
16
+
17
+ // WEB3CRIT Banner
18
+ function displayBanner() {
19
+ const banner = `
20
+ ██╗ ██╗███████╗██████╗ ██████╗ ██████╗██████╗ ██╗████████╗
21
+ ██║ ██║██╔════╝██╔══██╗╚════██╗██╔════╝██╔══██╗██║╚══██╔══╝
22
+ ██║ █╗ ██║█████╗ ██████╔╝ █████╔╝██║ ██████╔╝██║ ██║
23
+ ██║███╗██║██╔══╝ ██╔══██╗ ╚═══██╗██║ ██╔══██╗██║ ██║
24
+ ╚███╔███╔╝███████╗██████╔╝██████╔╝╚██████╗██║ ██║██║ ██║
25
+ ╚══╝╚══╝ ╚══════╝╚═════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝
26
+ `;
27
+
28
+ console.log(chalk.red.bold(banner));
29
+ console.log(chalk.gray(' Production-Grade Smart Contract Security Scanner'));
30
+ console.log(chalk.gray(` Version ${pkg.version} - Exploit-Driven | Immunefi-Aligned | High-TVL Ready\n`));
31
+ }
32
+
33
+ // Severity color mapping
34
+ function getSeverityColor(severity) {
35
+ const colors = {
36
+ CRITICAL: chalk.red.bold,
37
+ HIGH: chalk.red,
38
+ MEDIUM: chalk.yellow,
39
+ LOW: chalk.blue,
40
+ INFO: chalk.gray
41
+ };
42
+ return colors[severity] || chalk.white;
43
+ }
44
+
45
+ // Display results in table format
46
+ function displayTableResults(results) {
47
+ const { findings, stats } = results;
48
+
49
+ // Summary box
50
+ const modeIndicator = stats.productionMode
51
+ ? chalk.magenta.bold('\n[PRODUCTION MODE] PoC-verified findings only')
52
+ : '';
53
+
54
+ const pocStats = stats.pocValidated !== undefined
55
+ ? `\nPoC Validated: ${chalk.green(stats.pocValidated)} | Dropped: ${chalk.red(stats.pocDropped || 0)}`
56
+ : '';
57
+
58
+ const soloditStats = stats.soloditEnabled
59
+ ? `\n${chalk.cyan('Solodit Correlation:')}
60
+ Confirmed in Wild: ${chalk.red.bold(stats.soloditConfirmedInWild || 0)}
61
+ Confirmed Pattern: ${chalk.yellow(stats.soloditConfirmedPattern || 0)}
62
+ Theoretical: ${chalk.gray(stats.soloditTheoretical || 0)}`
63
+ : '';
64
+
65
+ const summaryContent = `
66
+ ${chalk.bold('Scan Summary')}${modeIndicator}
67
+ ${'─'.repeat(40)}
68
+ Files Scanned: ${chalk.cyan(stats.filesScanned)}
69
+ Total Findings: ${chalk.cyan(stats.totalFindings)}${pocStats}
70
+
71
+ ${chalk.red.bold(figures.cross)} Critical: ${stats.critical}
72
+ ${chalk.red(figures.warning)} High: ${stats.high}
73
+ ${chalk.yellow(figures.warning)} Medium: ${stats.medium}
74
+ ${chalk.blue(figures.info)} Low: ${stats.low}
75
+ ${chalk.gray(figures.info)} Info: ${stats.info}
76
+ ${soloditStats}
77
+ `;
78
+
79
+ console.log(boxen(summaryContent, {
80
+ padding: 1,
81
+ margin: 1,
82
+ borderStyle: 'round',
83
+ borderColor: 'cyan'
84
+ }));
85
+
86
+ if (findings.length === 0) {
87
+ console.log(chalk.green.bold('\nNo vulnerabilities found!\n'));
88
+ return;
89
+ }
90
+
91
+ // Findings table
92
+ console.log(chalk.bold('\nDetailed Findings:\n'));
93
+
94
+ findings.forEach((finding, index) => {
95
+ const severityColor = getSeverityColor(finding.severity);
96
+
97
+ // Display confidence level for findings
98
+ const confidenceBadge = finding.confidence ?
99
+ (finding.confidence === 'HIGH' ? chalk.green.bold('[HIGH CONFIDENCE]') :
100
+ finding.confidence === 'MEDIUM' ? chalk.yellow('[MEDIUM]') :
101
+ chalk.gray('[LOW]')) : '';
102
+
103
+ // Display proven impact badge for production mode findings
104
+ const impactBadge = finding.provenImpact ?
105
+ chalk.red.bold(` [IMPACT: ${finding.provenImpact.impactType}]`) : '';
106
+
107
+ console.log(severityColor(`\n[${finding.severity}] ${finding.title} ${confidenceBadge}${impactBadge}`));
108
+ console.log(chalk.gray(`${figures.arrowRight} ${finding.fileName}:${finding.line}:${finding.column}`));
109
+ console.log(chalk.white(` ${finding.description}`));
110
+
111
+ // Show proven impact evidence if available
112
+ if (finding.provenImpact) {
113
+ console.log(chalk.red(`\n ${figures.warning} Proven Impact: ${finding.provenImpact.evidence}`));
114
+ }
115
+
116
+ // Show Solodit correlation if available
117
+ if (finding.soloditMetadata && finding.soloditMetadata.matched) {
118
+ const sm = finding.soloditMetadata;
119
+ const statusColor = sm.validationStatus?.status === 'confirmed_in_wild' ? chalk.red.bold :
120
+ sm.validationStatus?.status === 'confirmed_pattern' ? chalk.yellow :
121
+ chalk.gray;
122
+ const statusLabel = sm.validationStatus?.label || 'Unknown';
123
+ const confidence = Math.round((sm.matchConfidence || 0) * 100);
124
+
125
+ console.log(statusColor(`\n ${figures.star} Solodit: [${statusLabel}] (${confidence}% match)`));
126
+
127
+ if (sm.topMatch) {
128
+ console.log(chalk.gray(` Best Match: "${sm.topMatch.title}" (${sm.topMatch.protocol})`));
129
+ if (sm.topMatch.exploited) {
130
+ console.log(chalk.red(` ${figures.warning} EXPLOITED in production`));
131
+ }
132
+ if (sm.topMatch.bountyAmount) {
133
+ console.log(chalk.green(` ${figures.tick} Bounty: $${sm.topMatch.bountyAmount.toLocaleString()}`));
134
+ }
135
+ }
136
+
137
+ if (sm.realWorldExploits && sm.realWorldExploits.length > 0) {
138
+ console.log(chalk.red(` Related Exploits:`));
139
+ sm.realWorldExploits.slice(0, 2).forEach(exp => {
140
+ const loss = exp.lossAmount ? ` ($${exp.lossAmount.toLocaleString()} loss)` : '';
141
+ console.log(chalk.red(` ${figures.pointer} ${exp.protocol}: ${exp.title}${loss}`));
142
+ });
143
+ }
144
+ } else if (finding.soloditMetadata && !finding.soloditMetadata.matched) {
145
+ console.log(chalk.gray(`\n ${figures.info} Solodit: [Theoretical] No matching real-world exploits found`));
146
+ }
147
+
148
+ if (finding.code) {
149
+ console.log(chalk.gray('\n Code:'));
150
+ console.log(chalk.dim(' ' + finding.code.split('\n').join('\n ')));
151
+ }
152
+
153
+ console.log(chalk.cyan(`\n ${figures.pointer} Recommendation:`));
154
+ console.log(chalk.white(` ${finding.recommendation}`));
155
+
156
+ if (finding.references && finding.references.length > 0) {
157
+ console.log(chalk.gray(`\n References:`));
158
+ finding.references.forEach(ref => {
159
+ console.log(chalk.gray(` ${figures.arrowRight} ${ref}`));
160
+ });
161
+ }
162
+
163
+ console.log(chalk.gray('─'.repeat(80)));
164
+ });
165
+ }
166
+
167
+ // Display results in JSON format
168
+ function displayJsonResults(results) {
169
+ console.log(JSON.stringify(results, null, 2));
170
+ }
171
+
172
+ // Save results to file
173
+ async function saveResults(results, outputPath, format) {
174
+ try {
175
+ let content;
176
+
177
+ if (format === 'json') {
178
+ content = JSON.stringify(results, null, 2);
179
+ } else if (format === 'markdown') {
180
+ content = generateMarkdownReport(results);
181
+ } else {
182
+ content = generateTextReport(results);
183
+ }
184
+
185
+ await fs.writeFile(outputPath, content, 'utf8');
186
+ console.log(chalk.green(`\nReport saved to: ${outputPath}\n`));
187
+ } catch (error) {
188
+ console.error(chalk.red(`Error saving report: ${error.message}`));
189
+ }
190
+ }
191
+
192
+ // Generate Markdown report
193
+ function generateMarkdownReport(results) {
194
+ const { findings, stats } = results;
195
+
196
+ let markdown = '# Web3CRIT Security Scan Report\n\n';
197
+ markdown += `**Scan Date:** ${new Date().toISOString()}\n\n`;
198
+
199
+ markdown += '## Summary\n\n';
200
+ markdown += `- **Files Scanned:** ${stats.filesScanned}\n`;
201
+ markdown += `- **Total Findings:** ${stats.totalFindings}\n`;
202
+ markdown += `- **Critical:** ${stats.critical}\n`;
203
+ markdown += `- **High:** ${stats.high}\n`;
204
+ markdown += `- **Medium:** ${stats.medium}\n`;
205
+ markdown += `- **Low:** ${stats.low}\n`;
206
+ markdown += `- **Info:** ${stats.info}\n\n`;
207
+
208
+ markdown += '## Findings\n\n';
209
+
210
+ findings.forEach((finding, index) => {
211
+ markdown += `### ${index + 1}. [${finding.severity}] ${finding.title}\n\n`;
212
+ markdown += `**File:** ${finding.fileName}:${finding.line}:${finding.column}\n\n`;
213
+ markdown += `**Description:** ${finding.description}\n\n`;
214
+
215
+ if (finding.code) {
216
+ markdown += '**Code:**\n```solidity\n' + finding.code + '\n```\n\n';
217
+ }
218
+
219
+ markdown += `**Recommendation:** ${finding.recommendation}\n\n`;
220
+
221
+ // Solodit correlation
222
+ if (finding.soloditMetadata) {
223
+ const sm = finding.soloditMetadata;
224
+ markdown += '**Solodit Correlation:**\n';
225
+ markdown += `- Status: ${sm.validationStatus?.label || 'Unknown'}\n`;
226
+ markdown += `- Match Confidence: ${Math.round((sm.matchConfidence || 0) * 100)}%\n`;
227
+
228
+ if (sm.topMatch) {
229
+ markdown += `- Best Match: "${sm.topMatch.title}" (${sm.topMatch.protocol})\n`;
230
+ if (sm.topMatch.exploited) {
231
+ markdown += `- **EXPLOITED in production**\n`;
232
+ }
233
+ if (sm.topMatch.bountyAmount) {
234
+ markdown += `- Bounty Amount: $${sm.topMatch.bountyAmount.toLocaleString()}\n`;
235
+ }
236
+ }
237
+
238
+ if (sm.realWorldExploits && sm.realWorldExploits.length > 0) {
239
+ markdown += '\n**Related Real-World Exploits:**\n';
240
+ sm.realWorldExploits.forEach(exp => {
241
+ const loss = exp.lossAmount ? ` ($${exp.lossAmount.toLocaleString()} loss)` : '';
242
+ markdown += `- ${exp.protocol}: ${exp.title}${loss}\n`;
243
+ });
244
+ }
245
+ markdown += '\n';
246
+ }
247
+
248
+ if (finding.references && finding.references.length > 0) {
249
+ markdown += '**References:**\n';
250
+ finding.references.forEach(ref => {
251
+ markdown += `- ${ref}\n`;
252
+ });
253
+ markdown += '\n';
254
+ }
255
+
256
+ markdown += '---\n\n';
257
+ });
258
+
259
+ return markdown;
260
+ }
261
+
262
+ // Generate text report
263
+ function generateTextReport(results) {
264
+ const { findings, stats } = results;
265
+
266
+ let text = 'WEB3CRIT SECURITY SCAN REPORT\n';
267
+ text += '='.repeat(80) + '\n\n';
268
+ text += `Scan Date: ${new Date().toISOString()}\n\n`;
269
+
270
+ text += 'SUMMARY\n';
271
+ text += '-'.repeat(80) + '\n';
272
+ text += `Files Scanned: ${stats.filesScanned}\n`;
273
+ text += `Total Findings: ${stats.totalFindings}\n`;
274
+ text += ` Critical: ${stats.critical}\n`;
275
+ text += ` High: ${stats.high}\n`;
276
+ text += ` Medium: ${stats.medium}\n`;
277
+ text += ` Low: ${stats.low}\n`;
278
+ text += ` Info: ${stats.info}\n\n`;
279
+
280
+ text += 'FINDINGS\n';
281
+ text += '='.repeat(80) + '\n\n';
282
+
283
+ findings.forEach((finding, index) => {
284
+ text += `${index + 1}. [${finding.severity}] ${finding.title}\n`;
285
+ text += ` File: ${finding.fileName}:${finding.line}:${finding.column}\n\n`;
286
+ text += ` Description:\n ${finding.description}\n\n`;
287
+
288
+ if (finding.code) {
289
+ text += ' Code:\n';
290
+ finding.code.split('\n').forEach(line => {
291
+ text += ` ${line}\n`;
292
+ });
293
+ text += '\n';
294
+ }
295
+
296
+ text += ` Recommendation:\n ${finding.recommendation}\n\n`;
297
+
298
+ if (finding.references && finding.references.length > 0) {
299
+ text += ' References:\n';
300
+ finding.references.forEach(ref => {
301
+ text += ` - ${ref}\n`;
302
+ });
303
+ text += '\n';
304
+ }
305
+
306
+ text += '-'.repeat(80) + '\n\n';
307
+ });
308
+
309
+ return text;
310
+ }
311
+
312
+ // Main scan command
313
+ program
314
+ .name('web3crit')
315
+ .description('Production-grade smart contract vulnerability scanner for high-value DeFi protocols')
316
+ .version(pkg.version);
317
+
318
+ program
319
+ .command('scan')
320
+ .description('Scan Solidity files for vulnerabilities')
321
+ .argument('<path>', 'File or directory to scan')
322
+ .option('-s, --severity <level>', 'Minimum severity level (critical, high, medium, low, info, all)', 'all')
323
+ .option('-o, --output <file>', 'Save report to file')
324
+ .option('-f, --format <format>', 'Output format (table, json, markdown, text)', 'table')
325
+ .option('-v, --verbose', 'Verbose output')
326
+ .option('--exploit-driven', 'Enable exploit-driven analysis (attach exploit chains + Immunefi classification)')
327
+ .option('--immunefi-only', 'Strict Immunefi mode (only keep High/Critical payout-eligible findings)')
328
+ .option('--production', 'PRODUCTION MODE: Gate HIGH/CRITICAL behind PoC execution with proven impact (for $5M+ TVL, Immunefi bounties)')
329
+ .option('--poc-validate', 'Validate attached Foundry PoCs (drop findings whose PoCs fail validation)')
330
+ .option('--poc-require-pass', 'Require PoCs to compile+pass under forge (requires foundry.toml + forge installed)')
331
+ .option('--poc-mode <mode>', 'PoC validation mode: test|build (default: test)', 'test')
332
+ .option('--foundry-root <path>', 'Path to Foundry project root (directory containing foundry.toml)')
333
+ .option('--fork-url <url>', 'RPC URL for fork testing (enables on-chain state verification)')
334
+ .option('--poc-keep-temp', 'Keep temporary test files written during PoC validation')
335
+ .option('--solodit-enrich', 'Enrich findings with Solodit vulnerability database (correlate with real-world exploits)')
336
+ .option('--solodit-api-key <key>', 'Solodit API key (or set SOLODIT_API_KEY env var)')
337
+ .option('--solodit-min-confidence <threshold>', 'Minimum Solodit match confidence (0-1, default: 0.3)', parseFloat)
338
+ .option('--no-banner', 'Disable banner')
339
+ .action(async (targetPath, options) => {
340
+ if (options.banner !== false) {
341
+ displayBanner();
342
+ }
343
+
344
+ const spinner = ora({
345
+ text: 'Initializing scanner...',
346
+ spinner: 'dots12'
347
+ }).start();
348
+
349
+ const startTime = Date.now();
350
+
351
+ try {
352
+ // Progress callback to update spinner
353
+ let detectorCount = 0;
354
+
355
+ const onProgress = (progress) => {
356
+ switch (progress.stage) {
357
+ case 'discovery':
358
+ spinner.color = 'cyan';
359
+ spinner.text = chalk.cyan.bold(`${figures.info} ${progress.message}`);
360
+ break;
361
+ case 'file-scan':
362
+ spinner.color = 'cyan';
363
+ spinner.text = chalk.cyan(`${figures.arrowRight} ${progress.message}: ${chalk.white.bold(path.basename(progress.fileName))}`);
364
+ break;
365
+ case 'parsing':
366
+ spinner.color = 'blue';
367
+ spinner.text = chalk.blue(`${figures.play} Parsing Solidity AST... ${chalk.gray('[Building syntax tree]')}`);
368
+ break;
369
+ case 'detecting':
370
+ detectorCount++;
371
+ const progress_percent = Math.round((progress.current / progress.total) * 100);
372
+ const progressBar = '█'.repeat(Math.floor(progress_percent / 5)) + '░'.repeat(20 - Math.floor(progress_percent / 5));
373
+
374
+ // Color code by detector type
375
+ let color = chalk.yellow;
376
+ if (progress.detector.includes('Taint') || progress.detector.includes('Reentrancy')) {
377
+ color = chalk.red;
378
+ } else if (progress.detector.includes('Access') || progress.detector.includes('Uninitialized')) {
379
+ color = chalk.magenta;
380
+ }
381
+
382
+ spinner.color = 'yellow';
383
+ spinner.text = color(`${figures.pointer} [${progress.current}/${progress.total}] ${progress.detector} ${chalk.gray(progressBar)} ${chalk.white(progress_percent + '%')}`);
384
+ break;
385
+ case 'analyzing':
386
+ spinner.color = 'green';
387
+ spinner.text = chalk.green(`${figures.tick} ${progress.message} ${chalk.gray('[Compiling results]')}`);
388
+ break;
389
+ case 'solodit-enrichment':
390
+ spinner.color = 'cyan';
391
+ spinner.text = chalk.cyan(`${figures.info} ${progress.message} ${chalk.gray('[Solodit API]')}`);
392
+ break;
393
+ default:
394
+ spinner.text = progress.message;
395
+ }
396
+ };
397
+
398
+ // Initialize scanner with progress callback
399
+ const scanner = new Web3CRITScanner({
400
+ verbose: options.verbose,
401
+ severity: options.severity,
402
+ outputFormat: options.format,
403
+ exploitDriven: options.exploitDriven || options.immunefiOnly || options.production,
404
+ immunefiOnly: options.immunefiOnly || options.production || false,
405
+ productionMode: options.production || false,
406
+ pocValidate: options.pocValidate || options.production || false,
407
+ pocRequirePass: options.pocRequirePass || options.production || false,
408
+ pocMode: options.pocMode || 'test',
409
+ foundryRoot: options.foundryRoot || null,
410
+ forkUrl: options.forkUrl || null,
411
+ pocKeepTemp: options.pocKeepTemp || false,
412
+ // Solodit integration options
413
+ soloditEnrich: options.soloditEnrich || false,
414
+ soloditApiKey: options.soloditApiKey || null,
415
+ soloditMinConfidence: options.soloditMinConfidence || 0.3,
416
+ onProgress: onProgress
417
+ });
418
+
419
+ spinner.text = chalk.cyan('Analyzing target...');
420
+
421
+ // Determine if path is file or directory
422
+ const stats = await fs.stat(targetPath);
423
+ let results;
424
+
425
+ if (stats.isDirectory()) {
426
+ spinner.text = chalk.cyan(`${figures.info} Scanning directory: ${targetPath}`);
427
+ results = await scanner.scanDirectory(targetPath);
428
+ } else if (stats.isFile()) {
429
+ spinner.text = chalk.cyan(`${figures.info} Scanning file: ${targetPath}`);
430
+ results = await scanner.scanFile(targetPath);
431
+ } else {
432
+ throw new Error('Invalid path: must be a file or directory');
433
+ }
434
+
435
+ const scanResults = scanner.getFindings();
436
+ const duration = ((Date.now() - startTime) / 1000).toFixed(2);
437
+
438
+ // Success message with summary
439
+ const criticalCount = scanResults.stats.critical;
440
+ const highCount = scanResults.stats.high;
441
+ const totalCount = scanResults.stats.totalFindings;
442
+
443
+ let successMsg = chalk.green.bold('Scan completed! ');
444
+ const modeLabel = options.production ? chalk.magenta('[PRODUCTION] ') : '';
445
+
446
+ if (criticalCount > 0) {
447
+ successMsg += modeLabel + chalk.red.bold(`Found ${totalCount} issues (${criticalCount} CRITICAL) `) + chalk.gray(`in ${duration}s`);
448
+ } else if (highCount > 0) {
449
+ successMsg += modeLabel + chalk.yellow.bold(`Found ${totalCount} issues (${highCount} HIGH) `) + chalk.gray(`in ${duration}s`);
450
+ } else if (totalCount > 0) {
451
+ successMsg += modeLabel + chalk.blue(`Found ${totalCount} issues `) + chalk.gray(`in ${duration}s`);
452
+ } else {
453
+ successMsg += modeLabel + chalk.green('No vulnerabilities detected! ') + chalk.gray(`in ${duration}s`);
454
+ }
455
+
456
+ // In production mode, add PoC validation stats
457
+ if (options.production && scanResults.stats.pocValidated !== undefined) {
458
+ console.log(chalk.gray(`\n PoC Validation: ${scanResults.stats.pocValidated} validated, ${scanResults.stats.pocDropped || 0} dropped (no proven impact)`));
459
+ }
460
+
461
+ spinner.succeed(successMsg);
462
+
463
+ // Display results
464
+ if (options.format === 'json') {
465
+ displayJsonResults(scanResults);
466
+ } else {
467
+ displayTableResults(scanResults);
468
+ }
469
+
470
+ // Save to file if requested
471
+ if (options.output) {
472
+ const outputFormat = options.format === 'table' ? 'markdown' : options.format;
473
+ await saveResults(scanResults, options.output, outputFormat);
474
+ }
475
+
476
+ // Exit with error code if critical/high vulnerabilities found
477
+ if (scanResults.stats.critical > 0 || scanResults.stats.high > 0) {
478
+ process.exit(1);
479
+ }
480
+
481
+ } catch (error) {
482
+ spinner.fail('Scan failed!');
483
+ console.error(chalk.red(`\nError: ${error.message}`));
484
+ if (options.verbose) {
485
+ console.error(error.stack);
486
+ }
487
+ process.exit(1);
488
+ }
489
+ });
490
+
491
+ // Info command
492
+ program
493
+ .command('info')
494
+ .description('Display information about available detectors')
495
+ .action(() => {
496
+ displayBanner();
497
+
498
+ // Solodit integration info
499
+ console.log(chalk.bold.cyan('Solodit Integration:\n'));
500
+ console.log(chalk.white(' Correlate findings with real-world exploits from the Solodit database.'));
501
+ console.log(chalk.gray(' - Tags findings as "Confirmed in Wild", "Confirmed Pattern", or "Theoretical"'));
502
+ console.log(chalk.gray(' - Shows matching exploits with loss amounts and references'));
503
+ console.log(chalk.gray(' - Works offline with built-in knowledge base of 15+ major exploits'));
504
+ console.log(chalk.gray(' - Online mode with API key for full Solodit database access\n'));
505
+ console.log(chalk.white(' Usage: web3crit scan <path> --solodit-enrich [--solodit-api-key <key>]\n'));
506
+
507
+ console.log(chalk.bold('Available Vulnerability Detectors:\n'));
508
+
509
+ const detectors = [
510
+ // Critical - Production-grade for multi-million dollar contracts
511
+ { name: 'Flash Loan Attacks', severity: 'CRITICAL', description: 'Detects balance-based logic and price manipulation vulnerabilities' },
512
+ { name: 'Signature Replay Attacks', severity: 'CRITICAL', description: 'Finds missing nonce/chainId in signature verification' },
513
+ { name: 'Reentrancy Vulnerability', severity: 'CRITICAL', description: 'Detects reentrancy attack patterns' },
514
+ { name: 'Taint Analysis', severity: 'CRITICAL', description: 'Tracks user-controlled data flow to dangerous operations' },
515
+ { name: 'Uninitialized Storage Pointers', severity: 'CRITICAL', description: 'Finds uninitialized local storage variables' },
516
+ { name: 'Access Control', severity: 'CRITICAL', description: 'Finds missing access control modifiers' },
517
+ { name: 'Delegatecall Vulnerabilities', severity: 'CRITICAL', description: 'Detects unsafe delegatecall usage' },
518
+ { name: 'Unprotected Selfdestruct', severity: 'CRITICAL', description: 'Finds unprotected contract destruction' },
519
+ { name: 'Price Feed Manipulation', severity: 'CRITICAL', description: 'Detects oracle manipulation risks' },
520
+ // High - Advanced vulnerability detection
521
+ { name: 'Precision Loss', severity: 'HIGH', description: 'Finds division before multiplication and rounding errors' },
522
+ { name: 'Gas Griefing & DoS', severity: 'HIGH', description: 'Detects unbounded loops and denial of service vectors' },
523
+ { name: 'Integer Overflow/Underflow', severity: 'HIGH', description: 'Finds unchecked arithmetic operations' },
524
+ { name: 'Unchecked External Calls', severity: 'HIGH', description: 'Detects unchecked call return values' },
525
+ { name: 'Front-Running', severity: 'HIGH', description: 'Identifies transaction ordering vulnerabilities' },
526
+ { name: 'tx.origin Authentication', severity: 'HIGH', description: 'Detects dangerous use of tx.origin' },
527
+ { name: 'Shadowing', severity: 'HIGH', description: 'Finds shadowed variables and functions in inheritance' },
528
+ { name: 'Logic Bugs', severity: 'HIGH', description: 'Detects common logic errors' },
529
+ // Medium - Code quality and best practices
530
+ { name: 'Timestamp Dependence', severity: 'MEDIUM', description: 'Finds risky timestamp usage' },
531
+ { name: 'Assembly Usage', severity: 'MEDIUM', description: 'Detects inline assembly that bypasses safety checks' },
532
+ { name: 'Inheritance Order', severity: 'MEDIUM', description: 'Checks for incorrect inheritance patterns' },
533
+ // Low/Info - Optimization and maintainability
534
+ { name: 'Missing Events', severity: 'LOW', description: 'Finds state changes without event emissions' },
535
+ { name: 'Dead Code', severity: 'INFO', description: 'Detects unused functions and variables' },
536
+ { name: 'State Mutability', severity: 'INFO', description: 'Finds functions that should be view/pure' }
537
+ ];
538
+
539
+ detectors.forEach(detector => {
540
+ const color = getSeverityColor(detector.severity);
541
+ console.log(color(`${figures.pointer} ${detector.name} [${detector.severity}]`));
542
+ console.log(chalk.gray(` ${detector.description}\n`));
543
+ });
544
+ });
545
+
546
+ program.parse();