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
@@ -0,0 +1,494 @@
1
+ const BaseDetector = require('./base-detector');
2
+
3
+ /**
4
+ * Timestamp Dependence Detector (Enhanced)
5
+ * Detects dangerous reliance on block.timestamp with time-scale awareness
6
+ * to reduce false positives for legitimate long-term calculations.
7
+ */
8
+ class TimestampDependenceDetector extends BaseDetector {
9
+ constructor() {
10
+ super(
11
+ 'Timestamp Dependence',
12
+ 'Detects dangerous reliance on block.timestamp',
13
+ 'MEDIUM'
14
+ );
15
+ this.currentContract = null;
16
+ this.currentFunction = null;
17
+ this.currentFunctionNode = null;
18
+ this.timestampUsages = [];
19
+
20
+ // Miner manipulation window in seconds (~15 seconds)
21
+ this.MANIPULATION_WINDOW = 15;
22
+ // Safe threshold - windows longer than this have minimal manipulation impact
23
+ this.SAFE_WINDOW_THRESHOLD = 3600; // 1 hour in seconds
24
+ // Very safe threshold - negligible impact
25
+ this.VERY_SAFE_THRESHOLD = 86400; // 1 day in seconds
26
+ }
27
+
28
+ async detect(ast, sourceCode, fileName, cfg, dataFlow) {
29
+ this.findings = [];
30
+ this.ast = ast;
31
+ this.sourceCode = sourceCode;
32
+ this.fileName = fileName;
33
+ this.sourceLines = sourceCode.split('\n');
34
+ this.timestampUsages = [];
35
+
36
+ this.traverse(ast);
37
+
38
+ return this.findings;
39
+ }
40
+
41
+ visitContractDefinition(node) {
42
+ this.currentContract = node.name;
43
+ }
44
+
45
+ visitFunctionDefinition(node) {
46
+ this.currentFunction = node.name || 'constructor';
47
+ this.currentFunctionNode = node;
48
+ }
49
+
50
+ visitMemberAccess(node) {
51
+ // Check for block.timestamp usage
52
+ if (node.expression &&
53
+ node.expression.type === 'Identifier' &&
54
+ node.expression.name === 'block') {
55
+
56
+ if (node.memberName === 'timestamp' || node.memberName === 'number') {
57
+ this.analyzeTimestampUsage(node);
58
+ }
59
+ }
60
+
61
+ // Also check for 'now' keyword (deprecated but still used)
62
+ if (node.memberName === 'now') {
63
+ this.reportNowUsage(node);
64
+ }
65
+ }
66
+
67
+ visitIdentifier(node) {
68
+ // Check for direct 'now' usage (deprecated)
69
+ if (node.name === 'now') {
70
+ this.reportNowUsage(node);
71
+ }
72
+ }
73
+
74
+ analyzeTimestampUsage(node) {
75
+ // Get surrounding context
76
+ const parentContext = this.getParentContext(node);
77
+ const code = this.getCodeSnippet(node.loc);
78
+
79
+ // 1. CRITICAL: Randomness based on timestamp - always dangerous
80
+ if (this.isUsedForRandomness(parentContext)) {
81
+ this.reportTimestampRandomness(node);
82
+ return;
83
+ }
84
+
85
+ // 2. HIGH: Equality comparison with timestamp - usually a bug
86
+ if (this.isEqualityComparison(parentContext)) {
87
+ this.reportTimestampEquality(node);
88
+ return;
89
+ }
90
+
91
+ // 3. Analyze time windows with scale awareness
92
+ const timeWindowAnalysis = this.analyzeTimeWindow(parentContext);
93
+
94
+ if (timeWindowAnalysis.hasTimeWindow) {
95
+ if (timeWindowAnalysis.isCritical) {
96
+ this.reportCriticalTimeWindow(node, timeWindowAnalysis);
97
+ return;
98
+ } else if (timeWindowAnalysis.isRisky) {
99
+ this.reportShortTimeWindow(node, timeWindowAnalysis);
100
+ return;
101
+ }
102
+ // Safe time windows (>1 hour) are not reported
103
+ }
104
+
105
+ // 4. Financial calculations - analyze impact scale
106
+ if (this.isFinancialCalculation(parentContext)) {
107
+ const financialRisk = this.analyzeFinancialImpact(parentContext);
108
+ if (financialRisk.isSignificant) {
109
+ this.reportTimestampFinancial(node, financialRisk);
110
+ }
111
+ // Don't report low-impact financial calculations
112
+ return;
113
+ }
114
+
115
+ // 5. Access control - analyze the lock duration
116
+ if (this.isAccessControl(parentContext)) {
117
+ const accessRisk = this.analyzeAccessControlRisk(parentContext);
118
+ if (accessRisk.isExploitable) {
119
+ this.reportTimestampAccessControl(node, accessRisk);
120
+ }
121
+ return;
122
+ }
123
+ }
124
+
125
+ getParentContext(node) {
126
+ // Get lines around the timestamp usage
127
+ if (!node.loc) return '';
128
+ const startLine = Math.max(0, node.loc.start.line - 5);
129
+ const endLine = Math.min(this.sourceLines.length, node.loc.start.line + 5);
130
+ return this.sourceLines.slice(startLine, endLine).join('\n');
131
+ }
132
+
133
+ isUsedForRandomness(context) {
134
+ const randomKeywords = ['random', 'seed', 'entropy'];
135
+ const contextLower = context.toLowerCase();
136
+
137
+ // Must be in a randomness context AND use hashing
138
+ const hasRandomContext = randomKeywords.some(kw => contextLower.includes(kw));
139
+ const hasHashing = /keccak256|sha3|hash/i.test(context);
140
+
141
+ return hasRandomContext || (hasHashing && /timestamp|block\.number/i.test(context));
142
+ }
143
+
144
+ isEqualityComparison(context) {
145
+ // Check for exact equality with timestamp (common bug)
146
+ // Look for patterns like: timestamp == value or value == timestamp
147
+ const exactEquality = /block\.timestamp\s*==|==\s*block\.timestamp/i.test(context);
148
+ return exactEquality;
149
+ }
150
+
151
+ /**
152
+ * Analyze time window with awareness of manipulation impact
153
+ */
154
+ analyzeTimeWindow(context) {
155
+ const result = {
156
+ hasTimeWindow: false,
157
+ windowSize: 0,
158
+ isCritical: false,
159
+ isRisky: false,
160
+ reason: ''
161
+ };
162
+
163
+ // Extract time constants - handle Solidity time units
164
+ const timeUnits = {
165
+ 'seconds': 1,
166
+ 'second': 1,
167
+ 'minutes': 60,
168
+ 'minute': 60,
169
+ 'hours': 3600,
170
+ 'hour': 3600,
171
+ 'days': 86400,
172
+ 'day': 86400,
173
+ 'weeks': 604800,
174
+ 'week': 604800
175
+ };
176
+
177
+ // Pattern: number followed by optional time unit
178
+ const timePattern = /(\d+)\s*(seconds?|minutes?|hours?|days?|weeks?)?/gi;
179
+ let match;
180
+ let smallestWindow = Infinity;
181
+
182
+ while ((match = timePattern.exec(context)) !== null) {
183
+ const value = parseInt(match[1]);
184
+ const unit = match[2] ? match[2].toLowerCase() : 'seconds';
185
+ const multiplier = timeUnits[unit] || 1;
186
+ const windowInSeconds = value * multiplier;
187
+
188
+ if (windowInSeconds > 0 && windowInSeconds < smallestWindow) {
189
+ smallestWindow = windowInSeconds;
190
+ }
191
+ }
192
+
193
+ if (smallestWindow !== Infinity) {
194
+ result.hasTimeWindow = true;
195
+ result.windowSize = smallestWindow;
196
+
197
+ // Calculate manipulation impact
198
+ const manipulationPercent = (this.MANIPULATION_WINDOW / smallestWindow) * 100;
199
+
200
+ if (smallestWindow < 60) {
201
+ // Less than 1 minute - critical
202
+ result.isCritical = true;
203
+ result.reason = `Time window of ${smallestWindow}s can be significantly manipulated (${manipulationPercent.toFixed(1)}% of window)`;
204
+ } else if (smallestWindow < this.SAFE_WINDOW_THRESHOLD) {
205
+ // Between 1 minute and 1 hour - risky
206
+ result.isRisky = true;
207
+ result.reason = `Time window of ${this.formatDuration(smallestWindow)} has ${manipulationPercent.toFixed(1)}% manipulation risk`;
208
+ }
209
+ // >= 1 hour is considered safe (less than 0.4% manipulation)
210
+ }
211
+
212
+ return result;
213
+ }
214
+
215
+ /**
216
+ * Analyze if financial calculation has meaningful impact from timestamp manipulation
217
+ */
218
+ analyzeFinancialImpact(context) {
219
+ const result = {
220
+ isSignificant: false,
221
+ impactReason: '',
222
+ confidence: 'LOW'
223
+ };
224
+
225
+ const contextLower = context.toLowerCase();
226
+
227
+ // Check if this is a long-term calculation (days/weeks/years)
228
+ const longTermPatterns = /days?|weeks?|years?|annual|monthly/i;
229
+ const isLongTerm = longTermPatterns.test(context);
230
+
231
+ // Check for high-value operations
232
+ const highValueOps = /liquidat|collateral|borrow|lend|stake|unstake/i;
233
+ const isHighValue = highValueOps.test(context);
234
+
235
+ // Short-term, high-frequency rewards are risky
236
+ const shortTermRewards = /reward|claim|harvest/i.test(context) &&
237
+ /block|second|minute/i.test(context);
238
+
239
+ if (shortTermRewards) {
240
+ result.isSignificant = true;
241
+ result.impactReason = 'Short-term reward calculation where 15s manipulation could affect immediate payouts';
242
+ result.confidence = 'MEDIUM';
243
+ } else if (isHighValue && !isLongTerm) {
244
+ result.isSignificant = true;
245
+ result.impactReason = 'High-value financial operation without long-term averaging';
246
+ result.confidence = 'MEDIUM';
247
+ }
248
+ // Long-term APY/interest calculations are not significant
249
+ // 15 seconds out of 365 days = 0.0000005% impact
250
+
251
+ return result;
252
+ }
253
+
254
+ /**
255
+ * Analyze access control timestamp risk
256
+ */
257
+ analyzeAccessControlRisk(context) {
258
+ const result = {
259
+ isExploitable: false,
260
+ reason: '',
261
+ severity: 'MEDIUM'
262
+ };
263
+
264
+ // Check for timelock patterns
265
+ const timelockPatterns = /unlock|lock|delay|cliff|vesting/i;
266
+ const hasTimelock = timelockPatterns.test(context);
267
+
268
+ // Extract the timelock duration if possible
269
+ const timeAnalysis = this.analyzeTimeWindow(context);
270
+
271
+ if (hasTimelock && timeAnalysis.hasTimeWindow) {
272
+ if (timeAnalysis.windowSize < 3600) {
273
+ // Less than 1 hour timelock - exploitable
274
+ result.isExploitable = true;
275
+ result.reason = `Timelock of ${this.formatDuration(timeAnalysis.windowSize)} can be bypassed via ~15s timestamp manipulation`;
276
+ result.severity = 'HIGH';
277
+ } else if (timeAnalysis.windowSize < 86400) {
278
+ // 1-24 hour timelock - minor risk
279
+ result.isExploitable = true;
280
+ result.reason = `Timelock could be slightly shortened via timestamp manipulation (minor impact)`;
281
+ result.severity = 'LOW';
282
+ }
283
+ // Timelocks >= 1 day are effectively safe
284
+ } else if (!hasTimelock) {
285
+ // Generic timestamp access control without clear timelock
286
+ // Only flag if it appears to be a short window check
287
+ if (timeAnalysis.isCritical || timeAnalysis.isRisky) {
288
+ result.isExploitable = true;
289
+ result.reason = 'Short-duration timestamp check can be manipulated';
290
+ result.severity = 'MEDIUM';
291
+ }
292
+ }
293
+
294
+ return result;
295
+ }
296
+
297
+ isFinancialCalculation(context) {
298
+ const financialKeywords = ['interest', 'rate', 'reward', 'yield', 'apr', 'apy', 'compound', 'stake', 'earn'];
299
+ const contextLower = context.toLowerCase();
300
+ return financialKeywords.some(kw => contextLower.includes(kw));
301
+ }
302
+
303
+ isAccessControl(context) {
304
+ // More specific: must be in a require/if with comparison
305
+ const hasCondition = /require\s*\(|if\s*\(/i.test(context);
306
+ const hasComparison = /[<>=]/.test(context);
307
+ return hasCondition && hasComparison;
308
+ }
309
+
310
+ formatDuration(seconds) {
311
+ if (seconds < 60) return `${seconds} seconds`;
312
+ if (seconds < 3600) return `${Math.round(seconds / 60)} minutes`;
313
+ if (seconds < 86400) return `${Math.round(seconds / 3600)} hours`;
314
+ return `${Math.round(seconds / 86400)} days`;
315
+ }
316
+
317
+ reportNowUsage(node) {
318
+ this.addFinding({
319
+ title: 'Deprecated "now" Keyword Usage',
320
+ description: `Using deprecated 'now' keyword which is alias for block.timestamp. This should be replaced with block.timestamp for clarity.`,
321
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
322
+ line: node.loc ? node.loc.start.line : 0,
323
+ column: node.loc ? node.loc.start.column : 0,
324
+ code: this.getCodeSnippet(node.loc),
325
+ severity: 'INFO',
326
+ confidence: 'HIGH',
327
+ exploitable: false,
328
+ recommendation: 'Replace "now" with "block.timestamp" for Solidity 0.7+ compatibility.',
329
+ references: [
330
+ 'https://docs.soliditylang.org/en/latest/units-and-global-variables.html'
331
+ ]
332
+ });
333
+ }
334
+
335
+ reportTimestampRandomness(node) {
336
+ this.addFinding({
337
+ title: 'Timestamp Used for Randomness',
338
+ description: `Block timestamp or block number used in randomness generation. Miners/validators can manipulate block.timestamp within ~15 seconds to influence outcomes.`,
339
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
340
+ line: node.loc ? node.loc.start.line : 0,
341
+ column: node.loc ? node.loc.start.column : 0,
342
+ code: this.getCodeSnippet(node.loc),
343
+ severity: 'CRITICAL',
344
+ confidence: 'HIGH',
345
+ exploitable: true,
346
+ exploitabilityScore: 90,
347
+ attackVector: 'miner-timestamp-manipulation',
348
+ recommendation: 'Use Chainlink VRF for verifiable randomness. Never use block.timestamp, block.number, or blockhash for randomness in applications with economic value.',
349
+ references: [
350
+ 'https://swcregistry.io/docs/SWC-120',
351
+ 'https://docs.chain.link/vrf'
352
+ ],
353
+ foundryPoC: this.generateRandomnessPoC()
354
+ });
355
+ }
356
+
357
+ reportTimestampEquality(node) {
358
+ this.addFinding({
359
+ title: 'Timestamp Equality Comparison',
360
+ description: `Exact equality comparison (==) with block.timestamp. This condition is unreliable - the exact timestamp value is unpredictable and may never match.`,
361
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
362
+ line: node.loc ? node.loc.start.line : 0,
363
+ column: node.loc ? node.loc.start.column : 0,
364
+ code: this.getCodeSnippet(node.loc),
365
+ severity: 'HIGH',
366
+ confidence: 'HIGH',
367
+ exploitable: false,
368
+ exploitabilityScore: 0,
369
+ recommendation: 'Use range comparisons (>=, <=) instead of equality. Example: require(block.timestamp >= deadline) instead of require(block.timestamp == deadline).',
370
+ references: [
371
+ 'https://swcregistry.io/docs/SWC-116'
372
+ ]
373
+ });
374
+ }
375
+
376
+ reportCriticalTimeWindow(node, analysis) {
377
+ this.addFinding({
378
+ title: 'Critical: Very Short Time Window',
379
+ description: `${analysis.reason}. A miner can manipulate timestamps by ~15 seconds, which is a significant portion of this window.`,
380
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
381
+ line: node.loc ? node.loc.start.line : 0,
382
+ column: node.loc ? node.loc.start.column : 0,
383
+ code: this.getCodeSnippet(node.loc),
384
+ severity: 'HIGH',
385
+ confidence: 'HIGH',
386
+ exploitable: true,
387
+ exploitabilityScore: 75,
388
+ attackVector: 'timestamp-manipulation',
389
+ recommendation: 'Increase the time window to at least 15 minutes (900 seconds) for security-critical operations. Consider using block.number with average block time for more predictable timing.',
390
+ references: [
391
+ 'https://swcregistry.io/docs/SWC-116'
392
+ ]
393
+ });
394
+ }
395
+
396
+ reportShortTimeWindow(node, analysis) {
397
+ this.addFinding({
398
+ title: 'Short Timestamp-Based Time Window',
399
+ description: `${analysis.reason}. While not critical, this window is short enough that timestamp manipulation could have measurable impact.`,
400
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
401
+ line: node.loc ? node.loc.start.line : 0,
402
+ column: node.loc ? node.loc.start.column : 0,
403
+ code: this.getCodeSnippet(node.loc),
404
+ severity: 'MEDIUM',
405
+ confidence: 'MEDIUM',
406
+ exploitable: true,
407
+ exploitabilityScore: 40,
408
+ recommendation: `Consider increasing the time window if this controls high-value operations. Current window: ${this.formatDuration(analysis.windowSize)}.`,
409
+ references: [
410
+ 'https://swcregistry.io/docs/SWC-116'
411
+ ]
412
+ });
413
+ }
414
+
415
+ reportTimestampFinancial(node, financialRisk) {
416
+ this.addFinding({
417
+ title: 'Timestamp in Short-Term Financial Calculation',
418
+ description: `${financialRisk.impactReason}. Long-term calculations (APY over months/years) are generally safe as 15s manipulation is negligible.`,
419
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
420
+ line: node.loc ? node.loc.start.line : 0,
421
+ column: node.loc ? node.loc.start.column : 0,
422
+ code: this.getCodeSnippet(node.loc),
423
+ severity: 'MEDIUM',
424
+ confidence: financialRisk.confidence,
425
+ exploitable: true,
426
+ exploitabilityScore: 35,
427
+ recommendation: 'For short-term reward calculations, consider using block numbers or adding minimum time thresholds. Long-term interest/APY calculations using timestamps are acceptable.',
428
+ references: [
429
+ 'https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/timestamp-dependence/'
430
+ ]
431
+ });
432
+ }
433
+
434
+ reportTimestampAccessControl(node, accessRisk) {
435
+ this.addFinding({
436
+ title: 'Timestamp-Based Access Control',
437
+ description: `${accessRisk.reason}. This could allow early access or bypass of time-restricted functionality.`,
438
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
439
+ line: node.loc ? node.loc.start.line : 0,
440
+ column: node.loc ? node.loc.start.column : 0,
441
+ code: this.getCodeSnippet(node.loc),
442
+ severity: accessRisk.severity,
443
+ confidence: 'MEDIUM',
444
+ exploitable: true,
445
+ exploitabilityScore: accessRisk.severity === 'HIGH' ? 60 : 25,
446
+ recommendation: 'Use timelocks of at least 1 day for critical operations. Add buffer time (e.g., 1 hour) to account for timestamp manipulation.',
447
+ references: [
448
+ 'https://swcregistry.io/docs/SWC-116'
449
+ ]
450
+ });
451
+ }
452
+
453
+ generateRandomnessPoC() {
454
+ return `// SPDX-License-Identifier: MIT
455
+ pragma solidity ^0.8.0;
456
+
457
+ import "forge-std/Test.sol";
458
+
459
+ /**
460
+ * Proof of Concept: Timestamp-Based Randomness Manipulation
461
+ * Demonstrates how miners can manipulate block.timestamp for favorable outcomes
462
+ */
463
+ contract TimestampRandomnessExploit is Test {
464
+ // Target contract using timestamp for randomness
465
+ address constant TARGET = address(0);
466
+
467
+ function testExploit() public {
468
+ // Simulate miner ability to set timestamp within valid range
469
+ // Miners can adjust timestamp up to ~15 seconds from previous block
470
+
471
+ uint256 desiredOutcome = 1; // The outcome we want
472
+ uint256 baseTimestamp = block.timestamp;
473
+
474
+ // Try different timestamps within manipulation window
475
+ for (uint256 delta = 0; delta <= 15; delta++) {
476
+ vm.warp(baseTimestamp + delta);
477
+
478
+ // Calculate what the "random" result would be
479
+ // uint256 result = uint256(keccak256(abi.encodePacked(block.timestamp))) % N;
480
+
481
+ // if (result == desiredOutcome) {
482
+ // // Found favorable timestamp, proceed with attack
483
+ // break;
484
+ // }
485
+ }
486
+
487
+ // Call target contract at manipulated timestamp
488
+ // TARGET.randomFunction();
489
+ }
490
+ }`;
491
+ }
492
+ }
493
+
494
+ module.exports = TimestampDependenceDetector;