Context
Finalized headers carry consensus header fields together with a finalization certificate that should authenticate the header digest. Summit also exposes typed Rust structures and verifier helpers for checkpoint-chain validation, so those typed APIs form a trust boundary for integrations that operate on already-decoded values.
The intended invariant is that any finalized header accepted by the verifier has its public header fields bound to the certificate payload. This finding concerns the gap between decode-time validation and later typed-object use.
Claim
SSZ decoding verifies that a FinalizedHeader certificate signs the header digest, but the public typed struct and constructor do not preserve that invariant. verify_checkpoint_chain() accepts already-constructed FinalizedHeader values and trusts their mutable header fields without rechecking that the certificate payload still equals the header digest.
A malicious checkpoint supplier targeting a downstream typed-API integration can provide or induce an already-constructed FinalizedHeader with a valid finalization certificate but mutated public header fields. verify_checkpoint_chain() then trusts those unsigned fields because the payload/header binding was enforced only during SSZ decode.
Flow
The issue is reachable for typed API callers that bypass SSZ decode-time validation, including tests or integrations that construct FinalizedHeader directly or mutate its public fields after construction. It is not currently shown to be reachable through the stock startup checkpoint-file path, because that path decodes finalized headers with FinalizedHeader::from_ssz_bytes before verification.
Impact
A caller that treats verify_checkpoint_chain() as the trust boundary for untrusted typed artifacts can pass a valid finalization certificate paired with mutated header fields. In particular, checkpoint_hash can be changed to match attacker-controlled checkpoint data while the certificate still verifies against the old digest. Startup SSZ import is partially protected by decode-time checks, but the typed verifier API can still break trustless checkpoint validation for downstream callers or tests.
Root Cause
FinalizedHeader exposes mutable public fields and a constructor that does not bind finalization.proposal.payload to header.digest; the checkpoint verifier accepts typed values without recomputing that binding.
Code
Related Issues/PRs
Related issues cover adjacent signed-field, checkpoint, state-proof, and SSZ root binding gaps that can weaken finalized-header trust.
Fix
- Make
FinalizedHeader fields private or validate invariants in constructors.
- Have
verify_checkpoint_chain() recompute and enforce finalization.proposal.payload == header.digest for every typed header.
- Prefer deriving trusted fields from the signed digest path rather than trusting mutable struct fields.
- Add a negative test that mutates
checkpoint_hash after finalization construction.
Context
Finalized headers carry consensus header fields together with a finalization certificate that should authenticate the header digest. Summit also exposes typed Rust structures and verifier helpers for checkpoint-chain validation, so those typed APIs form a trust boundary for integrations that operate on already-decoded values.
The intended invariant is that any finalized header accepted by the verifier has its public header fields bound to the certificate payload. This finding concerns the gap between decode-time validation and later typed-object use.
Claim
SSZ decoding verifies that a
FinalizedHeadercertificate signs the header digest, but the public typed struct and constructor do not preserve that invariant.verify_checkpoint_chain()accepts already-constructedFinalizedHeadervalues and trusts their mutable header fields without rechecking that the certificate payload still equals the header digest.A malicious checkpoint supplier targeting a downstream typed-API integration can provide or induce an already-constructed
FinalizedHeaderwith a valid finalization certificate but mutated public header fields.verify_checkpoint_chain()then trusts those unsigned fields because the payload/header binding was enforced only during SSZ decode.Flow
The issue is reachable for typed API callers that bypass SSZ decode-time validation, including tests or integrations that construct
FinalizedHeaderdirectly or mutate its public fields after construction. It is not currently shown to be reachable through the stock startup checkpoint-file path, because that path decodes finalized headers withFinalizedHeader::from_ssz_bytesbefore verification.Impact
A caller that treats
verify_checkpoint_chain()as the trust boundary for untrusted typed artifacts can pass a valid finalization certificate paired with mutated header fields. In particular,checkpoint_hashcan be changed to match attacker-controlled checkpoint data while the certificate still verifies against the old digest. Startup SSZ import is partially protected by decode-time checks, but the typed verifier API can still break trustless checkpoint validation for downstream callers or tests.Root Cause
FinalizedHeaderexposes mutable public fields and a constructor that does not bindfinalization.proposal.payloadtoheader.digest; the checkpoint verifier accepts typed values without recomputing that binding.Code
FinalizedHeaderexposes publicheaderandfinalizationfields: https://github.com/SeismicSystems/summit/blob/ed2c5c8/types/src/header.rs#L327.FinalizedHeader::newstores those fields without checking that the finalization signsheader.digest: https://github.com/SeismicSystems/summit/blob/ed2c5c8/types/src/header.rs#L333.finalization.proposal.payload == header.digest, but only during decode: https://github.com/SeismicSystems/summit/blob/ed2c5c8/types/src/header.rs#L403.verify_checkpoint_chain()verifies the certificate but does not recheck the payload/header binding for typed inputs: https://github.com/SeismicSystems/summit/blob/ed2c5c8/types/src/checkpoint.rs#L239.last_header.header.checkpoint_hashto authenticate the checkpoint data: https://github.com/SeismicSystems/summit/blob/ed2c5c8/types/src/checkpoint.rs#L263.Related Issues/PRs
Related issues cover adjacent signed-field, checkpoint, state-proof, and SSZ root binding gaps that can weaken finalized-header trust.
Fix
FinalizedHeaderfields private or validate invariants in constructors.verify_checkpoint_chain()recompute and enforcefinalization.proposal.payload == header.digestfor every typed header.checkpoint_hashafter finalization construction.