When a moderator flags a pool as good-faith CORRUPTED with the wrong attacker address and later tries to correct it via reflag after 180 days, the corrected attacker cannot claim their bounty because the claim deadline is permanently anchored to the first flag's timestamp. The entire pool is then swept to recoveryAddress by anyone, and the legitimate whitehat receives nothing. The protocol exposes a pre-claim correction mechanism, but after the first good-faith bounty window expires, correcting the attacker address still succeeds while producing an attacker who cannot claim.
The ConfidencePool contract allows the moderator to reflag the outcome before any claims have been made. According to DESIGN.md Section 4, this is the documented correction mechanism, and it is gated by value movement (first claim), not by any timer:
DESIGN.md Section 4 — "The re-flag correction window closes on the first claim (by design)":
flagOutcomemay be re-flagged pre-claim so the moderator can fix a typo'd outcome/attacker before any participant locks in the wrong distribution. The window closing on the first claim (not on a grace timer) is deliberate
Additionally, DESIGN.md Section 12 describes the bounty flow and states the pool is reserved for the named whitehat for 180 days:
DESIGN.md Section 12 — "CORRUPTED bounty mechanics":
the pool is reserved for the named whitehat for
CORRUPTED_CLAIM_WINDOW(180 days), after which anyone sweeps the remainder torecoveryAddress.
Reading these two sections together, the expected behavior is: the moderator can correct a wrong attacker address at any time before the first claim, and the corrected whitehat should receive a usable claim path, or the correction should revert. Instead, the correction succeeds while leaving the corrected attacker unable to claim.
However, the code does not deliver this. The reflag guard in flagOutcome() correctly checks only claimsStarted:
So as long as no one has claimed, the moderator can reflag freely. When reflagging to good-faith CORRUPTED, the contract updates attacker and bountyEntitlement correctly:
However, the claim deadline is computed from _firstGoodFaithCorruptedAt, which is only set once and never reset:
The if (_firstGoodFaithCorruptedAt == 0) guard means this timestamp is permanently locked to whenever the first good-faith CORRUPTED flag was set. On any subsequent reflag, corruptedClaimDeadline is recomputed from the same original timestamp, so it always equals the original deadline regardless of when the correction happens.
When the corrected attacker tries to claim via claimAttackerBounty(), the deadline check blocks them:
Since block.timestamp is now past the original deadline, the call reverts with ClaimWindowExpired. At this point, sweepUnclaimedCorrupted() becomes callable by anyone because the deadline has passed:
The entire pool balance is sent to recoveryAddress and claimsStarted is set to true, permanently locking out any further reflag attempts.
There is also no workaround available to the moderator. No sequence of reflagging (e.g., toggling through SURVIVED and back to good-faith CORRUPTED) can reset _firstGoodFaithCorruptedAt because it is a write-once variable with no reset path anywhere in the contract.
DESIGN.md Section 4 says the reflag correction window closes on the first claim, not on a timer. Section 12 says the named whitehat is reserved the pool for 180 days. Together, the expected behavior is: if no one has claimed, the moderator can correct the attacker address and the corrected whitehat gets a usable bounty window.
What actually happens is different. After 180 days from the original flag, the moderator's correction succeeds on-chain (no revert, attacker and bountyEntitlement update correctly), but the corrected attacker's claimAttackerBounty() call reverts with ClaimWindowExpired because the deadline is still anchored to the original flag's timestamp. The entire pool (snapshotTotalStaked + snapshotTotalBonus) is then swept to recoveryAddress by anyone via sweepUnclaimedCorrupted(), and the legitimate whitehat receives nothing.
This scenario is realistic in cases where the wrong attacker address was provided due to a typo or miscommunication, and legal or identity verification processes to confirm the correct whitehat take longer than 180 days. The wrong address holder never claims (they may not even know about it), so the pool sits idle with claimsStarted = false the entire time, giving no on-chain signal that anything is wrong until the correction attempt fails.
If the protocol allows correcting the named attacker before any claim, the corrected attacker should receive a usable and predictable claim window. Currently, the window is inherited from the wrong attacker, which can be shortened or already expired.
Remove the if (_firstGoodFaithCorruptedAt == 0) guard so the timestamp resets on every good-faith CORRUPTED reflag:
The anti-extension concern (moderator gaming the deadline by toggling outcomes) is already fully addressed by claimsStarted. Once any participant claims, claimsStarted flips to true and flagOutcome() reverts with OutcomeAlreadySet. So the moderator can never extend the deadline past the point where value has moved. Resetting the timestamp on reflag simply ensures that a corrected attacker always gets a usable 180-day window, which is what DESIGN.md Section 4 promises.
The PoC walks through the exact scenario: moderator flags with the wrong attacker, 181 days pass without any claims, moderator corrects to the right attacker, and the corrected attacker's claim reverts while anyone can sweep the pool to recoveryAddress.
Create a file at test/audit/PoC_ReflagDeadline.t.sol and paste the code below, then run with:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.