Severity: Low ; requires moderator error + reflag, but silently shortens attacker deadline
Affected: ConfidencePool.flagOutcome at src/ConfidencePool.sol:364-372
Likelihood × Impact: Likelihood: Low × Impact: Medium
_firstGoodFaithCorruptedAt never resets across reflags. A moderator who corrects from CORRUPTED to SURVIVED and back silently shortens the attacker's 180-day bounty window.
flagOutcome at lines 363-375 sets the good-faith CORRUPTED deadline:
The _firstGoodFaithCorruptedAt variable is written only when it is zero (line 364). Once set, it persists through all subsequent reflags ; including reflags to SURVIVED (which do not reset it to zero because the else branch at line 373 only resets corruptedClaimDeadline, not _firstGoodFaithCorruptedAt).
When the moderator reflags back to good-faith CORRUPTED, _firstGoodFaithCorruptedAt is non-zero (from the first flag), so the if at line 364 is skipped, and corruptedClaimDeadline is recomputed from the STALE first-flag timestamp. The attacker's effective claim window shrinks.
Nominated attacker X at T=0. Moderator flags gf-CORRUPTED with attacker X. _firstGoodFaithCorruptedAt = 0 + block.timestamp = T0. corruptedClaimDeadline = T0 + 180 days.
Moderator corrects to SURVIVED at T=90 days. Realizes the breach was out-of-scope. Reflags SURVIVED. _firstGoodFaithCorruptedAt stays at T0 (not reset). corruptedClaimDeadline set to 0 (line 374).
New evidence emerges at T=150 days. Breach was actually in-scope. Moderator reflags gf-CORRUPTED with attacker X again. _firstGoodFaithCorruptedAt is non-zero (still T0), so skipped. corruptedClaimDeadline = T0 + 180 days.
Attacker X sees the new CORRUPTED flag and tries to claim. The deadline is T0+180, which is only 30 days away (from T150). If they don't claim within 30 days, they lose the bounty ; despite the reflag having just happened.
The attacker's window is silently shortened from 180 days to 30 days, with no event or notification of the reduced deadline.
If the attacker delays their claim based on the expectation of a full 180-day window from the most recent flag, they may find the window already expired. The bounty (up to snapshotTotalStaked + snapshotTotalBonus) is forfeited ; sweepUnclaimedCorrupted sweeps it to recoveryAddress after the deadline passes.
For a pool with 100 ETH staked + 50 ETH bonus, the attacker could lose 150 ETH if they wait more than the shortened window.
ConfidencePool.sol is listed in the contest scope. The flagOutcome function is callable by the moderator. The _firstGoodFaithCorruptedAt variable is not documented in docs/DESIGN.md; the code comment at lines 368-370 describes the behavior as intentional but no external documentation exists.
CodeHawks severity rules: Low findings are issues not exploitable under normal circumstances but representing poor practices or deviations from best practices that could theoretically lead to issues.
This finding: requires moderator error (reflag cycle), which is abnormal operational behavior. The impact is medium (attacker loses bounty), but likelihood depends on moderator making errors. Severity: Low.
No prior audit reports flag _firstGoodFaithCorruptedAt persistence. The inline comment at lines 368-370 says "Reuses the original window on re-entry ; which may already be in the past, leaving nothing to claim. Intended: the deadline must never be extendable." This comment describes the intent but the behavior is not in DESIGN.md. The finding is novel: it demonstrates a concrete path where this persistence causes harm.
Moderator commits an error that requires reflagging from good-faith CORRUPTED to a non-CORRUPTED outcome (SURVIVED or bad-faith CORRUPTED). Likelihood: moderator is a protocol DAO with governance ; error probability is non-zero but low.
Moderator later reflags back to good-faith CORRUPTED with the same attacker. Enough time passes that the original 180-day window is partially consumed. Likelihood: requires two moderator actions ; possible if new evidence emerges.
Attacker observes the second flag and expects a fresh 180-day window, delaying their claim past the original deadline. Likelihood: attacker should monitor on-chain; a cautious attacker claims immediately. However, the contract provides no event or notification warning of the reduced window.
Historical occurrence: No reflag cycles have been observed on deployed pools (code is pre-launch). The testHunt_reflagCorruptedToSurvivedSavesStakers test in BughuntHunt6 confirms reflag works but does not test the deadline-shortening scenario.
Reset _firstGoodFaithCorruptedAt to 0 when the outcome is changed away from good-faith CORRUPTED:
Or, alternatively, always update _firstGoodFaithCorruptedAt to the current timestamp on each good-faith CORRUPTED flag:
The comment at lines 368-370 argues that the deadline should never be extendable. This is correct ; a reflag should not EXTEND the deadline. But resetting _firstGoodFaithCorruptedAt on reflag ensures the window is measured from the current flag, not a historical one. The concern about extension is addressed by the fact that each reflag consumes the time between flags ; the attacker cannot extend the window, only the moderator can restart it (by reflagging).
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.