fix(state): cap RotateChannelKey + Event.deps (SEC-V-07)#452
Merged
intendednull merged 1 commit intoApr 28, 2026
Merged
Conversation
Anti-DoS caps for two attacker-controlled vectors that fan out via .clone() into per-peer state. Ref #236. Caps + tier: - MAX_EVENT_DEPS = 64. EventDag::insert rejects oversize deps with InsertError::DepsTooLong. Author-side path covered too because every event reaches `insert` before reaching state. - MAX_ENCRYPTED_KEY_BYTES = 128. EventDag::insert rejects oversize per-blob payloads inside RotateChannelKey.encrypted_keys with InsertError::EncryptedKeyTooLarge. One X25519-sealed key fits well under that. - MAX_ENCRYPTED_KEYS_OVER_MEMBERS = 4. apply_event for RotateChannelKey rejects when encrypted_keys.len() exceeds state.members.len() + epsilon. Needs state context, so it lives at apply time alongside the existing member gate. Tier choice: caps that need no state context go at the DAG insert boundary so they fire regardless of which call path produced the event. The member-count cap requires `state.members` and naturally slots into the existing apply_event Rejected branch. Runner-up: stuffing all three into Event::new. Rejected because Event::new is currently infallible and changing it cascades to ~25 production + test callsites for no extra coverage — every event already passes through dag.insert before mutating state. willow-replay matches on InsertError exhaustively; added warn arms for the two new variants. Tests (state crate, 5 added): - dag_insert_rejects_deps_over_cap - dag_insert_accepts_deps_at_cap - dag_insert_rejects_oversized_encrypted_key - apply_rotate_channel_key_rejects_excess_entries_over_member_count - apply_rotate_channel_key_accepts_at_member_count_plus_epsilon Local: cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, cargo test --workspace, cargo check --target wasm32-unknown-unknown all green. https://claude.ai/code/session_01VdcwSdvqig423A5CX2fSzR
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.
What
Cap two attacker-controlled vectors. Stop ManageChannels-holder flooding
state.channel_keysw/~7000fabricated entries perRotateChannelKey. Stop any author bloatingEvent.deps.Refs #236 (SEC-V-07).
Caps
MAX_EVENT_DEPS = 64— checked inEventDag::insert→InsertError::DepsTooLong.MAX_ENCRYPTED_KEY_BYTES = 128— checked inEventDag::insertfor each blob insideRotateChannelKey.encrypted_keys→InsertError::EncryptedKeyTooLarge.MAX_ENCRYPTED_KEYS_OVER_MEMBERS = 4— epsilon added tostate.members.len()ceiling. Checked inapply_eventforRotateChannelKey→ApplyResult::Rejected.Tier choice
EventDag::insert(fires for every code path: local + inbound wire, since all events go throughinsert).apply_event(needsstate.members).Runner-up: stuff all three into
Event::new. Rejected.Event::newinfallible today; changing signature cascades to ~25 callsites for zero extra coverage — every event passes throughdag.insertbefore touching state.Downstream
willow-replay::role::try_insertmatchesInsertErrorexhaustively. Addedwarn!arms for the two new variants.Tests (state crate, 5 added)
dag_insert_rejects_deps_over_capdag_insert_accepts_deps_at_capdag_insert_rejects_oversized_encrypted_keyapply_rotate_channel_key_rejects_excess_entries_over_member_countapply_rotate_channel_key_accepts_at_member_count_plus_epsilonLocal gate
cargo fmt --checkcleancargo clippy --workspace --all-targets -- -D warningscleancargo test --workspace227 willow-state + all downstream passcargo check --target wasm32-unknown-unknown(state + client + web etc) cleanSub-PR base ≠ main → CI not firing. Local check is the gate.
https://claude.ai/code/session_01VdcwSdvqig423A5CX2fSzR
Generated by Claude Code