solidity-argus 0.1.0

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 (131) hide show
  1. package/AGENTS.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +249 -0
  4. package/package.json +43 -0
  5. package/skills/INVENTORY.md +79 -0
  6. package/skills/README.md +56 -0
  7. package/skills/checklists/cyfrin-best-practices-runtime/SKILL.md +424 -0
  8. package/skills/checklists/cyfrin-best-practices-upgrades/SKILL.md +157 -0
  9. package/skills/checklists/cyfrin-defi-core/SKILL.md +373 -0
  10. package/skills/checklists/cyfrin-defi-integrations/SKILL.md +412 -0
  11. package/skills/checklists/cyfrin-gas/SKILL.md +55 -0
  12. package/skills/checklists/general-audit/SKILL.md +433 -0
  13. package/skills/methodology/audit-workflow/SKILL.md +129 -0
  14. package/skills/methodology/report-template/SKILL.md +190 -0
  15. package/skills/methodology/severity-classification/SKILL.md +179 -0
  16. package/skills/protocol-patterns/amm-dex/SKILL.md +229 -0
  17. package/skills/protocol-patterns/bridges-cross-chain/SKILL.md +317 -0
  18. package/skills/protocol-patterns/dao-governance/SKILL.md +281 -0
  19. package/skills/protocol-patterns/lending-borrowing/SKILL.md +221 -0
  20. package/skills/protocol-patterns/staking-vesting/SKILL.md +247 -0
  21. package/skills/references/exploit-reference/SKILL.md +259 -0
  22. package/skills/references/smartbugs-examples/SKILL.md +296 -0
  23. package/skills/vulnerability-patterns/access-control/SKILL.md +298 -0
  24. package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +59 -0
  25. package/skills/vulnerability-patterns/assert-violation/SKILL.md +59 -0
  26. package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +61 -0
  27. package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +55 -0
  28. package/skills/vulnerability-patterns/default-visibility/SKILL.md +62 -0
  29. package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +60 -0
  30. package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +59 -0
  31. package/skills/vulnerability-patterns/dos-revert/SKILL.md +72 -0
  32. package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +249 -0
  33. package/skills/vulnerability-patterns/floating-pragma/SKILL.md +51 -0
  34. package/skills/vulnerability-patterns/hash-collision/SKILL.md +52 -0
  35. package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +61 -0
  36. package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +60 -0
  37. package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +59 -0
  38. package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +61 -0
  39. package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +61 -0
  40. package/skills/vulnerability-patterns/logic-errors/SKILL.md +333 -0
  41. package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +60 -0
  42. package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +66 -0
  43. package/skills/vulnerability-patterns/off-by-one/SKILL.md +67 -0
  44. package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +252 -0
  45. package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +65 -0
  46. package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +61 -0
  47. package/skills/vulnerability-patterns/reentrancy/SKILL.md +266 -0
  48. package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +72 -0
  49. package/skills/vulnerability-patterns/signature-malleability/SKILL.md +59 -0
  50. package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +63 -0
  51. package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +52 -0
  52. package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +65 -0
  53. package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +61 -0
  54. package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +63 -0
  55. package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +56 -0
  56. package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +80 -0
  57. package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +69 -0
  58. package/skills/vulnerability-patterns/unused-variables/SKILL.md +70 -0
  59. package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +81 -0
  60. package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +77 -0
  61. package/skills/vulnerability-patterns/weird-tokens/SKILL.md +294 -0
  62. package/src/agents/argus-prompt.ts +407 -0
  63. package/src/agents/pythia-prompt.ts +134 -0
  64. package/src/agents/scribe-prompt.ts +87 -0
  65. package/src/agents/sentinel-prompt.ts +133 -0
  66. package/src/cli/cli-program.ts +67 -0
  67. package/src/cli/commands/doctor.ts +83 -0
  68. package/src/cli/commands/init.ts +46 -0
  69. package/src/cli/commands/install.ts +55 -0
  70. package/src/cli/index.ts +13 -0
  71. package/src/cli/tui-prompts.ts +75 -0
  72. package/src/cli/types.ts +9 -0
  73. package/src/config/index.ts +3 -0
  74. package/src/config/loader.ts +36 -0
  75. package/src/config/schema.ts +82 -0
  76. package/src/config/types.ts +4 -0
  77. package/src/constants/defaults.ts +6 -0
  78. package/src/create-hooks.ts +84 -0
  79. package/src/create-managers.ts +26 -0
  80. package/src/create-tools.ts +30 -0
  81. package/src/features/audit-enforcer/audit-enforcer.ts +34 -0
  82. package/src/features/audit-enforcer/index.ts +1 -0
  83. package/src/features/background-agent/background-manager.ts +200 -0
  84. package/src/features/background-agent/index.ts +1 -0
  85. package/src/features/context-monitor/context-monitor.ts +48 -0
  86. package/src/features/context-monitor/index.ts +4 -0
  87. package/src/features/context-monitor/tool-output-truncator.ts +17 -0
  88. package/src/features/error-recovery/index.ts +2 -0
  89. package/src/features/error-recovery/session-recovery.ts +27 -0
  90. package/src/features/error-recovery/tool-error-recovery.ts +35 -0
  91. package/src/features/index.ts +5 -0
  92. package/src/features/persistent-state/audit-state-manager.ts +121 -0
  93. package/src/features/persistent-state/index.ts +1 -0
  94. package/src/hooks/compaction-hook.ts +50 -0
  95. package/src/hooks/config-handler.ts +116 -0
  96. package/src/hooks/event-hook-v2.ts +93 -0
  97. package/src/hooks/event-hook.ts +74 -0
  98. package/src/hooks/hook-system.ts +9 -0
  99. package/src/hooks/index.ts +5 -0
  100. package/src/hooks/knowledge-sync-hook.ts +57 -0
  101. package/src/hooks/safe-create-hook.ts +15 -0
  102. package/src/hooks/system-prompt-hook.ts +126 -0
  103. package/src/hooks/tool-tracking-hook.ts +234 -0
  104. package/src/hooks/types.ts +16 -0
  105. package/src/index.ts +36 -0
  106. package/src/knowledge/scvd-client.ts +242 -0
  107. package/src/knowledge/scvd-index.ts +183 -0
  108. package/src/knowledge/scvd-sync.ts +85 -0
  109. package/src/managers/index.ts +1 -0
  110. package/src/managers/types.ts +85 -0
  111. package/src/plugin-interface.ts +38 -0
  112. package/src/shared/binary-utils.ts +63 -0
  113. package/src/shared/deep-merge.ts +71 -0
  114. package/src/shared/file-utils.ts +56 -0
  115. package/src/shared/index.ts +5 -0
  116. package/src/shared/jsonc-parser.ts +39 -0
  117. package/src/shared/logger.ts +36 -0
  118. package/src/state/audit-state.ts +27 -0
  119. package/src/state/finding-store.ts +126 -0
  120. package/src/state/plugin-state.ts +14 -0
  121. package/src/state/types.ts +61 -0
  122. package/src/tools/contract-analyzer-tool.ts +184 -0
  123. package/src/tools/forge-fuzz-tool.ts +311 -0
  124. package/src/tools/forge-test-tool.ts +397 -0
  125. package/src/tools/pattern-checker-tool.ts +337 -0
  126. package/src/tools/report-generator-tool.ts +308 -0
  127. package/src/tools/slither-tool.ts +465 -0
  128. package/src/tools/solodit-search-tool.ts +131 -0
  129. package/src/tools/sync-knowledge-tool.ts +116 -0
  130. package/src/utils/project-detector.ts +133 -0
  131. package/src/utils/solidity-parser.ts +174 -0
@@ -0,0 +1,298 @@
1
+ ---
2
+ name: access-control
3
+ description: Access-control exploit patterns and secure authorization approaches for privileged Solidity functions.
4
+ ---
5
+
6
+ <!-- Source: DeFiFoFum/fofum-solidity-skills (MIT) -->
7
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
8
+
9
+ # Access Control Exploits
10
+
11
+ ## Overview
12
+
13
+ Access control vulnerabilities occur when unauthorized users can execute privileged functions. These are often the simplest bugs but cause catastrophic losses.
14
+
15
+ **Total Losses:** $1B+ across DeFi
16
+
17
+ ---
18
+
19
+ ## Attack Patterns
20
+
21
+ ### 1. Missing Access Control
22
+
23
+ ```solidity
24
+ // VULNERABLE: Anyone can call
25
+ function mint(address to, uint256 amount) external {
26
+ _mint(to, amount);
27
+ }
28
+
29
+ // SECURE: Access control
30
+ function mint(address to, uint256 amount) external onlyMinter {
31
+ _mint(to, amount);
32
+ }
33
+ ```
34
+
35
+ ### 2. Unprotected Initialize
36
+
37
+ ```solidity
38
+ // VULNERABLE: Anyone can initialize
39
+ function initialize(address _admin) external {
40
+ admin = _admin;
41
+ }
42
+
43
+ // SECURE: Initializer modifier
44
+ function initialize(address _admin) external initializer {
45
+ admin = _admin;
46
+ }
47
+ ```
48
+
49
+ ### 3. tx.origin Authentication
50
+
51
+ ```solidity
52
+ // VULNERABLE: tx.origin can be manipulated
53
+ function withdraw() external {
54
+ require(tx.origin == owner, "Not owner");
55
+ payable(owner).transfer(address(this).balance);
56
+ }
57
+
58
+ // Attack: Trick owner into calling attacker's contract
59
+ contract Attacker {
60
+ function attack(VulnerableContract target) external {
61
+ target.withdraw(); // tx.origin is still the owner!
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### 4. Privilege Escalation
67
+
68
+ ```solidity
69
+ // VULNERABLE: Admin can make anyone admin
70
+ function setAdmin(address newAdmin) external onlyAdmin {
71
+ admin = newAdmin; // No checks on newAdmin
72
+ }
73
+
74
+ // SECURE: Multi-sig or timelock for admin changes
75
+ ```
76
+
77
+ ### 5. Signature Replay
78
+
79
+ ```solidity
80
+ // VULNERABLE: Same signature works multiple times
81
+ function executeWithSig(bytes calldata data, bytes calldata sig) external {
82
+ address signer = recover(keccak256(data), sig);
83
+ require(signer == admin, "Invalid sig");
84
+ // Execute...
85
+ }
86
+
87
+ // SECURE: Include nonce
88
+ mapping(address => uint256) public nonces;
89
+
90
+ function executeWithSig(bytes calldata data, uint256 nonce, bytes calldata sig) external {
91
+ require(nonce == nonces[msg.sender]++, "Invalid nonce");
92
+ bytes32 hash = keccak256(abi.encode(data, nonce, address(this), block.chainid));
93
+ address signer = recover(hash, sig);
94
+ require(signer == admin, "Invalid sig");
95
+ // Execute...
96
+ }
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Real Exploits
102
+
103
+ ### Ronin Bridge (Mar 2022) — $625M
104
+
105
+ **What happened:**
106
+ - Attackers compromised 5 of 9 validator keys
107
+ - 4 keys from Sky Mavis, 1 from Axie DAO
108
+ - Signed fraudulent withdrawals
109
+
110
+ **Root cause:** Insufficient key distribution + social engineering
111
+
112
+ **Lesson:** Multi-sig threshold must assume some keys will be compromised.
113
+
114
+ ### Parity Wallet (Nov 2017) — $150M Frozen
115
+
116
+ **What happened:**
117
+ - Library contract had unprotected `initWallet()`
118
+ - Random user called it, became owner
119
+ - Called `kill()`, destroyed library
120
+ - All wallets using library became unusable
121
+
122
+ **Root cause:** Unprotected initialize function on library contract
123
+
124
+ ```solidity
125
+ // The fatal function
126
+ function initWallet(address[] _owners, uint _required, uint _daylimit) {
127
+ // NO ACCESS CONTROL
128
+ initMultiowned(_owners, _required);
129
+ }
130
+ ```
131
+
132
+ **Lesson:** ALL contracts need access control, especially libraries.
133
+
134
+ ### Wormhole (Feb 2022) — $326M
135
+
136
+ **What happened:**
137
+ - Attacker exploited signature verification bug
138
+ - Forged guardian signatures
139
+ - Minted 120k ETH on Solana, bridged to Ethereum
140
+
141
+ **Root cause:** Improper signature verification in Solana program
142
+
143
+ ### Poly Network (Aug 2021) — $611M
144
+
145
+ **What happened:**
146
+ - Attacker exploited cross-chain message verification
147
+ - Changed the keeper address to attacker-controlled address
148
+ - Drained assets across multiple chains
149
+
150
+ **Root cause:** Insufficient validation of cross-chain calls
151
+
152
+ ---
153
+
154
+ ## Detection Checklist
155
+
156
+ ### Function-Level
157
+ - [ ] Does every state-changing function have appropriate access control?
158
+ - [ ] Are there functions without any modifiers that should have them?
159
+ - [ ] Is `onlyOwner`/`onlyAdmin` used consistently?
160
+ - [ ] Can critical functions be called by anyone?
161
+
162
+ ### Initialization
163
+ - [ ] Is `initialize()` protected with `initializer` modifier?
164
+ - [ ] Can initialize be called multiple times?
165
+ - [ ] Is the implementation contract also initialized?
166
+ - [ ] Are all state variables properly set in initialize?
167
+
168
+ ### Authentication
169
+ - [ ] Is `tx.origin` used anywhere? (Almost always wrong)
170
+ - [ ] Are signatures properly validated with nonce/chainId/address?
171
+ - [ ] Can signatures be replayed across chains or contracts?
172
+ - [ ] Is there proper domain separation in signatures?
173
+
174
+ ### Privilege Management
175
+ - [ ] Can admin change critical parameters without timelock?
176
+ - [ ] Is there a way to revoke compromised admin access?
177
+ - [ ] Are role changes logged with events?
178
+ - [ ] Is there role hierarchy that could be exploited?
179
+
180
+ ## Secure Patterns
181
+
182
+ ### OpenZeppelin AccessControl
183
+
184
+ ```solidity
185
+ import "@openzeppelin/contracts/access/AccessControl.sol";
186
+
187
+ contract Secure is AccessControl {
188
+ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
189
+
190
+ constructor() {
191
+ _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
192
+ }
193
+
194
+ function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
195
+ _mint(to, amount);
196
+ }
197
+ }
198
+ ```
199
+
200
+ ### Two-Step Ownership Transfer
201
+
202
+ ```solidity
203
+ address public pendingOwner;
204
+
205
+ function transferOwnership(address newOwner) external onlyOwner {
206
+ pendingOwner = newOwner;
207
+ }
208
+
209
+ function acceptOwnership() external {
210
+ require(msg.sender == pendingOwner, "Not pending owner");
211
+ owner = pendingOwner;
212
+ pendingOwner = address(0);
213
+ }
214
+ ```
215
+
216
+ ### Initializer Pattern
217
+
218
+ ```solidity
219
+ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
220
+
221
+ contract MyContract is Initializable {
222
+ address public admin;
223
+
224
+ /// @custom:oz-upgrades-unsafe-allow constructor
225
+ constructor() {
226
+ _disableInitializers(); // Protect implementation
227
+ }
228
+
229
+ function initialize(address _admin) external initializer {
230
+ admin = _admin;
231
+ }
232
+ }
233
+ ```
234
+
235
+ ---
236
+
237
+ ## References
238
+
239
+ - [SWC-105: Unprotected Ether Withdrawal](https://swcregistry.io/docs/SWC-105)
240
+ - [SWC-115: Authorization through tx.origin](https://swcregistry.io/docs/SWC-115)
241
+ - [OpenZeppelin Access Control](https://docs.openzeppelin.com/contracts/access-control)
242
+ - [Ronin Post-Mortem](https://roninblockchain.substack.com/p/community-alert-ronin-validators)
243
+
244
+ ## Detection Heuristics (kadenzipfel)
245
+
246
+ ## Preconditions
247
+ - Contract has functions that modify sensitive state (ownership, balances, fees, minting, pausing, protocol parameters)
248
+ - Those functions lack access control modifiers (`onlyOwner`, `onlyRole`, etc.) or `require(msg.sender == ...)` checks
249
+ - OR: access control exists but is incomplete (e.g., role assignments themselves are unprotected)
250
+
251
+ ## Vulnerable Pattern
252
+ ```solidity
253
+ address public owner;
254
+ uint256 public feeRate;
255
+
256
+ // Missing access control — anyone can call
257
+ function setFeeRate(uint256 newRate) external {
258
+ feeRate = newRate;
259
+ }
260
+
261
+ // Missing access control — anyone can take ownership
262
+ function setOwner(address newOwner) external {
263
+ owner = newOwner;
264
+ }
265
+
266
+ // Incomplete: role grant is unprotected
267
+ function grantRole(address user, bytes32 role) external {
268
+ // Missing: require(hasRole(ADMIN_ROLE, msg.sender))
269
+ _roles[role][user] = true;
270
+ }
271
+ ```
272
+
273
+ ## Detection Heuristics
274
+ 1. Identify all `external` and `public` functions that modify state variables
275
+ 2. For each, check if it has an access control modifier (`onlyOwner`, `onlyRole`, `whenNotPaused`, etc.) or an inline `require(msg.sender == ...)` check
276
+ 3. If a state-changing function has no access control, flag it — especially if it modifies ownership, balances, fees, or addresses
277
+ 4. Check that role/permission management functions themselves are protected (e.g., `grantRole` requires `ADMIN_ROLE`)
278
+ 5. Check `initialize()` functions in upgradeable contracts — they must have an `initializer` modifier to prevent re-initialization
279
+
280
+ ## False Positives
281
+ - The function is intentionally permissionless by design (e.g., `deposit()`, `claim()` where anyone should be able to call)
282
+ - Access control is enforced in an internal function that the external function always calls through
283
+ - The contract is an implementation behind a proxy, and access control is enforced at the proxy level
284
+
285
+ ## Remediation
286
+ - Add explicit access control to every state-changing function that should be restricted
287
+ - Use OpenZeppelin's `Ownable` or `AccessControl` for standardized role management
288
+ - Protect `initialize()` with the `initializer` modifier
289
+ - Apply the principle of least privilege: each function should require the minimum role necessary
290
+ ```solidity
291
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
292
+
293
+ contract Secure is Ownable {
294
+ function setFeeRate(uint256 newRate) external onlyOwner {
295
+ feeRate = newRate;
296
+ }
297
+ }
298
+ ```
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: arbitrary-storage-location
3
+ description: - Contract has a dynamic array in storage
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Write to Arbitrary Storage Location
8
+
9
+ ## Preconditions
10
+ - Contract has a dynamic array in storage
11
+ - User input controls the index used for writing to that array
12
+ - No bounds checking on the index before the write
13
+ - OR: assembly-level `sstore` with a user-controlled slot value
14
+
15
+ ## Vulnerable Pattern
16
+ ```solidity
17
+ uint256[] public data;
18
+ address public owner;
19
+
20
+ function write(uint256 index, uint256 value) external {
21
+ // User controls index — can compute an index that maps
22
+ // to any storage slot via the array's storage layout:
23
+ // array elements start at keccak256(slot_of_length)
24
+ // attacker calculates index to target owner's slot
25
+ data[index] = value;
26
+ }
27
+
28
+ // Assembly variant
29
+ function writeSlot(uint256 slot, uint256 value) external {
30
+ assembly {
31
+ sstore(slot, value) // Direct arbitrary storage write
32
+ }
33
+ }
34
+ ```
35
+
36
+ ## Detection Heuristics
37
+ 1. Search for dynamic array writes where the index comes from user input or function parameters
38
+ 2. Check if the index is bounds-checked before the write (e.g., `require(index < data.length)`)
39
+ 3. Search for `sstore` in assembly blocks — check if the slot parameter is user-controlled
40
+ 4. Search for `.length` assignments on dynamic arrays (Solidity <0.6.0 allowed `array.length = X`, enabling array expansion to reach any slot)
41
+ 5. If an unbounded array write exists, verify whether the array's keccak256-based slot layout could collide with other state variable slots
42
+
43
+ ## False Positives
44
+ - Array index is validated against `array.length` before writing
45
+ - The array uses `push()` only (indices are not user-controlled)
46
+ - Assembly `sstore` uses a hardcoded or internally computed slot
47
+ - Modern Solidity (>=0.6.0) prevents direct `.length` manipulation
48
+
49
+ ## Remediation
50
+ - Always bounds-check array indices: `require(index < data.length)`
51
+ - Use `push()` and `pop()` instead of direct index assignment for dynamic arrays
52
+ - Avoid exposing `sstore` with user-controlled slot values
53
+ - Use mappings instead of arrays when random-access writes are needed
54
+ ```solidity
55
+ function safeWrite(uint256 index, uint256 value) external {
56
+ require(index < data.length, "out of bounds");
57
+ data[index] = value;
58
+ }
59
+ ```
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: assert-violation
3
+ description: - Contract uses `assert()` statements
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Assert Violation
8
+
9
+ ## Preconditions
10
+ - Contract uses `assert()` statements
11
+ - The `assert` condition can be reached through valid program execution (not a true invariant)
12
+ - OR: `assert()` is used to validate user input or external call results instead of `require()`
13
+
14
+ ## Vulnerable Pattern
15
+ ```solidity
16
+ function transfer(address to, uint256 amount) external {
17
+ // WRONG: assert used for input validation — should be require
18
+ // In Solidity <0.8.0, this consumes ALL remaining gas on failure
19
+ assert(balances[msg.sender] >= amount);
20
+
21
+ balances[msg.sender] -= amount;
22
+ balances[to] += amount;
23
+ }
24
+
25
+ function withdraw() external {
26
+ uint256 bal = balances[msg.sender];
27
+ balances[msg.sender] = 0;
28
+ (bool success,) = msg.sender.call{value: bal}("");
29
+ // WRONG: assert used to check external call result
30
+ assert(success);
31
+ }
32
+ ```
33
+
34
+ ## Detection Heuristics
35
+ 1. Search for all `assert(` calls in the codebase
36
+ 2. For each, determine whether the condition is a true invariant (mathematically impossible to violate in a correct contract) or an input/state validation
37
+ 3. If the condition depends on user input, external call results, or mutable external state, flag it — it should be `require()` instead
38
+ 4. Check Solidity version: in <0.8.0, failing `assert` uses the `0xfe` INVALID opcode and consumes ALL remaining gas; in >=0.8.0, it reverts with `Panic(uint256)` error code `0x01` (refunds remaining gas but provides no custom message)
39
+ 5. If an `assert` can be triggered by an attacker, check if the gas consumption creates a griefing vector
40
+
41
+ ## False Positives
42
+ - The `assert` checks a genuine invariant (e.g., `assert(totalSupply == sumOfAllBalances)` after a provably correct operation)
43
+ - The condition is mathematically unreachable given the contract's logic
44
+ - Used in test code, not production contracts
45
+
46
+ ## Remediation
47
+ - Use `require()` for input validation and external call checks — it refunds remaining gas and accepts an error message
48
+ - Reserve `assert()` only for invariant checks that should never fail in a correct contract
49
+ - In Solidity >=0.8.4, prefer custom errors with `require` for gas-efficient error handling
50
+ ```solidity
51
+ // Correct: require for input validation
52
+ function transfer(address to, uint256 amount) external {
53
+ require(balances[msg.sender] >= amount, "insufficient balance");
54
+ balances[msg.sender] -= amount;
55
+ balances[to] += amount;
56
+ // assert is appropriate here: totalSupply should never change during transfer
57
+ assert(balances[msg.sender] + balances[to] == oldSum);
58
+ }
59
+ ```
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: asserting-contract-from-code-size
3
+ description: - Contract uses `extcodesize` or `address.code.length` to check whether an address is an EOA vs. a contract
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Asserting Contract from Code Size
8
+
9
+ ## Preconditions
10
+ - Contract uses `extcodesize` or `address.code.length` to check whether an address is an EOA vs. a contract
11
+ - This check gates access control, anti-bot logic, or security-sensitive operations
12
+ - The check assumes that code size == 0 means EOA
13
+
14
+ ## Vulnerable Pattern
15
+ ```solidity
16
+ modifier onlyEOA() {
17
+ // During constructor execution, extcodesize returns 0
18
+ // An attacker calling from their constructor bypasses this check
19
+ require(msg.sender.code.length == 0, "no contracts");
20
+ _;
21
+ }
22
+
23
+ function mint() external onlyEOA {
24
+ _mint(msg.sender, 1);
25
+ }
26
+
27
+ // Assembly variant
28
+ function isContract(address addr) internal view returns (bool) {
29
+ uint256 size;
30
+ assembly { size := extcodesize(addr) }
31
+ return size > 0; // Returns false during constructor
32
+ }
33
+ ```
34
+
35
+ ## Detection Heuristics
36
+ 1. Search for `extcodesize`, `.code.length`, or helper functions named `isContract`
37
+ 2. Check if the result is used to gate access (e.g., `require(... == 0)` to allow only EOAs)
38
+ 3. If used for EOA-only enforcement, flag it — contracts calling from their constructor have code size 0
39
+ 4. Also check for `tx.origin == msg.sender` as an alternative EOA check — flag it as incompatible with account abstraction (ERC-4337) and smart contract wallets
40
+ 5. Check if the "no contracts" logic protects anything security-sensitive (minting limits, anti-bot, etc.)
41
+
42
+ ## False Positives
43
+ - `extcodesize` is used to check if a contract EXISTS at an address (not to distinguish EOA vs contract)
44
+ - The check is combined with other protections that don't rely solely on code size
45
+ - The function has no security implications if called by a contract
46
+
47
+ ## Remediation
48
+ - There is no fully reliable on-chain method to distinguish EOAs from contracts
49
+ - Redesign logic to not depend on this distinction — make the system work correctly regardless of caller type
50
+ - If EOA-only behavior is truly needed, consider off-chain verification (e.g., signed messages from known EOAs)
51
+ - Remove `isContract` checks from security-critical paths
52
+ ```solidity
53
+ // Instead of gating by caller type, use per-address limits
54
+ mapping(address => bool) public hasMinted;
55
+
56
+ function mint() external {
57
+ require(!hasMinted[msg.sender], "already minted");
58
+ hasMinted[msg.sender] = true;
59
+ _mint(msg.sender, 1);
60
+ }
61
+ ```
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: authorization-txorigin
3
+ description: - Contract uses `tx.origin` for authorization or access control checks (e.g., `require(tx.origin == owner)`)
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Authorization Through tx.origin
8
+
9
+ ## Preconditions
10
+ - Contract uses `tx.origin` for authorization or access control checks (e.g., `require(tx.origin == owner)`)
11
+ - A legitimate owner/admin may interact with untrusted external contracts
12
+
13
+ ## Vulnerable Pattern
14
+ ```solidity
15
+ contract Wallet {
16
+ address public owner;
17
+
18
+ function transferTo(address to, uint256 amount) external {
19
+ // tx.origin is the EOA that initiated the entire tx chain
20
+ // If owner calls MaliciousContract, which calls Wallet.transferTo,
21
+ // tx.origin is still the owner — check passes
22
+ require(tx.origin == owner, "not owner");
23
+ payable(to).transfer(amount);
24
+ }
25
+ }
26
+
27
+ contract Attacker {
28
+ Wallet wallet;
29
+ // Owner calls this (e.g., via a phishing link)
30
+ fallback() external {
31
+ // tx.origin == owner because owner initiated the tx
32
+ wallet.transferTo(address(this), address(wallet).balance);
33
+ }
34
+ }
35
+ ```
36
+
37
+ ## Detection Heuristics
38
+ 1. Search for all instances of `tx.origin` in the codebase
39
+ 2. If `tx.origin` is used in a `require`, `if`, or comparison for authorization purposes, flag it
40
+ 3. Check if `tx.origin` is compared against privileged addresses (owner, admin, etc.)
41
+ 4. Note: `tx.origin == msg.sender` used to verify EOA status is a different pattern (see asserting-contract-from-code-size) — still flag it but for different reasons (breaks with account abstraction)
42
+
43
+ ## False Positives
44
+ - `tx.origin` used only for logging or analytics, not for authorization
45
+ - `tx.origin` used in combination with `msg.sender` checks where `msg.sender` is the primary authorization mechanism
46
+
47
+ ## Remediation
48
+ - Replace `tx.origin` with `msg.sender` for all authorization checks
49
+ - `msg.sender` reflects the immediate caller, not the transaction originator
50
+ ```solidity
51
+ function transferTo(address to, uint256 amount) external {
52
+ require(msg.sender == owner, "not owner"); // Immediate caller check
53
+ payable(to).transfer(amount);
54
+ }
55
+ ```
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: default-visibility
3
+ description: - Functions or state variables are declared without an explicit visibility specifier
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Default Visibility
8
+
9
+ ## Preconditions
10
+ - Functions or state variables are declared without an explicit visibility specifier
11
+ - For functions: Solidity <0.5.0 (compiler does not enforce explicit visibility; defaults to `public`)
12
+ - For state variables: any Solidity version (defaults to `internal`, but developer may have intended `private`)
13
+ - The function or variable performs sensitive operations or holds sensitive data
14
+
15
+ ## Vulnerable Pattern
16
+ ```solidity
17
+ // Solidity <0.5.0: function defaults to public
18
+ contract Wallet {
19
+ address owner;
20
+
21
+ // No visibility specified — defaults to public
22
+ // Anyone can call this and take ownership
23
+ function initWallet(address _owner) {
24
+ owner = _owner;
25
+ }
26
+
27
+ // Internal helper exposed as public by default
28
+ function _sendFunds(address to, uint256 amount) {
29
+ payable(to).transfer(amount);
30
+ }
31
+ }
32
+ ```
33
+
34
+ ## Detection Heuristics
35
+ 1. Check the Solidity version: if <0.5.0, search for functions without `public`, `external`, `internal`, or `private` keywords
36
+ 2. For any version, search for state variables without explicit visibility — they default to `internal`
37
+ 3. For each function without explicit visibility, check if it modifies state or performs sensitive operations — these are exposed as `public` by default
38
+ 4. Look for functions prefixed with `_` (convention for internal) that lack the `internal` keyword
39
+ 5. In Solidity >=0.5.0, the compiler requires function visibility — but state variable visibility is still optional
40
+
41
+ ## False Positives
42
+ - Solidity >=0.5.0 contracts: the compiler enforces function visibility, so missing function visibility won't compile
43
+ - State variable intended to be `internal` and correctly defaults to `internal`
44
+ - The function is genuinely intended to be `public`
45
+
46
+ ## Remediation
47
+ - Always specify explicit visibility for all functions and state variables
48
+ - Upgrade to Solidity >=0.5.0 where function visibility is compiler-enforced
49
+ - Review all functions to ensure visibility matches intended access level
50
+ ```solidity
51
+ contract Wallet {
52
+ address private owner;
53
+
54
+ function initWallet(address _owner) internal {
55
+ owner = _owner;
56
+ }
57
+
58
+ function _sendFunds(address to, uint256 amount) private {
59
+ payable(to).transfer(amount);
60
+ }
61
+ }
62
+ ```
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: delegatecall-untrusted-callee
3
+ description: - Contract uses `delegatecall`
4
+ ---
5
+ <!-- Source: kadenzipfel/smart-contract-vulnerabilities (MIT) -->
6
+
7
+ # Delegatecall to Untrusted Callee
8
+
9
+ ## Preconditions
10
+ - Contract uses `delegatecall`
11
+ - The target address of the `delegatecall` is derived from user input, function parameters, or a mutable state variable settable by non-admin users
12
+ - OR: a proxy pattern where the implementation address can be changed by unauthorized parties
13
+
14
+ ## Vulnerable Pattern
15
+ ```solidity
16
+ // User-controlled delegatecall target
17
+ function forward(address callee, bytes calldata data) external {
18
+ // Attacker supplies callee = malicious contract
19
+ // Malicious contract overwrites storage (e.g., slot 0 = owner)
20
+ (bool success,) = callee.delegatecall(data);
21
+ require(success);
22
+ }
23
+
24
+ // Proxy with unprotected implementation setter
25
+ function setImplementation(address _impl) external {
26
+ // Missing: require(msg.sender == admin)
27
+ implementation = _impl;
28
+ }
29
+ ```
30
+
31
+ ## Detection Heuristics
32
+ 1. Search for all `delegatecall` invocations in the codebase
33
+ 2. For each, trace the target address: is it hardcoded, immutable, admin-only settable, or user-influenced?
34
+ 3. If the target comes from a function parameter, calldata, or storage variable — check that only authorized roles can set it
35
+ 4. For proxy contracts, verify that `upgradeTo` / `setImplementation` functions have proper access control
36
+ 5. Check storage layout compatibility between the proxy and all possible implementation contracts
37
+ 6. Flag any generic forwarding function that passes user-supplied addresses to `delegatecall`
38
+
39
+ ## False Positives
40
+ - `delegatecall` target is hardcoded or immutable (e.g., `address immutable IMPL`)
41
+ - Target is set only in the constructor and cannot be changed
42
+ - Target is restricted to a whitelist of audited contracts
43
+ - Standard proxy patterns (EIP-1967, UUPS, TransparentProxy) with proper access control on upgrades
44
+
45
+ ## Remediation
46
+ - Restrict `delegatecall` targets to trusted, immutable, or admin-only-settable addresses
47
+ - Use established proxy patterns (OpenZeppelin TransparentProxy, UUPS) with proper access control
48
+ - Never expose `delegatecall` with a user-supplied target address
49
+ - Verify storage layout compatibility between proxy and implementation contracts using tools like OpenZeppelin's storage layout checker
50
+ ```solidity
51
+ // Safe: immutable implementation
52
+ address immutable implementation;
53
+ constructor(address _impl) {
54
+ implementation = _impl;
55
+ }
56
+ fallback() external payable {
57
+ (bool s,) = implementation.delegatecall(msg.data);
58
+ require(s);
59
+ }
60
+ ```