fix(enm): block validator creation against a retired pod [audit finding 4, MED] - #488
Merged
0xpanicError merged 2 commits intoAug 11, 2026
Conversation
disablePod does not clear ownerToPod, so the resolver keeps returning a retired pod as the credential target. All three StakingManager creation paths would then bake 0x02 credentials pointing at a dead pod, and the resulting validator could never verify credentials or checkpoint - stranding its 32 ETH. The PR also removed the getEigenPod()==0 guard from registerBeaconValidators, so nothing else blocked this. Make the validated withdrawalCredentialTarget resolver revert PodRetired when the pod reports restakingDisabled(). The check lives in the resolver used only by creation paths (the request paths call _credentialTarget directly), so validators already live on a since-retired pod are unaffected. try/catch keeps pre-v1.14 pods working. Finding 4 (MED) from the PR #485 pre-audit review; the retired-pod resolver output was reproduced on a Tenderly fork with real EigenLayer v1.14.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
…wal-credentials' into fix/enm-block-creation-against-retired-pod # Conflicts: # src/staking/interfaces/IEtherFiNodesManager.sol
0xpanicError
merged commit Aug 11, 2026
95abc75
into
pankaj/feat/non-eigenpod-withdrawal-credentials
2 checks passed
0xpanicError
added a commit
that referenced
this pull request
Aug 11, 2026
Two follow-ups from Cursor Bugbot on the merged fixes: Revert finding-3 change (#487): extending the MixedNodeRequest guard to disabled pods enforced membership via etherFiNodeFromPubkeyHash, which does not contain all legacy validators. A legitimate same-pod consolidation batch of legacy validators - the actual migration case - could revert MixedNodeRequest on the very retirement path it targets. Restore the pod-less-only guard and drop the _requiresBatchMembershipCheck helper. Scope finding-4 check (#488): the PodRetired revert lived in withdrawalCredentialTarget, which confirmAndFundBeaconValidators also calls, so disabling a pod between the 1 ETH create and the 31 ETH top-up stranded the validator at 1 ETH. Resolve the target directly in the top-up path (same pod/node address regardless of retirement); the checked resolver still guards initial creation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0xpanicError
added a commit
that referenced
this pull request
Aug 11, 2026
Two follow-ups from Cursor Bugbot on the merged fixes: Revert finding-3 change (#487): extending the MixedNodeRequest guard to disabled pods enforced membership via etherFiNodeFromPubkeyHash, which does not contain all legacy validators. A legitimate same-pod consolidation batch of legacy validators - the actual migration case - could revert MixedNodeRequest on the very retirement path it targets. Restore the pod-less-only guard and drop the _requiresBatchMembershipCheck helper. Scope finding-4 check (#488): the PodRetired revert lived in withdrawalCredentialTarget, which confirmAndFundBeaconValidators also calls, so disabling a pod between the 1 ETH create and the 31 ETH top-up stranded the validator at 1 ETH. Resolve the target directly in the top-up path (same pod/node address regardless of retirement); the checked resolver still guards initial creation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Finding 4 (MED) — new validators can be created against a retired pod
disablePodnever clearsownerToPod, so_credentialTargetkeeps returning the (now disabled) pod for a retired node. All three StakingManager creation paths resolve credentials throughwithdrawalCredentialTarget, so they would bake0x02credentials pointing at the dead pod — and such a validator can neververifyWithdrawalCredentialsor checkpoint, stranding its 32 ETH. PR #485 also removed thegetEigenPod() == 0guard fromregisterBeaconValidators, so nothing else blocked it.Fix
withdrawalCredentialTarget(the validated resolver used only by the creation paths) revertsPodRetiredwhen the target pod reportsrestakingDisabled(). The request paths use_credentialTargetdirectly and are intentionally untouched, so validators already live on a since-retired pod keep working.try/catchleaves pre-v1.14 pods working as before.Tests
test_credentialTarget_revertsForRetiredPod.non-eigenpod-credentials: 77 passing;non-eigenpod-validator-lifecycle: 54 passing.Evidence
Reproduced on a Tenderly fork with real EL v1.14.0 (rehearsal test T3): after disabling a node's pod, the resolver returned the disabled pod and baked
0x02creds pointing at it.Base:
pankaj/feat/non-eigenpod-withdrawal-credentials.🤖 Generated with Claude Code
Note
Medium Risk
Touches the credential resolver used by all validator creation paths and guards against stranded ETH, but the change is a small defensive check with a regression test and does not alter request/exit flows.
Overview
Prevents new validators from being created against a retired EigenPod, which would strand 32 ETH behind a dead pod that can never verify credentials or checkpoint.
withdrawalCredentialTarget(used by all three StakingManager creation paths) now revertsPodRetiredwhen the target pod reportsrestakingDisabled(). Request paths still use_credentialTargetdirectly, so existing validators on a since-retired pod keep working. Atry/catchpreserves pre-v1.14 pod behavior.Reviewed by Cursor Bugbot for commit f219f9b. Bugbot is set up for automated code reviews on this repo. Configure here.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.