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.
- package/README.md +685 -0
- package/bin/web3crit +10 -0
- package/package.json +59 -0
- package/src/analyzers/control-flow.js +256 -0
- package/src/analyzers/data-flow.js +720 -0
- package/src/analyzers/exploit-chain.js +751 -0
- package/src/analyzers/immunefi-classifier.js +515 -0
- package/src/analyzers/poc-validator.js +396 -0
- package/src/analyzers/solodit-enricher.js +1122 -0
- package/src/cli.js +546 -0
- package/src/detectors/access-control-enhanced.js +458 -0
- package/src/detectors/base-detector.js +213 -0
- package/src/detectors/callback-reentrancy.js +362 -0
- package/src/detectors/cross-contract-reentrancy.js +697 -0
- package/src/detectors/delegatecall.js +167 -0
- package/src/detectors/deprecated-functions.js +62 -0
- package/src/detectors/flash-loan.js +408 -0
- package/src/detectors/frontrunning.js +553 -0
- package/src/detectors/gas-griefing.js +701 -0
- package/src/detectors/governance-attacks.js +366 -0
- package/src/detectors/integer-overflow.js +487 -0
- package/src/detectors/oracle-manipulation.js +524 -0
- package/src/detectors/permit-exploits.js +368 -0
- package/src/detectors/precision-loss.js +408 -0
- package/src/detectors/price-manipulation-advanced.js +548 -0
- package/src/detectors/proxy-vulnerabilities.js +651 -0
- package/src/detectors/readonly-reentrancy.js +473 -0
- package/src/detectors/rebasing-token-vault.js +416 -0
- package/src/detectors/reentrancy-enhanced.js +359 -0
- package/src/detectors/selfdestruct.js +259 -0
- package/src/detectors/share-manipulation.js +412 -0
- package/src/detectors/signature-replay.js +409 -0
- package/src/detectors/storage-collision.js +446 -0
- package/src/detectors/timestamp-dependence.js +494 -0
- package/src/detectors/toctou.js +427 -0
- package/src/detectors/token-standard-compliance.js +465 -0
- package/src/detectors/unchecked-call.js +214 -0
- package/src/detectors/vault-inflation.js +421 -0
- package/src/index.js +42 -0
- package/src/package-lock.json +2874 -0
- package/src/package.json +39 -0
- 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();
|