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,651 @@
1
+ const BaseDetector = require('./base-detector');
2
+
3
+ /**
4
+ * Proxy Contract Vulnerabilities Detector
5
+ * Detects critical issues in upgradeable contracts (UUPS, Transparent Proxies, EIP-1967)
6
+ *
7
+ * Detects:
8
+ * - Missing initialization protection (uninitialized proxy)
9
+ * - Storage collision in proxy patterns
10
+ * - Unauthorized upgrade functions
11
+ * - Missing implementation contract validation
12
+ * - UUPS-specific vulnerabilities (missing _authorizeUpgrade)
13
+ * - Transparent proxy selector clash
14
+ * - Storage layout incompatibility warnings
15
+ */
16
+ class ProxyVulnerabilitiesDetector extends BaseDetector {
17
+ constructor() {
18
+ super(
19
+ 'Proxy Contract Vulnerabilities',
20
+ 'Detects critical vulnerabilities in upgradeable proxy contracts (UUPS, Transparent, EIP-1967)',
21
+ 'CRITICAL'
22
+ );
23
+ this.currentContract = null;
24
+ this.proxyPatterns = {
25
+ uups: false,
26
+ transparent: false,
27
+ beacon: false,
28
+ diamond: false
29
+ };
30
+ this.hasInitializer = false;
31
+ this.hasInitialized = false;
32
+ this.upgradeFunctions = [];
33
+ this.storageSlots = new Set();
34
+ }
35
+
36
+ async detect(ast, sourceCode, fileName, cfg, dataFlow) {
37
+ this.findings = [];
38
+ this.ast = ast;
39
+ this.sourceCode = sourceCode;
40
+ this.fileName = fileName;
41
+ this.sourceLines = sourceCode.split('\n');
42
+ this.cfg = cfg;
43
+ this.dataFlow = dataFlow;
44
+
45
+ this.traverse(ast);
46
+
47
+ // Post-traversal analysis
48
+ this.analyzeProxyPatterns();
49
+
50
+ return this.findings;
51
+ }
52
+
53
+ visitContractDefinition(node) {
54
+ this.currentContract = node.name;
55
+ this.proxyPatterns = {
56
+ uups: false,
57
+ transparent: false,
58
+ beacon: false,
59
+ diamond: false
60
+ };
61
+ this.hasInitializer = false;
62
+ this.hasInitialized = false;
63
+ this.upgradeFunctions = [];
64
+ this.storageSlots = new Set();
65
+
66
+ // Check if contract is a proxy implementation
67
+ const contractNameLower = (node.name || '').toLowerCase();
68
+ const isProxyContract = contractNameLower.includes('proxy') ||
69
+ contractNameLower.includes('upgradeable') ||
70
+ contractNameLower.includes('implementation');
71
+
72
+ if (node.baseContracts && node.baseContracts.length > 0) {
73
+ node.baseContracts.forEach(base => {
74
+ const baseName = base.baseName.namePath || base.baseName.name;
75
+ if (this.isProxyBaseContract(baseName)) {
76
+ this.detectProxyPattern(baseName);
77
+ }
78
+ });
79
+ }
80
+
81
+ // Also check contract name for proxy patterns
82
+ if (isProxyContract) {
83
+ // Mark as potential proxy to enable upgrade function detection
84
+ this.proxyPatterns.transparent = true; // Default to transparent if name suggests proxy
85
+ }
86
+ }
87
+
88
+ visitFunctionDefinition(node) {
89
+ const funcName = node.name || '';
90
+ const funcCode = this.getCodeSnippet(node.loc);
91
+ const funcCodeLower = funcCode.toLowerCase();
92
+
93
+ // Detect initializer functions
94
+ if (funcName.toLowerCase().includes('initializ') ||
95
+ funcCodeLower.includes('initializer') ||
96
+ funcCodeLower.includes('__init')) {
97
+ this.hasInitializer = true;
98
+
99
+ // Check if initializer is protected
100
+ if (!this.hasInitializerProtection(funcCode, node)) {
101
+ this.addFinding({
102
+ title: 'Unprotected Initializer Function',
103
+ description: `Initializer function '${funcName}' lacks proper protection against multiple initialization. Proxy contracts must prevent re-initialization to avoid storage corruption.`,
104
+ location: `Contract: ${this.currentContract}, Function: ${funcName}`,
105
+ line: node.loc ? node.loc.start.line : 0,
106
+ column: node.loc ? node.loc.start.column : 0,
107
+ code: this.getCodeSnippet(node.loc),
108
+ severity: 'CRITICAL',
109
+ confidence: 'HIGH',
110
+ exploitable: true,
111
+ exploitabilityScore: 95,
112
+ attackVector: 'proxy-initialization',
113
+ recommendation: 'Use OpenZeppelin\'s Initializable pattern with initializer modifier, or check an initialized storage variable. Never allow re-initialization.',
114
+ references: [
115
+ 'https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable',
116
+ 'https://swcregistry.io/docs/SWC-118',
117
+ 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/Initializable.sol'
118
+ ],
119
+ foundryPoC: this.generateInitializerPoC(this.currentContract, funcName)
120
+ });
121
+ }
122
+ }
123
+
124
+ // Detect upgrade functions
125
+ if (this.isUpgradeFunction(funcName, funcCode)) {
126
+ this.upgradeFunctions.push({
127
+ name: funcName,
128
+ node: node,
129
+ code: funcCode
130
+ });
131
+
132
+ // Check for unauthorized upgrades
133
+ if (!this.hasUpgradeProtection(funcCode, node)) {
134
+ this.addFinding({
135
+ title: 'Unauthorized Upgrade Function',
136
+ description: `Upgrade function '${funcName}' lacks access control. Anyone can upgrade the proxy implementation, potentially stealing funds or breaking functionality.`,
137
+ location: `Contract: ${this.currentContract}, Function: ${funcName}`,
138
+ line: node.loc ? node.loc.start.line : 0,
139
+ column: node.loc ? node.loc.start.column : 0,
140
+ code: this.getCodeSnippet(node.loc),
141
+ severity: 'CRITICAL',
142
+ confidence: 'HIGH',
143
+ exploitable: true,
144
+ exploitabilityScore: 100,
145
+ attackVector: 'unauthorized-upgrade',
146
+ recommendation: 'Add access control (onlyOwner, onlyRole, etc.) to all upgrade functions. Consider using OpenZeppelin\'s UUPSUpgradeable or TransparentUpgradeableProxy.',
147
+ references: [
148
+ 'https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable',
149
+ 'https://swcregistry.io/docs/SWC-119'
150
+ ],
151
+ foundryPoC: this.generateUpgradePoC(this.currentContract, funcName)
152
+ });
153
+ }
154
+ }
155
+
156
+ // UUPS-specific: Check for _authorizeUpgrade
157
+ if (this.proxyPatterns.uups) {
158
+ if (funcName === '_authorizeUpgrade' || funcCodeLower.includes('_authorizeupgrade')) {
159
+ // Good - UUPS pattern requires this
160
+ return;
161
+ }
162
+ }
163
+ }
164
+
165
+ visitVariableDeclaration(node) {
166
+ // Track storage slots for collision detection
167
+ if (node.stateVariable && node.name) {
168
+ // Check for EIP-1967 storage slots
169
+ const code = this.getLineContent(node.loc ? node.loc.start.line : 0);
170
+ if (code.includes('bytes32') && code.includes('0x')) {
171
+ // Potential storage slot declaration
172
+ const slotMatch = code.match(/0x[0-9a-fA-F]{64}/);
173
+ if (slotMatch) {
174
+ this.storageSlots.add(slotMatch[0]);
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ visitMemberAccess(node) {
181
+ // Detect delegatecall usage (common in proxies)
182
+ if (node.memberName === 'delegatecall') {
183
+ // Get the full function context, not just the member access
184
+ const lineNum = node.loc ? node.loc.start.line : 0;
185
+ // Look for the containing function - get more context
186
+ let funcCode = '';
187
+
188
+ // Try to get more context - check surrounding lines for fallback/receive
189
+ let foundFallback = false;
190
+ for (let i = Math.max(1, lineNum - 30); i <= Math.min(this.sourceLines.length, lineNum + 10); i++) {
191
+ const line = this.sourceLines[i - 1] || '';
192
+ if (line.includes('fallback') || line.includes('receive')) {
193
+ foundFallback = true;
194
+ // This is in a fallback/receive function - get the full function
195
+ const funcStart = Math.max(1, i - 2);
196
+ const funcEnd = Math.min(this.sourceLines.length, i + 40);
197
+ funcCode = this.sourceLines.slice(funcStart - 1, funcEnd).join('\n');
198
+ break;
199
+ }
200
+ }
201
+
202
+ // If we didn't find fallback, get context around the delegatecall
203
+ if (!funcCode) {
204
+ const start = Math.max(0, lineNum - 15);
205
+ const end = Math.min(this.sourceLines.length, lineNum + 5);
206
+ funcCode = this.sourceLines.slice(start, end).join('\n');
207
+ }
208
+
209
+ if (!this.hasDelegatecallProtection(funcCode)) {
210
+ this.addFinding({
211
+ title: 'Unprotected Delegatecall in Proxy',
212
+ description: 'Delegatecall detected without proper validation of target address. Malicious implementation contracts can corrupt proxy storage.',
213
+ location: `Contract: ${this.currentContract}`,
214
+ line: node.loc ? node.loc.start.line : 0,
215
+ column: node.loc ? node.loc.start.column : 0,
216
+ code: this.getCodeSnippet(node.loc),
217
+ severity: 'CRITICAL',
218
+ confidence: 'MEDIUM',
219
+ exploitable: true,
220
+ exploitabilityScore: 90,
221
+ attackVector: 'delegatecall-exploit',
222
+ recommendation: 'Validate implementation address before delegatecall. Use OpenZeppelin\'s proxy patterns or ensure implementation is from trusted source.',
223
+ references: [
224
+ 'https://swcregistry.io/docs/SWC-112',
225
+ 'https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable'
226
+ ]
227
+ });
228
+ }
229
+ }
230
+
231
+ // Detect storage slot access (EIP-1967)
232
+ if (node.memberName === 'sload' || node.memberName === 'sstore') {
233
+ // Check for EIP-1967 storage slots
234
+ const code = this.getCodeSnippet(node.loc);
235
+ const eip1967Slots = [
236
+ '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc', // IMPLEMENTATION_SLOT
237
+ '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103', // ADMIN_SLOT
238
+ '0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7' // BEACON_SLOT
239
+ ];
240
+
241
+ eip1967Slots.forEach(slot => {
242
+ if (code.includes(slot)) {
243
+ // This is using EIP-1967 - check for proper usage
244
+ if (!this.hasEIP1967Protection(code)) {
245
+ this.addFinding({
246
+ title: 'Unprotected EIP-1967 Storage Access',
247
+ description: `Direct access to EIP-1967 storage slot detected without proper access control. This could allow unauthorized upgrades or storage corruption.`,
248
+ location: `Contract: ${this.currentContract}`,
249
+ line: node.loc ? node.loc.start.line : 0,
250
+ column: node.loc ? node.loc.start.column : 0,
251
+ code: this.getCodeSnippet(node.loc),
252
+ severity: 'HIGH',
253
+ confidence: 'MEDIUM',
254
+ exploitable: true,
255
+ exploitabilityScore: 75,
256
+ attackVector: 'storage-slot-manipulation',
257
+ recommendation: 'Use OpenZeppelin\'s ERC1967Upgrade or similar library for safe storage slot access. Never directly manipulate EIP-1967 slots without access control.',
258
+ references: [
259
+ 'https://eips.ethereum.org/EIPS/eip-1967',
260
+ 'https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable'
261
+ ]
262
+ });
263
+ }
264
+ }
265
+ });
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Analyze proxy patterns after traversal
271
+ */
272
+ analyzeProxyPatterns() {
273
+ // UUPS pattern requires _authorizeUpgrade
274
+ if (this.proxyPatterns.uups) {
275
+ const hasAuthorizeUpgrade = this.sourceCode.toLowerCase().includes('_authorizeupgrade');
276
+ if (!hasAuthorizeUpgrade) {
277
+ this.addFinding({
278
+ title: 'Missing _authorizeUpgrade in UUPS Pattern',
279
+ description: 'Contract appears to use UUPS pattern but lacks _authorizeUpgrade function. This is required for UUPS proxies to prevent unauthorized upgrades.',
280
+ location: `Contract: ${this.currentContract}`,
281
+ line: 1,
282
+ column: 0,
283
+ code: this.sourceCode.substring(0, 200),
284
+ severity: 'CRITICAL',
285
+ confidence: 'HIGH',
286
+ exploitable: true,
287
+ exploitabilityScore: 95,
288
+ attackVector: 'uups-missing-authorization',
289
+ recommendation: 'Implement _authorizeUpgrade function with proper access control. Use OpenZeppelin\'s UUPSUpgradeable contract.',
290
+ references: [
291
+ 'https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable',
292
+ 'https://eips.ethereum.org/EIPS/eip-1822'
293
+ ]
294
+ });
295
+ }
296
+ }
297
+
298
+ // Check for storage collision risks
299
+ if (this.storageSlots.size > 0) {
300
+ // Warn about potential storage layout issues
301
+ const hasStorageLayout = this.sourceCode.includes('StorageLayout') ||
302
+ this.sourceCode.includes('storage gap');
303
+ if (!hasStorageLayout && this.proxyPatterns.uups) {
304
+ this.addFinding({
305
+ title: 'Potential Storage Layout Collision',
306
+ description: 'Proxy implementation may have storage layout conflicts. Upgrading to a new implementation with different storage layout can corrupt data.',
307
+ location: `Contract: ${this.currentContract}`,
308
+ line: 1,
309
+ column: 0,
310
+ code: this.sourceCode.substring(0, 200),
311
+ severity: 'HIGH',
312
+ confidence: 'MEDIUM',
313
+ exploitable: false,
314
+ exploitabilityScore: 50,
315
+ attackVector: 'storage-collision',
316
+ recommendation: 'Use storage gaps (e.g., uint256[50] __gap) in base contracts. Document storage layout. Use OpenZeppelin\'s storage layout validation tools.',
317
+ references: [
318
+ 'https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps',
319
+ 'https://swcregistry.io/docs/SWC-120'
320
+ ]
321
+ });
322
+ }
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Check if contract inherits from proxy base contracts
328
+ */
329
+ isProxyBaseContract(baseName) {
330
+ const proxyBases = [
331
+ 'UUPSUpgradeable',
332
+ 'TransparentUpgradeableProxy',
333
+ 'ERC1967Proxy',
334
+ 'BeaconProxy',
335
+ 'Diamond',
336
+ 'DiamondProxy',
337
+ 'UpgradeableProxy',
338
+ 'Proxy'
339
+ ];
340
+
341
+ return proxyBases.some(base =>
342
+ baseName.toLowerCase().includes(base.toLowerCase())
343
+ );
344
+ }
345
+
346
+ /**
347
+ * Detect which proxy pattern is being used
348
+ */
349
+ detectProxyPattern(baseName) {
350
+ const nameLower = baseName.toLowerCase();
351
+ if (nameLower.includes('uups')) {
352
+ this.proxyPatterns.uups = true;
353
+ } else if (nameLower.includes('transparent')) {
354
+ this.proxyPatterns.transparent = true;
355
+ } else if (nameLower.includes('beacon')) {
356
+ this.proxyPatterns.beacon = true;
357
+ } else if (nameLower.includes('diamond')) {
358
+ this.proxyPatterns.diamond = true;
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Check if function is an upgrade function
364
+ */
365
+ isUpgradeFunction(funcName, code) {
366
+ const nameLower = (funcName || '').toLowerCase();
367
+ const codeLower = code.toLowerCase();
368
+
369
+ // Skip fallback and receive functions - these are for delegation, not upgrades
370
+ if (nameLower === 'fallback' || nameLower === '' || nameLower === 'receive') {
371
+ return false;
372
+ }
373
+
374
+ // Check for explicit upgrade function names
375
+ const upgradeKeywords = ['upgrade', 'upgradeto', 'upgradetoandcall', 'changeimplementation',
376
+ 'setimplementation', 'updateimplementation'];
377
+ const hasUpgradeKeyword = upgradeKeywords.some(keyword =>
378
+ nameLower.includes(keyword)
379
+ );
380
+
381
+ // Check for implementation assignment pattern (not just usage)
382
+ // e.g., implementation = newImpl, _setImplementation(newImpl)
383
+ const hasImplementationWrite = /implementation\s*=|_setimplementation\s*\(|_upgrade\s*\(/i.test(code);
384
+
385
+ const isUpgrade = hasUpgradeKeyword || hasImplementationWrite;
386
+
387
+ // Only consider it an upgrade function if it's in a contract that looks like a proxy
388
+ if (isUpgrade) {
389
+ const contractNameLower = (this.currentContract || '').toLowerCase();
390
+ const isProxyContract = this.proxyPatterns.uups || this.proxyPatterns.transparent ||
391
+ this.proxyPatterns.beacon || this.proxyPatterns.diamond ||
392
+ contractNameLower.includes('proxy') ||
393
+ contractNameLower.includes('upgradeable') ||
394
+ contractNameLower.includes('implementation');
395
+
396
+ if (!isProxyContract) {
397
+ // Not a proxy contract, skip
398
+ return false;
399
+ }
400
+ }
401
+
402
+ return isUpgrade;
403
+ }
404
+
405
+ /**
406
+ * Check if initializer has protection
407
+ */
408
+ hasInitializerProtection(code, node) {
409
+ const codeLower = code.toLowerCase();
410
+
411
+ // Check for initializer modifier
412
+ if (node.modifiers && node.modifiers.some(m =>
413
+ m.name && m.name.toLowerCase().includes('initializer')
414
+ )) {
415
+ return true;
416
+ }
417
+
418
+ // Check for initialized variable check
419
+ if (codeLower.includes('initialized') &&
420
+ (codeLower.includes('require') || codeLower.includes('revert'))) {
421
+ return true;
422
+ }
423
+
424
+ // Check for initializer() modifier usage
425
+ if (codeLower.includes('initializer()') || codeLower.includes('!initialized')) {
426
+ return true;
427
+ }
428
+
429
+ return false;
430
+ }
431
+
432
+ /**
433
+ * Check if upgrade function has protection
434
+ */
435
+ hasUpgradeProtection(code, node) {
436
+ const codeLower = code.toLowerCase();
437
+
438
+ // Check for access control modifiers in the function definition
439
+ const accessControlModifiers = ['onlyowner', 'onlyrole', 'onlyadmin', 'onlygovernance', 'onlyauthorized'];
440
+ if (node.modifiers && node.modifiers.length > 0) {
441
+ const hasAccessControl = node.modifiers.some(m => {
442
+ if (!m || !m.name) return false;
443
+ const modName = m.name.toLowerCase();
444
+ return accessControlModifiers.some(ac => modName.includes(ac));
445
+ });
446
+ if (hasAccessControl) {
447
+ return true;
448
+ }
449
+ }
450
+
451
+ // Also check the function signature in code for modifiers
452
+ // Pattern: function upgrade(...) public onlyAdmin
453
+ // Modifiers can be on the same line or next line after function declaration
454
+ const modifierPatterns = [
455
+ /\bfunction\s+\w+\s*\([^)]*\)\s+[^{]*(onlyOwner|onlyRole|onlyAdmin|onlyGovernance|onlyAuthorized)/i,
456
+ /\bfunction\s+\w+\s*\([^)]*\)\s+public\s+(onlyOwner|onlyRole|onlyAdmin|onlyGovernance|onlyAuthorized)/i,
457
+ /\bfunction\s+\w+\s*\([^)]*\)\s+external\s+(onlyOwner|onlyRole|onlyAdmin|onlyGovernance|onlyAuthorized)/i
458
+ ];
459
+
460
+ if (modifierPatterns.some(pattern => pattern.test(code))) {
461
+ return true;
462
+ }
463
+
464
+ // Check if modifier appears near the function declaration (within first few lines)
465
+ const lines = code.split('\n');
466
+ let foundFunction = false;
467
+ for (let i = 0; i < Math.min(5, lines.length); i++) {
468
+ const line = lines[i];
469
+ if (line.includes('function') && line.includes('upgrade')) {
470
+ foundFunction = true;
471
+ }
472
+ if (foundFunction && /\b(onlyOwner|onlyRole|onlyAdmin|onlyGovernance|onlyAuthorized)\b/i.test(line)) {
473
+ return true;
474
+ }
475
+ }
476
+
477
+ // Check for require statements with access control in the function body
478
+ const accessControlPatterns = [
479
+ /require\s*\(\s*msg\.sender\s*==\s*owner/i,
480
+ /require\s*\(\s*msg\.sender\s*==\s*admin/i,
481
+ /require\s*\(\s*hasRole/i,
482
+ /require\s*\(\s*_authorizeUpgrade/i,
483
+ /\bonlyOwner\b/i,
484
+ /\bonlyRole\b/i,
485
+ /\bonlyAdmin\b/i
486
+ ];
487
+
488
+ // Also check if the function code contains modifier usage
489
+ if (accessControlPatterns.some(pattern => pattern.test(code))) {
490
+ return true;
491
+ }
492
+
493
+ // Check if there's a require with approvedImplementations or similar whitelist
494
+ if (codeLower.includes('require') &&
495
+ (codeLower.includes('approvedimplementations') ||
496
+ codeLower.includes('trustedimplementations') ||
497
+ codeLower.includes('whitelist'))) {
498
+ return true;
499
+ }
500
+
501
+ return false;
502
+ }
503
+
504
+ /**
505
+ * Check if delegatecall has protection
506
+ */
507
+ hasDelegatecallProtection(code) {
508
+ const codeLower = code.toLowerCase();
509
+
510
+ // Check if it's in a fallback function (standard proxy pattern)
511
+ // Fallback functions with delegatecall are typically secure proxy patterns
512
+ if (codeLower.includes('fallback') || codeLower.includes('receive')) {
513
+ // Check if implementation is validated
514
+ if (codeLower.includes('require') && (codeLower.includes('implementation') || codeLower.includes('impl'))) {
515
+ return true;
516
+ }
517
+ // Assembly delegatecall in fallback is standard proxy pattern
518
+ // Check if there's a require before the delegatecall
519
+ const lines = code.split('\n');
520
+ let foundRequire = false;
521
+ let foundDelegatecall = false;
522
+ for (const line of lines) {
523
+ const lineLower = line.toLowerCase();
524
+ if (lineLower.includes('require') && (lineLower.includes('implementation') || lineLower.includes('impl'))) {
525
+ foundRequire = true;
526
+ }
527
+ if (lineLower.includes('delegatecall')) {
528
+ foundDelegatecall = true;
529
+ // If we found delegatecall and there was a require before, it's protected
530
+ if (foundRequire) {
531
+ return true;
532
+ }
533
+ }
534
+ }
535
+ // Assembly delegatecall in fallback with implementation variable is standard pattern
536
+ if (codeLower.includes('assembly') && codeLower.includes('delegatecall') &&
537
+ (codeLower.includes('implementation') || codeLower.includes('impl'))) {
538
+ return true;
539
+ }
540
+ }
541
+
542
+ // Should validate implementation address
543
+ const validationPatterns = [
544
+ /require\s*\(\s*.*implementation/i,
545
+ /require\s*\(\s*.*impl\s*!=\s*address\(0\)/i,
546
+ /require\s*\(\s*.*code\.length\s*>\s*0/i,
547
+ /isContract\s*\(/i,
548
+ /address\(.*\)\.code\.length/i,
549
+ /approvedImplementations\[/i,
550
+ /trustedImplementations\[/i
551
+ ];
552
+
553
+ return validationPatterns.some(pattern => pattern.test(codeLower));
554
+ }
555
+
556
+ /**
557
+ * Check if EIP-1967 storage access has protection
558
+ */
559
+ hasEIP1967Protection(code) {
560
+ const codeLower = code.toLowerCase();
561
+
562
+ // Should have access control
563
+ return /onlyOwner|onlyRole|_authorizeUpgrade|require.*msg\.sender/i.test(codeLower);
564
+ }
565
+
566
+ /**
567
+ * Generate Foundry PoC for initializer exploit
568
+ */
569
+ generateInitializerPoC(contractName, funcName) {
570
+ return `// SPDX-License-Identifier: MIT
571
+ pragma solidity ^0.8.0;
572
+
573
+ import "forge-std/Test.sol";
574
+
575
+ /**
576
+ * Proof of Concept: Unprotected Initializer Exploit
577
+ * Target: ${contractName}.${funcName}()
578
+ * Attack Vector: Re-initialize proxy to corrupt storage
579
+ */
580
+ contract InitializerExploit is Test {
581
+ address constant PROXY = address(0); // ${contractName} proxy address
582
+ address attacker = address(this);
583
+
584
+ function testExploit() public {
585
+ // 1. Call initializer multiple times to corrupt storage
586
+ // ${contractName}(PROXY).${funcName}(...);
587
+
588
+ // 2. First call sets owner to legitimate address
589
+ // Second call can overwrite storage if unprotected
590
+
591
+ // 3. If storage is corrupted, attacker can:
592
+ // - Change owner to attacker address
593
+ // - Reset balances
594
+ // - Corrupt critical state variables
595
+
596
+ // Assert storage corruption
597
+ // assertEq(owner, attacker);
598
+ }
599
+ }`;
600
+ }
601
+
602
+ /**
603
+ * Generate Foundry PoC for unauthorized upgrade
604
+ */
605
+ generateUpgradePoC(contractName, funcName) {
606
+ return `// SPDX-License-Identifier: MIT
607
+ pragma solidity ^0.8.0;
608
+
609
+ import "forge-std/Test.sol";
610
+
611
+ /**
612
+ * Proof of Concept: Unauthorized Upgrade Exploit
613
+ * Target: ${contractName}.${funcName}()
614
+ * Attack Vector: Upgrade to malicious implementation
615
+ */
616
+ contract UpgradeExploit is Test {
617
+ address constant PROXY = address(0); // ${contractName} proxy address
618
+ MaliciousImplementation maliciousImpl;
619
+
620
+ function setUp() public {
621
+ maliciousImpl = new MaliciousImplementation();
622
+ }
623
+
624
+ function testExploit() public {
625
+ // 1. Deploy malicious implementation
626
+ // MaliciousImplementation impl = new MaliciousImplementation();
627
+
628
+ // 2. Call upgrade function without authorization
629
+ // ${contractName}(PROXY).${funcName}(address(impl));
630
+
631
+ // 3. Malicious implementation can:
632
+ // - Steal all funds via selfdestruct
633
+ // - Change owner
634
+ // - Break functionality
635
+
636
+ // Assert exploit succeeded
637
+ // assertEq(address(PROXY).balance, 0);
638
+ }
639
+ }
640
+
641
+ contract MaliciousImplementation {
642
+ function initialize() external {
643
+ // Steal funds
644
+ selfdestruct(payable(msg.sender));
645
+ }
646
+ }`;
647
+ }
648
+ }
649
+
650
+ module.exports = ProxyVulnerabilitiesDetector;
651
+