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,720 @@
1
+ const parser = require('@solidity-parser/parser');
2
+
3
+ /**
4
+ * Advanced Data Flow Analyzer
5
+ * Implements real taint propagation with source-sink analysis
6
+ * Tracks user-controlled data through complex expressions and assignments
7
+ */
8
+ class DataFlowAnalyzer {
9
+ constructor(controlFlowGraph) {
10
+ this.cfg = controlFlowGraph;
11
+ this.taintedVariables = new Map(); // variable -> taint info
12
+ this.taintedExpressions = new Map(); // expression hash -> taint info
13
+ this.dataFlows = [];
14
+ this.stateVariableTaints = new Map(); // state var -> taint info
15
+ this.arithmeticOperations = []; // Track arithmetic for precision loss
16
+ this.valueFlows = []; // Track ETH/token value flows
17
+ this.oracleValueFlows = []; // Track oracle-derived values flowing into value-moving operations
18
+ }
19
+
20
+ /**
21
+ * Perform comprehensive taint analysis
22
+ */
23
+ analyze() {
24
+ this.taintedVariables.clear();
25
+ this.taintedExpressions.clear();
26
+ this.dataFlows = [];
27
+ this.stateVariableTaints.clear();
28
+ this.arithmeticOperations = [];
29
+ this.valueFlows = [];
30
+ this.oracleValueFlows = [];
31
+
32
+ // Phase 1: Identify all taint sources
33
+ this.identifyTaintSources();
34
+
35
+ // Phase 2: Propagate taint through assignments (fixed-point)
36
+ this.propagateTaint();
37
+
38
+ // Phase 3: Track arithmetic operations for precision loss
39
+ this.analyzeArithmeticOperations();
40
+
41
+ // Phase 4: Track value flows (ETH, tokens)
42
+ this.analyzeValueFlows();
43
+
44
+ // Phase 5: Check dangerous sinks
45
+ this.checkDangerousSinks();
46
+
47
+ return {
48
+ taintedVariables: this.taintedVariables,
49
+ stateVariableTaints: this.stateVariableTaints,
50
+ dataFlows: this.dataFlows,
51
+ arithmeticOperations: this.arithmeticOperations,
52
+ valueFlows: this.valueFlows,
53
+ oracleValueFlows: this.oracleValueFlows
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Identify all sources of tainted (user-controlled) data
59
+ */
60
+ identifyTaintSources() {
61
+ for (const [funcKey, funcInfo] of this.cfg.functions) {
62
+ // Skip internal/private functions for external taint sources
63
+ const isExternallyCallable = funcInfo.visibility === 'public' ||
64
+ funcInfo.visibility === 'external' ||
65
+ funcInfo.isConstructor;
66
+
67
+ if (!isExternallyCallable && !funcInfo.isFallback && !funcInfo.isReceive) {
68
+ continue;
69
+ }
70
+
71
+ // Function parameters are tainted (user-controlled)
72
+ funcInfo.parameters.forEach((param, index) => {
73
+ if (param.name) {
74
+ const varKey = `${funcKey}.${param.name}`;
75
+ this.taintedVariables.set(varKey, {
76
+ type: 'parameter',
77
+ source: 'user_input',
78
+ function: funcKey,
79
+ name: param.name,
80
+ paramIndex: index,
81
+ paramType: param.type,
82
+ confidence: 'HIGH',
83
+ exploitability: this.assessParameterExploitability(param.type)
84
+ });
85
+ }
86
+ });
87
+
88
+ // Analyze function body for msg.*, tx.*, block.* usage
89
+ if (funcInfo.node && funcInfo.node.body) {
90
+ this.findImplicitTaintSources(funcInfo.node.body, funcKey);
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Find implicit taint sources in function body (msg.sender, msg.value, etc.)
97
+ */
98
+ findImplicitTaintSources(node, funcKey) {
99
+ if (!node) return;
100
+
101
+ const self = this;
102
+
103
+ parser.visit(node, {
104
+ MemberAccess(memberNode) {
105
+ const expr = self.nodeToString(memberNode);
106
+
107
+ // msg.* sources
108
+ if (expr.startsWith('msg.')) {
109
+ self.taintedExpressions.set(expr, {
110
+ type: 'msg_property',
111
+ source: expr,
112
+ function: funcKey,
113
+ confidence: 'HIGH',
114
+ exploitability: expr === 'msg.value' ? 'HIGH' : 'MEDIUM'
115
+ });
116
+ }
117
+
118
+ // tx.* sources
119
+ if (expr.startsWith('tx.')) {
120
+ self.taintedExpressions.set(expr, {
121
+ type: 'tx_property',
122
+ source: expr,
123
+ function: funcKey,
124
+ confidence: 'HIGH',
125
+ exploitability: expr === 'tx.origin' ? 'HIGH' : 'MEDIUM'
126
+ });
127
+ }
128
+
129
+ // block.* sources (manipulable by miners)
130
+ if (expr.startsWith('block.')) {
131
+ const manipulable = ['block.timestamp', 'block.number', 'block.coinbase'];
132
+ if (manipulable.some(m => expr.includes(m))) {
133
+ self.taintedExpressions.set(expr, {
134
+ type: 'block_property',
135
+ source: expr,
136
+ function: funcKey,
137
+ confidence: 'MEDIUM',
138
+ exploitability: 'MEDIUM',
139
+ note: 'Manipulable by miners/validators'
140
+ });
141
+ }
142
+ }
143
+ }
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Detect whether a function call expression looks like an oracle read.
149
+ * This is intentionally conservative and used to taint *oracle-derived values*,
150
+ * which can enable oracle->profit/dataflow checks.
151
+ */
152
+ isOracleRead(node) {
153
+ if (!node) return false;
154
+ // Covers common Chainlink, Uniswap, and "median price" style oracles
155
+ const expr = this.nodeToString(node);
156
+ const oraclePatterns = [
157
+ /latestRoundData\s*\(/i,
158
+ /latestAnswer\s*\(/i,
159
+ /\.getReserves\s*\(\s*\)/i,
160
+ /\.slot0\s*\(\s*\)/i,
161
+ /\.observe\s*\(/i,
162
+ /getMedianPrice\s*\(/i,
163
+ /consult\s*\(/i,
164
+ /priceCumulative/i,
165
+ /cumulativePrice/i
166
+ ];
167
+ return oraclePatterns.some(p => p.test(expr));
168
+ }
169
+
170
+ /**
171
+ * Propagate taint through assignments using fixed-point iteration
172
+ */
173
+ propagateTaint() {
174
+ let changed = true;
175
+ let iterations = 0;
176
+ const maxIterations = 50;
177
+
178
+ while (changed && iterations < maxIterations) {
179
+ changed = false;
180
+ iterations++;
181
+
182
+ for (const [funcKey, funcInfo] of this.cfg.functions) {
183
+ if (!funcInfo.node || !funcInfo.node.body) continue;
184
+
185
+ // Analyze all assignments in function
186
+ const newTaints = this.analyzeAssignments(funcInfo.node.body, funcKey);
187
+
188
+ for (const [varKey, taintInfo] of newTaints) {
189
+ if (!this.taintedVariables.has(varKey)) {
190
+ this.taintedVariables.set(varKey, taintInfo);
191
+ changed = true;
192
+ }
193
+ }
194
+
195
+ // Propagate to state variables
196
+ funcInfo.stateWrites.forEach(write => {
197
+ const localTaint = this.findTaintForWrite(write, funcKey);
198
+ if (localTaint) {
199
+ const stateKey = `${funcInfo.contract}.${write.variable}`;
200
+ if (!this.stateVariableTaints.has(stateKey)) {
201
+ this.stateVariableTaints.set(stateKey, {
202
+ ...localTaint,
203
+ stateVariable: write.variable,
204
+ writtenBy: funcKey,
205
+ loc: write.loc
206
+ });
207
+ changed = true;
208
+ }
209
+ }
210
+ });
211
+ }
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Analyze assignments to propagate taint
217
+ */
218
+ analyzeAssignments(node, funcKey) {
219
+ const newTaints = new Map();
220
+ const self = this;
221
+
222
+ parser.visit(node, {
223
+ BinaryOperation(binNode) {
224
+ if (!['=', '+=', '-=', '*=', '/='].includes(binNode.operator)) return;
225
+
226
+ const leftName = self.getAssignmentTarget(binNode.left);
227
+ if (!leftName) return;
228
+
229
+ const rightExpr = self.nodeToString(binNode.right);
230
+ const rightTaint = self.isExpressionTainted(binNode.right, funcKey);
231
+
232
+ if (rightTaint) {
233
+ const varKey = `${funcKey}.${leftName}`;
234
+ newTaints.set(varKey, {
235
+ type: 'derived',
236
+ source: rightTaint.source,
237
+ derivedFrom: rightExpr,
238
+ function: funcKey,
239
+ name: leftName,
240
+ confidence: rightTaint.confidence,
241
+ exploitability: rightTaint.exploitability
242
+ });
243
+ }
244
+ },
245
+
246
+ VariableDeclarationStatement(declNode) {
247
+ if (!declNode.initialValue) return;
248
+
249
+ declNode.variables.forEach(variable => {
250
+ if (!variable || !variable.name) return;
251
+
252
+ const rightTaint = self.isExpressionTainted(declNode.initialValue, funcKey);
253
+ if (rightTaint) {
254
+ const varKey = `${funcKey}.${variable.name}`;
255
+ newTaints.set(varKey, {
256
+ type: 'derived',
257
+ source: rightTaint.source,
258
+ derivedFrom: self.nodeToString(declNode.initialValue),
259
+ function: funcKey,
260
+ name: variable.name,
261
+ confidence: rightTaint.confidence,
262
+ exploitability: rightTaint.exploitability
263
+ });
264
+ }
265
+ });
266
+ }
267
+ });
268
+
269
+ return newTaints;
270
+ }
271
+
272
+ /**
273
+ * Check if an expression is tainted
274
+ */
275
+ isExpressionTainted(node, funcKey) {
276
+ if (!node) return null;
277
+
278
+ const exprStr = this.nodeToString(node);
279
+
280
+ // Oracle read is a taint source (even if no user input involved)
281
+ if (node.type === 'FunctionCall' && this.isOracleRead(node)) {
282
+ return {
283
+ type: 'oracle_read',
284
+ source: exprStr,
285
+ confidence: 'HIGH',
286
+ exploitability: 'HIGH'
287
+ };
288
+ }
289
+
290
+ // Direct taint source
291
+ if (this.taintedExpressions.has(exprStr)) {
292
+ return this.taintedExpressions.get(exprStr);
293
+ }
294
+
295
+ // Check if it's a tainted variable
296
+ if (node.type === 'Identifier') {
297
+ const varKey = `${funcKey}.${node.name}`;
298
+ if (this.taintedVariables.has(varKey)) {
299
+ return this.taintedVariables.get(varKey);
300
+ }
301
+ }
302
+
303
+ // Check for msg.*, tx.*, block.* in expression
304
+ if (exprStr.includes('msg.') || exprStr.includes('tx.') || exprStr.includes('block.timestamp')) {
305
+ return {
306
+ type: 'implicit',
307
+ source: exprStr,
308
+ confidence: 'MEDIUM',
309
+ exploitability: 'MEDIUM'
310
+ };
311
+ }
312
+
313
+ // Recursively check sub-expressions
314
+ if (node.type === 'BinaryOperation') {
315
+ const leftTaint = this.isExpressionTainted(node.left, funcKey);
316
+ const rightTaint = this.isExpressionTainted(node.right, funcKey);
317
+ return leftTaint || rightTaint;
318
+ }
319
+
320
+ if (node.type === 'FunctionCall' && node.arguments) {
321
+ // Also consider oracle reads on the callee expression (e.g., oracle.latestRoundData())
322
+ if (this.isOracleRead(node)) {
323
+ return {
324
+ type: 'oracle_read',
325
+ source: exprStr,
326
+ confidence: 'HIGH',
327
+ exploitability: 'HIGH'
328
+ };
329
+ }
330
+ for (const arg of node.arguments) {
331
+ const argTaint = this.isExpressionTainted(arg, funcKey);
332
+ if (argTaint) return argTaint;
333
+ }
334
+ }
335
+
336
+ if (node.type === 'IndexAccess') {
337
+ const baseTaint = this.isExpressionTainted(node.base, funcKey);
338
+ const indexTaint = this.isExpressionTainted(node.index, funcKey);
339
+ return baseTaint || indexTaint;
340
+ }
341
+
342
+ if (node.type === 'MemberAccess') {
343
+ return this.isExpressionTainted(node.expression, funcKey);
344
+ }
345
+
346
+ return null;
347
+ }
348
+
349
+ /**
350
+ * Analyze arithmetic operations for precision loss vulnerabilities
351
+ */
352
+ analyzeArithmeticOperations() {
353
+ for (const [funcKey, funcInfo] of this.cfg.functions) {
354
+ if (!funcInfo.node || !funcInfo.node.body) continue;
355
+
356
+ const self = this;
357
+
358
+ parser.visit(funcInfo.node.body, {
359
+ BinaryOperation(node) {
360
+ if (!['+', '-', '*', '/', '%'].includes(node.operator)) return;
361
+
362
+ const operation = {
363
+ function: funcKey,
364
+ operator: node.operator,
365
+ left: self.nodeToString(node.left),
366
+ right: self.nodeToString(node.right),
367
+ loc: node.loc,
368
+ issues: []
369
+ };
370
+
371
+ // Check for division before multiplication (precision loss)
372
+ if (node.operator === '*') {
373
+ if (self.containsDivision(node.left) || self.containsDivision(node.right)) {
374
+ operation.issues.push({
375
+ type: 'division_before_multiplication',
376
+ severity: 'MEDIUM',
377
+ description: 'Division before multiplication can cause precision loss'
378
+ });
379
+ }
380
+ }
381
+
382
+ // Check for division that could truncate to zero
383
+ if (node.operator === '/') {
384
+ const rightVal = self.tryGetConstantValue(node.right);
385
+ if (rightVal && rightVal > 1e18) {
386
+ operation.issues.push({
387
+ type: 'large_divisor',
388
+ severity: 'MEDIUM',
389
+ description: 'Division by large number may truncate to zero'
390
+ });
391
+ }
392
+ }
393
+
394
+ // Check for unchecked arithmetic with tainted values
395
+ const leftTaint = self.isExpressionTainted(node.left, funcKey);
396
+ const rightTaint = self.isExpressionTainted(node.right, funcKey);
397
+
398
+ if (leftTaint || rightTaint) {
399
+ operation.tainted = true;
400
+ operation.taintSource = (leftTaint || rightTaint).source;
401
+ }
402
+
403
+ if (operation.issues.length > 0 || operation.tainted) {
404
+ self.arithmeticOperations.push(operation);
405
+ }
406
+ }
407
+ });
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Analyze value flows (ETH and token transfers)
413
+ */
414
+ analyzeValueFlows() {
415
+ for (const [funcKey, funcInfo] of this.cfg.functions) {
416
+ // Check external calls for value transfers
417
+ funcInfo.externalCalls.forEach(call => {
418
+ if (['call', 'transfer', 'send'].includes(call.type)) {
419
+ const flow = {
420
+ function: funcKey,
421
+ type: call.type,
422
+ target: call.target,
423
+ loc: call.loc,
424
+ issues: []
425
+ };
426
+
427
+ // Check if target is tainted (user-controlled recipient)
428
+ const targetTaint = this.isExpressionTainted({ type: 'Identifier', name: call.target }, funcKey);
429
+ if (targetTaint || call.target.includes('msg.sender')) {
430
+ flow.targetTainted = true;
431
+ flow.targetTaintSource = targetTaint?.source || 'msg.sender';
432
+ }
433
+
434
+ // Check if this is in a loop (gas griefing potential)
435
+ // (would need loop context tracking)
436
+
437
+ this.valueFlows.push(flow);
438
+ }
439
+ });
440
+
441
+ // Additionally: detect oracle-derived values used as amounts in value-moving calls.
442
+ // This enables oracle manipulation detectors to reduce pure-regex false positives.
443
+ if (!funcInfo.node || !funcInfo.node.body) continue;
444
+ const self = this;
445
+
446
+ parser.visit(funcInfo.node.body, {
447
+ FunctionCall(node) {
448
+ // Identify value-moving calls: transfer(to, amount), transferFrom(from,to,amount), mint(to,amount), burn(amount)
449
+ const callee = self.nodeToString(node.expression).toLowerCase();
450
+ if (!callee) return;
451
+
452
+ const isValueMoving =
453
+ /transferfrom\s*\(/.test(callee) ||
454
+ /transfer\s*\(/.test(callee) ||
455
+ /mint\s*\(/.test(callee) ||
456
+ /_mint\s*\(/.test(callee) ||
457
+ /burn\s*\(/.test(callee) ||
458
+ /_burn\s*\(/.test(callee);
459
+
460
+ if (!isValueMoving) return;
461
+
462
+ // Heuristic: last argument is commonly the amount
463
+ const args = node.arguments || [];
464
+ if (args.length === 0) return;
465
+ const amountNode = args[args.length - 1];
466
+ const amountExpr = self.nodeToString(amountNode);
467
+ const amountTaint = self.isExpressionTainted(amountNode, funcKey);
468
+
469
+ if (amountTaint && amountTaint.type === 'oracle_read') {
470
+ self.oracleValueFlows.push({
471
+ function: funcKey,
472
+ contract: funcInfo.contract,
473
+ operation: callee,
474
+ amountExpr,
475
+ oracleSource: amountTaint.source,
476
+ loc: node.loc
477
+ });
478
+ }
479
+ }
480
+ });
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Check if tainted data reaches dangerous sinks
486
+ */
487
+ checkDangerousSinks() {
488
+ const dangerousSinks = {
489
+ 'delegatecall': { severity: 'CRITICAL', description: 'Arbitrary code execution' },
490
+ 'selfdestruct': { severity: 'CRITICAL', description: 'Contract destruction' },
491
+ 'suicide': { severity: 'CRITICAL', description: 'Contract destruction (deprecated)' },
492
+ 'call': { severity: 'HIGH', description: 'External call with potential reentrancy' },
493
+ 'staticcall': { severity: 'MEDIUM', description: 'External read call' },
494
+ 'send': { severity: 'HIGH', description: 'ETH transfer' }
495
+ };
496
+
497
+ for (const [funcKey, funcInfo] of this.cfg.functions) {
498
+ funcInfo.externalCalls.forEach(call => {
499
+ const sinkInfo = dangerousSinks[call.type];
500
+ if (!sinkInfo) return;
501
+
502
+ // Check if call target is tainted
503
+ const targetTaint = this.checkCallTargetTaint(call, funcKey);
504
+
505
+ if (targetTaint) {
506
+ const exploitable = this.isExploitable(funcKey, call);
507
+
508
+ this.dataFlows.push({
509
+ source: targetTaint.source,
510
+ sourceType: targetTaint.type,
511
+ sink: call.type,
512
+ sinkDescription: sinkInfo.description,
513
+ function: funcKey,
514
+ contract: funcInfo.contract,
515
+ location: call.loc,
516
+ severity: sinkInfo.severity,
517
+ confidence: targetTaint.confidence,
518
+ exploitable: exploitable,
519
+ exploitabilityReason: exploitable ?
520
+ 'Function is externally callable without effective access control' :
521
+ 'Protected by access control or not externally callable',
522
+ taintPath: this.reconstructTaintPath(targetTaint, call, funcKey)
523
+ });
524
+ }
525
+ });
526
+ }
527
+ }
528
+
529
+ /**
530
+ * Check if call target is tainted
531
+ */
532
+ checkCallTargetTaint(call, funcKey) {
533
+ // Check if target address is tainted
534
+ if (call.target) {
535
+ // Direct parameter taint
536
+ const varKey = `${funcKey}.${call.target}`;
537
+ if (this.taintedVariables.has(varKey)) {
538
+ return this.taintedVariables.get(varKey);
539
+ }
540
+
541
+ // Check for msg.sender (always tainted)
542
+ if (call.target.includes('msg.sender')) {
543
+ return {
544
+ type: 'msg_property',
545
+ source: 'msg.sender',
546
+ confidence: 'HIGH'
547
+ };
548
+ }
549
+
550
+ // Check state variable taint
551
+ for (const [stateKey, taint] of this.stateVariableTaints) {
552
+ if (call.target.includes(stateKey.split('.')[1])) {
553
+ return taint;
554
+ }
555
+ }
556
+ }
557
+
558
+ return null;
559
+ }
560
+
561
+ /**
562
+ * Reconstruct the taint propagation path
563
+ */
564
+ reconstructTaintPath(taint, call, funcKey) {
565
+ const path = [];
566
+
567
+ path.push({
568
+ step: 'source',
569
+ description: `Taint originates from ${taint.source}`,
570
+ type: taint.type
571
+ });
572
+
573
+ if (taint.derivedFrom) {
574
+ path.push({
575
+ step: 'propagation',
576
+ description: `Derived through: ${taint.derivedFrom}`,
577
+ type: 'assignment'
578
+ });
579
+ }
580
+
581
+ path.push({
582
+ step: 'sink',
583
+ description: `Reaches dangerous operation: ${call.type}`,
584
+ location: call.loc
585
+ });
586
+
587
+ return path;
588
+ }
589
+
590
+ /**
591
+ * Determine if a taint flow is exploitable
592
+ */
593
+ isExploitable(funcKey, call) {
594
+ const funcInfo = this.cfg.functions.get(funcKey);
595
+ if (!funcInfo) return false;
596
+
597
+ // Private/internal functions not directly exploitable
598
+ if (funcInfo.visibility === 'private' || funcInfo.visibility === 'internal') {
599
+ return false;
600
+ }
601
+
602
+ // Check for effective access control
603
+ if (funcInfo.modifiers.length > 0) {
604
+ for (const modName of funcInfo.modifiers) {
605
+ const modKey = `${funcInfo.contract}.${modName}`;
606
+ const modInfo = this.cfg.modifiers.get(modKey);
607
+
608
+ if (modInfo && (modInfo.checksAccess || modInfo.checksOwnership || modInfo.checksRole)) {
609
+ return false;
610
+ }
611
+ }
612
+ }
613
+
614
+ return true;
615
+ }
616
+
617
+ /**
618
+ * Assess exploitability based on parameter type
619
+ */
620
+ assessParameterExploitability(paramType) {
621
+ if (!paramType) return 'MEDIUM';
622
+
623
+ // Address parameters - high exploitability for target manipulation
624
+ if (paramType.includes('address')) return 'HIGH';
625
+
626
+ // Bytes parameters - can contain arbitrary data
627
+ if (paramType.includes('bytes')) return 'HIGH';
628
+
629
+ // Integer parameters - potential for overflow/manipulation
630
+ if (paramType.includes('uint') || paramType.includes('int')) return 'MEDIUM';
631
+
632
+ return 'MEDIUM';
633
+ }
634
+
635
+ // Helper methods
636
+
637
+ nodeToString(node) {
638
+ if (!node) return '';
639
+
640
+ switch (node.type) {
641
+ case 'Identifier':
642
+ return node.name;
643
+ case 'MemberAccess':
644
+ return `${this.nodeToString(node.expression)}.${node.memberName}`;
645
+ case 'IndexAccess':
646
+ return `${this.nodeToString(node.base)}[${this.nodeToString(node.index)}]`;
647
+ case 'NumberLiteral':
648
+ return node.number;
649
+ case 'BinaryOperation':
650
+ return `${this.nodeToString(node.left)} ${node.operator} ${this.nodeToString(node.right)}`;
651
+ case 'FunctionCall':
652
+ const funcName = this.nodeToString(node.expression);
653
+ const args = (node.arguments || []).map(a => this.nodeToString(a)).join(', ');
654
+ return `${funcName}(${args})`;
655
+ default:
656
+ return `[${node.type}]`;
657
+ }
658
+ }
659
+
660
+ getAssignmentTarget(node) {
661
+ if (!node) return null;
662
+ if (node.type === 'Identifier') return node.name;
663
+ if (node.type === 'IndexAccess') return this.nodeToString(node.base);
664
+ if (node.type === 'MemberAccess') return this.nodeToString(node);
665
+ return null;
666
+ }
667
+
668
+ findTaintForWrite(write, funcKey) {
669
+ // Check if any tainted variable could flow to this write
670
+ for (const [varKey, taint] of this.taintedVariables) {
671
+ if (varKey.startsWith(funcKey)) {
672
+ return taint;
673
+ }
674
+ }
675
+ return null;
676
+ }
677
+
678
+ containsDivision(node) {
679
+ if (!node) return false;
680
+ if (node.type === 'BinaryOperation' && node.operator === '/') return true;
681
+ if (node.type === 'BinaryOperation') {
682
+ return this.containsDivision(node.left) || this.containsDivision(node.right);
683
+ }
684
+ return false;
685
+ }
686
+
687
+ tryGetConstantValue(node) {
688
+ if (!node) return null;
689
+ if (node.type === 'NumberLiteral') {
690
+ return parseFloat(node.number);
691
+ }
692
+ return null;
693
+ }
694
+
695
+ // Public API methods
696
+
697
+ isTainted(varName, funcKey) {
698
+ const varKey = `${funcKey}.${varName}`;
699
+ return this.taintedVariables.has(varKey);
700
+ }
701
+
702
+ getTaintInfo(varName, funcKey) {
703
+ const varKey = `${funcKey}.${varName}`;
704
+ return this.taintedVariables.get(varKey);
705
+ }
706
+
707
+ getDangerousFlows(funcKey) {
708
+ return this.dataFlows.filter(flow => flow.function === funcKey);
709
+ }
710
+
711
+ getExploitableFlows() {
712
+ return this.dataFlows.filter(flow => flow.exploitable);
713
+ }
714
+
715
+ getPrecisionLossRisks() {
716
+ return this.arithmeticOperations.filter(op => op.issues.length > 0);
717
+ }
718
+ }
719
+
720
+ module.exports = DataFlowAnalyzer;