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
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
const parser = require('@solidity-parser/parser');
|
|
2
|
+
const fs = require('fs').promises;
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
// Import enhanced analyzers
|
|
6
|
+
const ControlFlowAnalyzer = require('./analyzers/control-flow');
|
|
7
|
+
const DataFlowAnalyzer = require('./analyzers/data-flow');
|
|
8
|
+
|
|
9
|
+
// Import Immunefi classifier and exploit chain modeler
|
|
10
|
+
const ImmunefiClassifier = require('./analyzers/immunefi-classifier');
|
|
11
|
+
const ExploitChainModeler = require('./analyzers/exploit-chain');
|
|
12
|
+
const PocValidator = require('./analyzers/poc-validator');
|
|
13
|
+
const SoloditEnricher = require('./analyzers/solodit-enricher');
|
|
14
|
+
|
|
15
|
+
// Import enhanced detectors only
|
|
16
|
+
const ReentrancyEnhancedDetector = require('./detectors/reentrancy-enhanced');
|
|
17
|
+
const AccessControlEnhancedDetector = require('./detectors/access-control-enhanced');
|
|
18
|
+
|
|
19
|
+
// Keep critical detectors that don't need enhancement
|
|
20
|
+
const UncheckedCallDetector = require('./detectors/unchecked-call');
|
|
21
|
+
const DelegateCallDetector = require('./detectors/delegatecall');
|
|
22
|
+
const UnprotectedSelfdestruct = require('./detectors/selfdestruct');
|
|
23
|
+
|
|
24
|
+
// Advanced Web3 vulnerability detectors
|
|
25
|
+
const IntegerOverflowDetector = require('./detectors/integer-overflow');
|
|
26
|
+
const FlashLoanDetector = require('./detectors/flash-loan');
|
|
27
|
+
const OracleManipulationDetector = require('./detectors/oracle-manipulation');
|
|
28
|
+
const FrontRunningDetector = require('./detectors/frontrunning');
|
|
29
|
+
const TimestampDependenceDetector = require('./detectors/timestamp-dependence');
|
|
30
|
+
const GasGriefingDetector = require('./detectors/gas-griefing');
|
|
31
|
+
const DeprecatedFunctionsDetector = require('./detectors/deprecated-functions');
|
|
32
|
+
|
|
33
|
+
// High-value TVL contract detectors
|
|
34
|
+
const ProxyVulnerabilitiesDetector = require('./detectors/proxy-vulnerabilities');
|
|
35
|
+
const SignatureReplayDetector = require('./detectors/signature-replay');
|
|
36
|
+
const CrossContractReentrancyDetector = require('./detectors/cross-contract-reentrancy');
|
|
37
|
+
const TokenStandardComplianceDetector = require('./detectors/token-standard-compliance');
|
|
38
|
+
const TOCTOUDetector = require('./detectors/toctou');
|
|
39
|
+
|
|
40
|
+
// Immunefi-tier detectors (NEW)
|
|
41
|
+
const GovernanceAttackDetector = require('./detectors/governance-attacks');
|
|
42
|
+
const ShareManipulationDetector = require('./detectors/share-manipulation');
|
|
43
|
+
const PermitExploitsDetector = require('./detectors/permit-exploits');
|
|
44
|
+
|
|
45
|
+
// Advanced high-TVL detectors (Production-grade)
|
|
46
|
+
const CallbackReentrancyDetector = require('./detectors/callback-reentrancy');
|
|
47
|
+
const RebasingTokenVaultDetector = require('./detectors/rebasing-token-vault');
|
|
48
|
+
const AdvancedPriceManipulationDetector = require('./detectors/price-manipulation-advanced');
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Enhanced Web3CRIT Scanner v6.0 (Production-Grade)
|
|
52
|
+
* Top-tier exploit-driven scanner for high TVL contracts ($5M+)
|
|
53
|
+
* Only reports findings that qualify for Immunefi High/Critical payouts
|
|
54
|
+
*
|
|
55
|
+
* PRODUCTION MODE RULES (--production flag):
|
|
56
|
+
* - HIGH/CRITICAL only emitted if Foundry PoC compiles, executes, and proves impact
|
|
57
|
+
* - Impact = fund drain, unauthorized transfer, role takeover, invariant break, or permanent DoS
|
|
58
|
+
* - If PoC fails to compile, run, or show impact → discarded silently
|
|
59
|
+
* - Severity derived from observed PoC results, not heuristics
|
|
60
|
+
*
|
|
61
|
+
* Features:
|
|
62
|
+
* - Control flow and data flow analysis
|
|
63
|
+
* - Immunefi severity classification
|
|
64
|
+
* - Concrete exploit chain modeling
|
|
65
|
+
* - Assumes adversarial capabilities (flash loans, MEV, malicious contracts)
|
|
66
|
+
* - Foundry PoC execution with impact verification
|
|
67
|
+
*/
|
|
68
|
+
class Web3CRITScannerEnhanced {
|
|
69
|
+
constructor(options = {}) {
|
|
70
|
+
this.options = {
|
|
71
|
+
verbose: options.verbose || false,
|
|
72
|
+
severity: options.severity || 'all',
|
|
73
|
+
outputFormat: options.outputFormat || 'json',
|
|
74
|
+
onProgress: options.onProgress || null,
|
|
75
|
+
// Exploit-driven mode (only Immunefi High/Critical)
|
|
76
|
+
// Default OFF for backwards compatibility, use --immunefi flag to enable
|
|
77
|
+
exploitDriven: options.exploitDriven || false,
|
|
78
|
+
immunefiOnly: options.immunefiOnly || false, // Strict Immunefi mode
|
|
79
|
+
// PRODUCTION MODE: Strict PoC gating for HIGH/CRITICAL
|
|
80
|
+
productionMode: options.productionMode || false,
|
|
81
|
+
// Foundry PoC validation
|
|
82
|
+
pocValidate: options.pocValidate || false,
|
|
83
|
+
pocRequirePass: options.pocRequirePass || false,
|
|
84
|
+
pocMode: options.pocMode || 'test', // 'test' or 'build'
|
|
85
|
+
foundryRoot: options.foundryRoot || null,
|
|
86
|
+
pocKeepTemp: options.pocKeepTemp || false,
|
|
87
|
+
forkUrl: options.forkUrl || null, // RPC URL for fork testing
|
|
88
|
+
...options
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// In production mode, automatically enable exploit-driven and poc validation
|
|
92
|
+
if (this.options.productionMode) {
|
|
93
|
+
this.options.exploitDriven = true;
|
|
94
|
+
this.options.immunefiOnly = true;
|
|
95
|
+
this.options.pocValidate = true;
|
|
96
|
+
this.options.pocRequirePass = true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Initialize Immunefi classifier
|
|
100
|
+
this.immunefiClassifier = new ImmunefiClassifier();
|
|
101
|
+
this.pocValidator = new PocValidator({
|
|
102
|
+
enabled: this.options.pocValidate || this.options.productionMode,
|
|
103
|
+
requirePass: this.options.pocRequirePass || this.options.productionMode,
|
|
104
|
+
productionMode: this.options.productionMode || false,
|
|
105
|
+
mode: this.options.pocMode || 'test',
|
|
106
|
+
foundryRoot: this.options.foundryRoot || null,
|
|
107
|
+
keepTemp: this.options.pocKeepTemp || false,
|
|
108
|
+
forkUrl: this.options.forkUrl || null
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Initialize Solodit enricher for real-world vulnerability correlation
|
|
112
|
+
this.soloditEnricher = new SoloditEnricher({
|
|
113
|
+
apiKey: this.options.soloditApiKey || process.env.SOLODIT_API_KEY,
|
|
114
|
+
baseUrl: this.options.soloditBaseUrl || process.env.SOLODIT_API_URL,
|
|
115
|
+
enabled: this.options.soloditEnrich || false,
|
|
116
|
+
verbose: this.options.verbose,
|
|
117
|
+
timeout: this.options.soloditTimeout || 10000,
|
|
118
|
+
minConfidenceThreshold: this.options.soloditMinConfidence || 0.3
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Initialize enhanced detectors - ordered by Immunefi severity
|
|
122
|
+
// Production-grade for high-TVL DeFi targets
|
|
123
|
+
this.detectors = [
|
|
124
|
+
// CRITICAL: Direct theft of funds (highest priority)
|
|
125
|
+
new ReentrancyEnhancedDetector(),
|
|
126
|
+
new CallbackReentrancyDetector(), // NEW: ERC777/ERC1155 callback reentrancy
|
|
127
|
+
new AccessControlEnhancedDetector(),
|
|
128
|
+
new DelegateCallDetector(),
|
|
129
|
+
new UnprotectedSelfdestruct(),
|
|
130
|
+
new ProxyVulnerabilitiesDetector(),
|
|
131
|
+
new CrossContractReentrancyDetector(),
|
|
132
|
+
|
|
133
|
+
// CRITICAL: Protocol insolvency / Governance takeover
|
|
134
|
+
new GovernanceAttackDetector(),
|
|
135
|
+
new ShareManipulationDetector(),
|
|
136
|
+
new RebasingTokenVaultDetector(), // NEW: Aave/Lido rebasing token issues
|
|
137
|
+
new FlashLoanDetector(),
|
|
138
|
+
new OracleManipulationDetector(),
|
|
139
|
+
new AdvancedPriceManipulationDetector(), // NEW: LP token, Curve, Balancer manipulation
|
|
140
|
+
|
|
141
|
+
// HIGH: Theft of yield / Manipulation profit
|
|
142
|
+
new SignatureReplayDetector(),
|
|
143
|
+
new PermitExploitsDetector(),
|
|
144
|
+
new UncheckedCallDetector(),
|
|
145
|
+
new TOCTOUDetector(),
|
|
146
|
+
|
|
147
|
+
// HIGH: Price manipulation
|
|
148
|
+
new FrontRunningDetector(),
|
|
149
|
+
new IntegerOverflowDetector(),
|
|
150
|
+
|
|
151
|
+
// MEDIUM: Conditional inclusion (filtered in exploit-driven mode)
|
|
152
|
+
new TimestampDependenceDetector(),
|
|
153
|
+
new GasGriefingDetector(),
|
|
154
|
+
new DeprecatedFunctionsDetector(),
|
|
155
|
+
new TokenStandardComplianceDetector()
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
this.findings = [];
|
|
159
|
+
this.stats = {
|
|
160
|
+
filesScanned: 0,
|
|
161
|
+
totalFindings: 0,
|
|
162
|
+
critical: 0,
|
|
163
|
+
high: 0,
|
|
164
|
+
medium: 0,
|
|
165
|
+
low: 0,
|
|
166
|
+
info: 0,
|
|
167
|
+
exploitable: 0
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async scanFile(filePath) {
|
|
172
|
+
try {
|
|
173
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
174
|
+
return await this.scanSource(content, filePath);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
throw new Error(`Failed to read file ${filePath}: ${error.message}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async scanSource(sourceCode, fileName = 'contract.sol') {
|
|
181
|
+
this.stats.filesScanned++;
|
|
182
|
+
|
|
183
|
+
// Progress: Parsing
|
|
184
|
+
if (this.options.onProgress) {
|
|
185
|
+
this.options.onProgress({
|
|
186
|
+
stage: 'parsing',
|
|
187
|
+
message: 'Parsing Solidity code...',
|
|
188
|
+
fileName
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let ast;
|
|
193
|
+
try {
|
|
194
|
+
ast = parser.parse(sourceCode, {
|
|
195
|
+
loc: true,
|
|
196
|
+
range: true,
|
|
197
|
+
tolerant: true
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
throw new Error(`Failed to parse Solidity code: ${error.message}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Progress: Building control flow graph
|
|
204
|
+
if (this.options.onProgress) {
|
|
205
|
+
this.options.onProgress({
|
|
206
|
+
stage: 'analyzing',
|
|
207
|
+
message: 'Building control flow graph...',
|
|
208
|
+
fileName
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Build control flow graph
|
|
213
|
+
const cfgAnalyzer = new ControlFlowAnalyzer();
|
|
214
|
+
const cfg = cfgAnalyzer.analyze(ast, sourceCode);
|
|
215
|
+
|
|
216
|
+
// Progress: Data flow analysis
|
|
217
|
+
if (this.options.onProgress) {
|
|
218
|
+
this.options.onProgress({
|
|
219
|
+
stage: 'analyzing',
|
|
220
|
+
message: 'Performing data flow analysis...',
|
|
221
|
+
fileName
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Perform data flow analysis
|
|
226
|
+
const dataFlowAnalyzer = new DataFlowAnalyzer(cfg);
|
|
227
|
+
const dataFlow = dataFlowAnalyzer.analyze();
|
|
228
|
+
|
|
229
|
+
const contractFindings = [];
|
|
230
|
+
const totalDetectors = this.detectors.length;
|
|
231
|
+
|
|
232
|
+
// Run enhanced detectors with CFG and data flow info
|
|
233
|
+
for (let i = 0; i < this.detectors.length; i++) {
|
|
234
|
+
const detector = this.detectors[i];
|
|
235
|
+
|
|
236
|
+
// Progress: Running detector
|
|
237
|
+
if (this.options.onProgress) {
|
|
238
|
+
this.options.onProgress({
|
|
239
|
+
stage: 'detecting',
|
|
240
|
+
message: `Running detector: ${detector.name}`,
|
|
241
|
+
detector: detector.name,
|
|
242
|
+
current: i + 1,
|
|
243
|
+
total: totalDetectors,
|
|
244
|
+
fileName
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
// Pass CFG and data flow to enhanced detectors
|
|
250
|
+
const detectorFindings = await detector.detect(ast, sourceCode, fileName, cfg, dataFlow);
|
|
251
|
+
contractFindings.push(...detectorFindings);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (this.options.verbose) {
|
|
254
|
+
console.error(`Detector ${detector.name} failed: ${error.message}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Progress: Analyzing results
|
|
260
|
+
if (this.options.onProgress) {
|
|
261
|
+
this.options.onProgress({
|
|
262
|
+
stage: 'filtering',
|
|
263
|
+
message: 'Classifying for Immunefi payout eligibility...',
|
|
264
|
+
fileName
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Apply exploit-driven filtering with Immunefi classification
|
|
269
|
+
let filteredFindings;
|
|
270
|
+
if (this.options.exploitDriven || this.options.immunefiOnly || this.options.productionMode) {
|
|
271
|
+
// Model exploit chains
|
|
272
|
+
const exploitModeler = new ExploitChainModeler(cfg, dataFlow);
|
|
273
|
+
const withExploitChains = exploitModeler.modelExploitChains(contractFindings);
|
|
274
|
+
|
|
275
|
+
// Classify for Immunefi payouts
|
|
276
|
+
filteredFindings = this.immunefiClassifier.filterToPayoutLevel(withExploitChains);
|
|
277
|
+
|
|
278
|
+
// In strict immunefi mode, only keep Critical/High
|
|
279
|
+
if (this.options.immunefiOnly || this.options.productionMode) {
|
|
280
|
+
filteredFindings = filteredFindings.filter(f =>
|
|
281
|
+
f.payoutTier === 'Critical' || f.payoutTier === 'High'
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// PRODUCTION MODE: Gate ALL HIGH/CRITICAL behind PoC execution with impact proof
|
|
286
|
+
if (this.options.productionMode) {
|
|
287
|
+
// Progress: PoC validation
|
|
288
|
+
if (this.options.onProgress) {
|
|
289
|
+
this.options.onProgress({
|
|
290
|
+
stage: 'poc-validation',
|
|
291
|
+
message: 'Validating Foundry PoCs (production mode)...',
|
|
292
|
+
fileName
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const validation = this.pocValidator.validateForProduction(filteredFindings, {
|
|
297
|
+
scanTargetPath: fileName
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
filteredFindings = validation.findings;
|
|
301
|
+
|
|
302
|
+
// Track stats
|
|
303
|
+
this.stats.pocValidated = validation.stats.validatedHighSeverity;
|
|
304
|
+
this.stats.pocDropped = validation.stats.droppedCount;
|
|
305
|
+
this.stats.productionMode = true;
|
|
306
|
+
|
|
307
|
+
// In production mode, only keep findings with proven impact
|
|
308
|
+
filteredFindings = filteredFindings.filter(f => {
|
|
309
|
+
if (['CRITICAL', 'HIGH'].includes(f.severity)) {
|
|
310
|
+
// Must have PoC validation that passed with proven impact
|
|
311
|
+
return f.pocValidation?.ok === true && f.provenImpact;
|
|
312
|
+
}
|
|
313
|
+
// Lower severity findings are still included
|
|
314
|
+
return true;
|
|
315
|
+
});
|
|
316
|
+
} else if (this.options.pocValidate) {
|
|
317
|
+
// Non-production PoC validation (optional)
|
|
318
|
+
const validation = this.pocValidator.validateFindings(filteredFindings, { scanTargetPath: fileName });
|
|
319
|
+
filteredFindings = validation.kept;
|
|
320
|
+
// Track dropped findings for reporting
|
|
321
|
+
this.stats.pocDropped = (this.stats.pocDropped || 0) + validation.dropped.length;
|
|
322
|
+
this.stats.pocKept = (this.stats.pocKept || 0) + validation.kept.length;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Solodit enrichment: correlate with real-world exploits
|
|
326
|
+
if (this.soloditEnricher.enabled) {
|
|
327
|
+
// Progress: Solodit enrichment
|
|
328
|
+
if (this.options.onProgress) {
|
|
329
|
+
this.options.onProgress({
|
|
330
|
+
stage: 'solodit-enrichment',
|
|
331
|
+
message: 'Correlating with Solodit vulnerability database...',
|
|
332
|
+
fileName
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
filteredFindings = await this.soloditEnricher.enrichFindings(filteredFindings);
|
|
337
|
+
|
|
338
|
+
// Track Solodit stats
|
|
339
|
+
const soloditStats = this.soloditEnricher.getStats();
|
|
340
|
+
this.stats.soloditEnriched = soloditStats.findingsEnriched;
|
|
341
|
+
this.stats.soloditConfirmedInWild = soloditStats.confirmedInWild;
|
|
342
|
+
this.stats.soloditTheoretical = soloditStats.theoretical;
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
// Legacy filtering mode
|
|
346
|
+
filteredFindings = this.filterFindings(contractFindings);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Update statistics
|
|
350
|
+
this.updateStats(filteredFindings);
|
|
351
|
+
this.findings.push(...filteredFindings);
|
|
352
|
+
|
|
353
|
+
return filteredFindings;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async sleep(ms) {
|
|
357
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async scanDirectory(dirPath) {
|
|
361
|
+
const files = await this.getSolidityFiles(dirPath);
|
|
362
|
+
const allFindings = [];
|
|
363
|
+
const totalFiles = files.length;
|
|
364
|
+
|
|
365
|
+
// Progress: Found files
|
|
366
|
+
if (this.options.onProgress) {
|
|
367
|
+
this.options.onProgress({
|
|
368
|
+
stage: 'discovery',
|
|
369
|
+
message: `Found ${totalFiles} Solidity file(s)`,
|
|
370
|
+
totalFiles
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
for (let i = 0; i < files.length; i++) {
|
|
375
|
+
const file = files[i];
|
|
376
|
+
|
|
377
|
+
// Progress: Scanning file
|
|
378
|
+
if (this.options.onProgress) {
|
|
379
|
+
this.options.onProgress({
|
|
380
|
+
stage: 'file-scan',
|
|
381
|
+
message: `Scanning file ${i + 1}/${totalFiles}`,
|
|
382
|
+
fileName: file,
|
|
383
|
+
currentFile: i + 1,
|
|
384
|
+
totalFiles
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const findings = await this.scanFile(file);
|
|
390
|
+
allFindings.push(...findings);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
if (this.options.verbose) {
|
|
393
|
+
console.error(`Error scanning ${file}: ${error.message}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return allFindings;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async getSolidityFiles(dirPath) {
|
|
402
|
+
const files = [];
|
|
403
|
+
|
|
404
|
+
async function traverse(currentPath) {
|
|
405
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
406
|
+
|
|
407
|
+
for (const entry of entries) {
|
|
408
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
409
|
+
|
|
410
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
411
|
+
await traverse(fullPath);
|
|
412
|
+
} else if (entry.isFile() && entry.name.endsWith('.sol')) {
|
|
413
|
+
files.push(fullPath);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
await traverse(dirPath);
|
|
419
|
+
return files;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Filter findings by severity, exploitability, and confidence
|
|
424
|
+
* HARDENED: Aggressive filtering to report only real, exploitable vulnerabilities
|
|
425
|
+
* Optimized for precision over recall (fewer false positives)
|
|
426
|
+
*/
|
|
427
|
+
filterFindings(findings) {
|
|
428
|
+
// Filter by severity level
|
|
429
|
+
let filtered = this.filterBySeverity(findings);
|
|
430
|
+
|
|
431
|
+
// Deduplicate findings by location (same file:line)
|
|
432
|
+
const seenLocations = new Set();
|
|
433
|
+
filtered = filtered.filter(finding => {
|
|
434
|
+
const locationKey = `${finding.fileName}:${finding.line}:${finding.title}`;
|
|
435
|
+
if (seenLocations.has(locationKey)) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
seenLocations.add(locationKey);
|
|
439
|
+
return true;
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Apply exploitability-based filtering (HARDENED)
|
|
443
|
+
filtered = filtered.filter(finding => {
|
|
444
|
+
// Always report high-confidence findings with PoC
|
|
445
|
+
if (finding.isHighConfidence === true) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// CRITICAL severity: require exploitable flag AND (HIGH confidence OR score >= 70)
|
|
450
|
+
if (finding.severity === 'CRITICAL') {
|
|
451
|
+
if (finding.exploitable !== true) return false;
|
|
452
|
+
if (finding.confidence === 'HIGH') return true;
|
|
453
|
+
if (finding.exploitabilityScore >= 70) return true;
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// HIGH severity: require HIGH confidence OR (MEDIUM confidence AND exploitable AND score >= 65)
|
|
458
|
+
if (finding.severity === 'HIGH') {
|
|
459
|
+
if (finding.confidence === 'HIGH' && finding.exploitable === true) return true;
|
|
460
|
+
if (finding.confidence === 'MEDIUM' && finding.exploitable === true && finding.exploitabilityScore >= 65) return true;
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// MEDIUM severity: require HIGH confidence AND exploitable AND score >= 70
|
|
465
|
+
if (finding.severity === 'MEDIUM') {
|
|
466
|
+
if (finding.confidence === 'HIGH' && finding.exploitable === true && finding.exploitabilityScore >= 70) return true;
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// LOW/INFO: Only include with very high score (rare)
|
|
471
|
+
if (finding.severity === 'LOW' || finding.severity === 'INFO') {
|
|
472
|
+
return finding.exploitabilityScore >= 80 && finding.confidence === 'HIGH';
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Default: filter out
|
|
476
|
+
return false;
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// Sort by exploitability score (highest first)
|
|
480
|
+
filtered.sort((a, b) => {
|
|
481
|
+
// First by severity
|
|
482
|
+
const severityOrder = { 'CRITICAL': 5, 'HIGH': 4, 'MEDIUM': 3, 'LOW': 2, 'INFO': 1 };
|
|
483
|
+
const severityDiff = (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
|
|
484
|
+
if (severityDiff !== 0) return severityDiff;
|
|
485
|
+
|
|
486
|
+
// Then by confidence
|
|
487
|
+
const confOrder = { 'HIGH': 3, 'MEDIUM': 2, 'LOW': 1 };
|
|
488
|
+
const confDiff = (confOrder[b.confidence] || 0) - (confOrder[a.confidence] || 0);
|
|
489
|
+
if (confDiff !== 0) return confDiff;
|
|
490
|
+
|
|
491
|
+
// Then by exploitability score
|
|
492
|
+
return (b.exploitabilityScore || 0) - (a.exploitabilityScore || 0);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
return filtered;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
filterBySeverity(findings) {
|
|
499
|
+
if (this.options.severity === 'all') {
|
|
500
|
+
return findings;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const severityLevels = {
|
|
504
|
+
critical: 5,
|
|
505
|
+
high: 4,
|
|
506
|
+
medium: 3,
|
|
507
|
+
low: 2,
|
|
508
|
+
info: 1
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const minLevel = severityLevels[this.options.severity] || 0;
|
|
512
|
+
|
|
513
|
+
return findings.filter(f =>
|
|
514
|
+
severityLevels[f.severity.toLowerCase()] >= minLevel
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
updateStats(findings) {
|
|
519
|
+
findings.forEach(finding => {
|
|
520
|
+
this.stats.totalFindings++;
|
|
521
|
+
const severity = finding.severity.toLowerCase();
|
|
522
|
+
if (this.stats[severity] !== undefined) {
|
|
523
|
+
this.stats[severity]++;
|
|
524
|
+
}
|
|
525
|
+
if (finding.exploitable === true) {
|
|
526
|
+
this.stats.exploitable++;
|
|
527
|
+
}
|
|
528
|
+
// Track Immunefi payout eligibility
|
|
529
|
+
if (finding.payoutTier === 'Critical') {
|
|
530
|
+
this.stats.immunefiCritical = (this.stats.immunefiCritical || 0) + 1;
|
|
531
|
+
} else if (finding.payoutTier === 'High') {
|
|
532
|
+
this.stats.immunefiHigh = (this.stats.immunefiHigh || 0) + 1;
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get Immunefi-focused summary for bug bounty submission
|
|
539
|
+
* Groups findings by payout category with exploit chains
|
|
540
|
+
*/
|
|
541
|
+
getImmunefiReport() {
|
|
542
|
+
const criticalFindings = this.findings.filter(f => f.payoutTier === 'Critical');
|
|
543
|
+
const highFindings = this.findings.filter(f => f.payoutTier === 'High');
|
|
544
|
+
|
|
545
|
+
const formatFinding = (f, index) => ({
|
|
546
|
+
id: index + 1,
|
|
547
|
+
title: f.title,
|
|
548
|
+
severity: f.severity,
|
|
549
|
+
payoutTier: f.payoutTier,
|
|
550
|
+
immunefiCategory: f.immunefiClassification?.category?.name || 'Unknown',
|
|
551
|
+
impact: f.immunefiClassification?.impactDescription || f.description,
|
|
552
|
+
location: f.location,
|
|
553
|
+
file: `${f.fileName}:${f.line}`,
|
|
554
|
+
exploitChain: f.exploitChain ? {
|
|
555
|
+
attackerRequirements: f.exploitChain.requirements,
|
|
556
|
+
steps: f.exploitChain.steps?.map(s => s.action) || [],
|
|
557
|
+
profitMechanism: f.exploitChain.profitPath?.mechanism
|
|
558
|
+
} : null,
|
|
559
|
+
recommendation: f.recommendation,
|
|
560
|
+
poc: f.foundryPoC ? 'Included' : 'Not generated'
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
summary: {
|
|
565
|
+
totalFindings: this.findings.length,
|
|
566
|
+
criticalCount: criticalFindings.length,
|
|
567
|
+
highCount: highFindings.length,
|
|
568
|
+
scanMode: this.options.immunefiOnly ? 'Immunefi Strict' : 'Exploit-Driven'
|
|
569
|
+
},
|
|
570
|
+
criticalFindings: criticalFindings.map(formatFinding),
|
|
571
|
+
highFindings: highFindings.map(formatFinding),
|
|
572
|
+
attackVectors: this.getAttackVectorSummary(),
|
|
573
|
+
generatedAt: new Date().toISOString()
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Get summary by attack vector for Immunefi submission
|
|
579
|
+
*/
|
|
580
|
+
getAttackVectorSummary() {
|
|
581
|
+
const vectors = {};
|
|
582
|
+
for (const finding of this.findings) {
|
|
583
|
+
const vector = finding.attackVector || 'other';
|
|
584
|
+
if (!vectors[vector]) {
|
|
585
|
+
vectors[vector] = { count: 0, critical: 0, high: 0 };
|
|
586
|
+
}
|
|
587
|
+
vectors[vector].count++;
|
|
588
|
+
if (finding.payoutTier === 'Critical') vectors[vector].critical++;
|
|
589
|
+
if (finding.payoutTier === 'High') vectors[vector].high++;
|
|
590
|
+
}
|
|
591
|
+
return vectors;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
getFindings() {
|
|
595
|
+
// Separate by Immunefi payout tier
|
|
596
|
+
const criticalFindings = this.findings.filter(f => f.payoutTier === 'Critical');
|
|
597
|
+
const highFindings = this.findings.filter(f => f.payoutTier === 'High');
|
|
598
|
+
const highConfidenceFindings = this.findings.filter(f => f.isHighConfidence);
|
|
599
|
+
|
|
600
|
+
// Solodit validation status breakdown
|
|
601
|
+
const soloditConfirmedInWild = this.findings.filter(f =>
|
|
602
|
+
f.soloditMetadata?.validationStatus?.status === 'confirmed_in_wild'
|
|
603
|
+
);
|
|
604
|
+
const soloditConfirmedPattern = this.findings.filter(f =>
|
|
605
|
+
f.soloditMetadata?.validationStatus?.status === 'confirmed_pattern'
|
|
606
|
+
);
|
|
607
|
+
const soloditTheoretical = this.findings.filter(f =>
|
|
608
|
+
f.soloditMetadata?.validationStatus?.status === 'theoretical' ||
|
|
609
|
+
!f.soloditMetadata?.matched
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// Build features list
|
|
613
|
+
const features = [
|
|
614
|
+
'Immunefi Severity Classification',
|
|
615
|
+
'Concrete Exploit Chain Modeling',
|
|
616
|
+
'Adversarial Capability Modeling (Flash Loans, MEV)',
|
|
617
|
+
'Control Flow Analysis',
|
|
618
|
+
'Data Flow Analysis',
|
|
619
|
+
'Cross-Function Reentrancy Detection',
|
|
620
|
+
'Cross-Contract Reentrancy Detection',
|
|
621
|
+
'Governance Attack Detection',
|
|
622
|
+
'Vault/Share Manipulation Detection',
|
|
623
|
+
'Permit/Approval Exploit Detection',
|
|
624
|
+
'Modifier Logic Validation',
|
|
625
|
+
'Exploitability Scoring (0-100)',
|
|
626
|
+
'Attack Vector Classification',
|
|
627
|
+
'Foundry PoC Generation',
|
|
628
|
+
'Flash Loan Attack Detection',
|
|
629
|
+
'Front-Running/MEV Protection',
|
|
630
|
+
'Proxy Contract Vulnerabilities (UUPS, Transparent)',
|
|
631
|
+
'Signature Replay Protection',
|
|
632
|
+
'First Depositor Attack Detection'
|
|
633
|
+
];
|
|
634
|
+
|
|
635
|
+
// Add Solodit feature if enabled
|
|
636
|
+
if (this.soloditEnricher.enabled) {
|
|
637
|
+
features.push('Solodit Real-World Vulnerability Correlation');
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return {
|
|
641
|
+
findings: this.findings,
|
|
642
|
+
// Immunefi categorization
|
|
643
|
+
immunefiCritical: criticalFindings,
|
|
644
|
+
immunefiHigh: highFindings,
|
|
645
|
+
highConfidenceFindings: highConfidenceFindings,
|
|
646
|
+
// Solodit validation breakdown
|
|
647
|
+
solodit: this.soloditEnricher.enabled ? {
|
|
648
|
+
confirmedInWild: soloditConfirmedInWild,
|
|
649
|
+
confirmedPattern: soloditConfirmedPattern,
|
|
650
|
+
theoretical: soloditTheoretical,
|
|
651
|
+
stats: this.soloditEnricher.getStats()
|
|
652
|
+
} : null,
|
|
653
|
+
stats: {
|
|
654
|
+
...this.stats,
|
|
655
|
+
// Immunefi stats
|
|
656
|
+
immunefiCritical: criticalFindings.length,
|
|
657
|
+
immunefiHigh: highFindings.length,
|
|
658
|
+
totalPayoutEligible: criticalFindings.length + highFindings.length,
|
|
659
|
+
highConfidence: highConfidenceFindings.length,
|
|
660
|
+
withPoC: this.findings.filter(f => f.foundryPoC).length,
|
|
661
|
+
withExploitChain: this.findings.filter(f => f.exploitChain).length,
|
|
662
|
+
// Solodit stats
|
|
663
|
+
soloditEnabled: this.soloditEnricher.enabled,
|
|
664
|
+
soloditConfirmedInWild: soloditConfirmedInWild.length,
|
|
665
|
+
soloditConfirmedPattern: soloditConfirmedPattern.length,
|
|
666
|
+
soloditTheoretical: soloditTheoretical.length
|
|
667
|
+
},
|
|
668
|
+
analysis: {
|
|
669
|
+
engine: 'exploit-driven',
|
|
670
|
+
version: '6.0.0',
|
|
671
|
+
mode: this.options.immunefiOnly ? 'immunefi-strict' : 'exploit-driven',
|
|
672
|
+
soloditEnabled: this.soloditEnricher.enabled,
|
|
673
|
+
features: features
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Get high-confidence findings with Foundry PoC templates
|
|
680
|
+
* Only returns findings that meet the high-confidence threshold
|
|
681
|
+
*/
|
|
682
|
+
getHighConfidenceFindings() {
|
|
683
|
+
return this.findings.filter(f => f.isHighConfidence && f.foundryPoC);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Generate Foundry test file with all high-confidence PoCs
|
|
688
|
+
* @param {string} contractName - Name for the test file
|
|
689
|
+
* @returns {string} Complete Foundry test file content
|
|
690
|
+
*/
|
|
691
|
+
generateFoundryTestFile(contractName = 'VulnerabilityExploits') {
|
|
692
|
+
const pocFindings = this.getHighConfidenceFindings();
|
|
693
|
+
|
|
694
|
+
if (pocFindings.length === 0) {
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const criticalCount = pocFindings.filter(f => f.payoutTier === 'Critical').length;
|
|
699
|
+
const highCount = pocFindings.filter(f => f.payoutTier === 'High').length;
|
|
700
|
+
|
|
701
|
+
const header = `// SPDX-License-Identifier: MIT
|
|
702
|
+
pragma solidity ^0.8.0;
|
|
703
|
+
|
|
704
|
+
import "forge-std/Test.sol";
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Foundry Proof of Concept Tests
|
|
708
|
+
* Generated by Web3CRIT Scanner v6.0.0 (Exploit-Driven Mode)
|
|
709
|
+
*
|
|
710
|
+
* Immunefi Payout-Eligible Findings: ${pocFindings.length}
|
|
711
|
+
* - Critical: ${criticalCount}
|
|
712
|
+
* - High: ${highCount}
|
|
713
|
+
*
|
|
714
|
+
* To run: forge test --match-contract ${contractName} -vvv
|
|
715
|
+
*/
|
|
716
|
+
`;
|
|
717
|
+
|
|
718
|
+
const contracts = pocFindings.map((finding, index) => {
|
|
719
|
+
const testName = this.sanitizeTestName(finding.title);
|
|
720
|
+
const attackVector = finding.attackVector || 'unknown';
|
|
721
|
+
|
|
722
|
+
return `
|
|
723
|
+
/**
|
|
724
|
+
* Finding #${index + 1}: ${finding.title}
|
|
725
|
+
* Severity: ${finding.severity}
|
|
726
|
+
* Confidence: ${finding.confidence}
|
|
727
|
+
* Exploitability Score: ${finding.exploitabilityScore}/100
|
|
728
|
+
* Attack Vector: ${attackVector}
|
|
729
|
+
* File: ${finding.fileName}:${finding.line}
|
|
730
|
+
*
|
|
731
|
+
* Description: ${finding.description}
|
|
732
|
+
*/
|
|
733
|
+
${finding.foundryPoC}
|
|
734
|
+
`;
|
|
735
|
+
}).join('\n');
|
|
736
|
+
|
|
737
|
+
return header + contracts;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Sanitize a title into a valid Solidity test function name
|
|
742
|
+
*/
|
|
743
|
+
sanitizeTestName(title) {
|
|
744
|
+
return title
|
|
745
|
+
.replace(/[^a-zA-Z0-9\s]/g, '')
|
|
746
|
+
.split(/\s+/)
|
|
747
|
+
.map((word, i) => i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
748
|
+
.join('');
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Get summary of findings by attack vector
|
|
753
|
+
*/
|
|
754
|
+
getFindingsSummary() {
|
|
755
|
+
const byVector = {};
|
|
756
|
+
const bySeverity = {};
|
|
757
|
+
|
|
758
|
+
for (const finding of this.findings) {
|
|
759
|
+
// By attack vector
|
|
760
|
+
const vector = finding.attackVector || 'unknown';
|
|
761
|
+
if (!byVector[vector]) {
|
|
762
|
+
byVector[vector] = { count: 0, highConfidence: 0, findings: [] };
|
|
763
|
+
}
|
|
764
|
+
byVector[vector].count++;
|
|
765
|
+
if (finding.isHighConfidence) {
|
|
766
|
+
byVector[vector].highConfidence++;
|
|
767
|
+
}
|
|
768
|
+
byVector[vector].findings.push({
|
|
769
|
+
title: finding.title,
|
|
770
|
+
severity: finding.severity,
|
|
771
|
+
exploitabilityScore: finding.exploitabilityScore
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// By severity
|
|
775
|
+
const severity = finding.severity;
|
|
776
|
+
if (!bySeverity[severity]) {
|
|
777
|
+
bySeverity[severity] = 0;
|
|
778
|
+
}
|
|
779
|
+
bySeverity[severity]++;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
total: this.findings.length,
|
|
784
|
+
highConfidence: this.findings.filter(f => f.isHighConfidence).length,
|
|
785
|
+
withPoC: this.findings.filter(f => f.foundryPoC).length,
|
|
786
|
+
byAttackVector: byVector,
|
|
787
|
+
bySeverity: bySeverity,
|
|
788
|
+
topExploitable: this.findings
|
|
789
|
+
.filter(f => f.exploitabilityScore >= 70)
|
|
790
|
+
.sort((a, b) => b.exploitabilityScore - a.exploitabilityScore)
|
|
791
|
+
.slice(0, 5)
|
|
792
|
+
.map(f => ({
|
|
793
|
+
title: f.title,
|
|
794
|
+
severity: f.severity,
|
|
795
|
+
score: f.exploitabilityScore,
|
|
796
|
+
vector: f.attackVector
|
|
797
|
+
}))
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
reset() {
|
|
802
|
+
this.findings = [];
|
|
803
|
+
this.stats = {
|
|
804
|
+
filesScanned: 0,
|
|
805
|
+
totalFindings: 0,
|
|
806
|
+
critical: 0,
|
|
807
|
+
high: 0,
|
|
808
|
+
medium: 0,
|
|
809
|
+
low: 0,
|
|
810
|
+
info: 0,
|
|
811
|
+
exploitable: 0
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
module.exports = Web3CRITScannerEnhanced;
|