# Startale: transient init flag persists, enabling account reinit drain

Startale ERC-7579 account's transient init flag persisted past the constructor, letting anyone reinitialize a freshly deployed account and drain its pre-funded USDC.

- Payout: Real-world hack
- Severity: critical
- Program: Startale
- Disclosed: 2026-09-16
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2026-09/Startale_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20260916-startale
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:business-logic

**Target:** ERC-7579 smart account (AccountProxy / StartaleSmartAccount / factory)
**Root cause:** AccountProxy constructor sets a transient-storage initializable flag via tstore, but EIP-1153 transient storage is transaction-scoped, not frame-scoped, so the flag survives the constructor. initializeAccount() gates only on that transient flag with no already-initialized guard, allowing a second unauthorized init that delegatecalls an attacker bootstrap.
**Steps:**
1. Attacker deploys a throwaway contract whose constructor runs the whole attack in one tx.
2. For each victim: call `factory.createAccount(legitInit, salt)` to deploy the pre-funded counterfactual account; constructor does `tstore(INIT_SLOT,1)` and runs legit init.
3. In the same tx, call `account.initializeAccount(evilInit)` where evilInit = `abi.encode(evilBootstrap, abi.encodeWithSelector(EvilBootstrap.sweep.selector, token, recipient))`.
4. requireInitializable() passes because INIT_SLOT is still 1; account delegatecalls evilBootstrap.sweep, which transfers the account's USDC balance to the attacker recipient.
**How found:** Researcher read verified source of AccountProxy and StartaleSmartAccount, noticed initializeAccount gated only on a transient-storage flag set in the constructor, and hypothesized EIP-1153 tstore persists for the whole transaction rather than the constructor frame; confirmed empirically with a PoC second-init call.
**Exploitation:** Zero-capital, no-signature attack. Victims pre-fund counterfactual account addresses before deployment. Attacker batches: deploy each account via factory (setting transient flag), then immediately re-call initializeAccount with a malicious bootstrap in the same tx, sweeping pre-funded USDC to a collector. Example tx drained 427.55 USDC across 23 accounts; campaign aggregate ~$2,876 across ~41 batches.
**Escalation:** Batched across many accounts per tx (40 AccountCreated, 23 drained in one tx) and repeated across ~41 batches to scale the campaign.
**Insight:** When auditing contracts using EIP-1153 transient storage for one-shot init/reentrancy guards, check whether the tstore is intended to be frame-scoped (constructor, single call) but is actually tx-scoped — any guard relying on tstore set in a sub-frame can be replayed later in the same transaction.
