The transfer to msg.sender executes before balances is decremented, so a contract receiving it can re-enter emergencyWithdraw and drain the vault in one transaction. Move the state update above the call, or apply the existing nonReentrant modifier.
- (bool ok, ) = msg.sender.call{value: amount}("");+ balances[msg.sender] -= amount;
