Skip to content

fix(p2p): harden direct-P2P framing and ingress admission - #75

Merged
trvon merged 1 commit into
fix/p2p-operator-validationfrom
fix/p2p-wire-hardening
Aug 30, 2026
Merged

fix(p2p): harden direct-P2P framing and ingress admission#75
trvon merged 1 commit into
fix/p2p-operator-validationfrom
fix/p2p-wire-hardening

Conversation

@trvon

@trvon trvon commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Summary

Stacked draft child of #74. Closes four direct-P2P wire/ingress findings from the deep review of the #68 stack, without merging or deploying anything.

  1. Pre-authentication JSON recursion DoS (P1)parseJsonFrame now rejects control frames nested deeper than 32 levels before nlohmann recursively descends them, so a ~32k-deep [[[…]]] hello cannot overflow the session-thread stack before any trust decision.
  2. Control-frame cap vs 256-writer state frame (P1)kP2pMaxControlFrameBytes raised 64 KiB → 512 KiB to cover the worst-case authenticated state frame (~300 KiB: version vector + seen watermark + 256 commitments + quarantined set).
  3. Direct-P2P ingress bypasses the resource governor (P2)MemorySyncLoop::applyDeltas now runs beforeDirectIngress() (unconditional canAdmitRemoteWork + isCancelled), so the daemon's ResourceGovernor can throttle a peer flooding deltas on the new replication path even though the backend is local.
  4. Wire-byte under-counting (P2) — delta/bootstrap accounting now uses the exact received frame size (readJsonFrame().payloadBytes) instead of a compact re-serialization, so whitespace/duplicate-key padding from a peer counts against maxWireBytesPerSession/maxSnapshotWireBytes.

Confirmed non-issue (no change): the cold-bootstrap snapshot path is correctly multi-writer — each winner's origin is authenticated by its own schema-v4 writer signature (validateBootstrapWinnervalidateDirectDeltawriterAuth_->verify) plus the witness root digest, so it does not require the single-writer origin == peerNodeId check used on the delta-continuation path.

AI disclosure

Extensive AI assistance was used for implementation, test generation, and adversarial review. All changes were locally inspected and validated; the umbrella #68 remains blocked by the decision-grade live-performance gate.

Stack

Do not merge or deploy this child independently.

Bound pre-authentication JSON nesting depth to 32 so a pathologically nested
control frame cannot overflow the session-thread stack before any trust
decision. Raise the control-frame cap to 512 KiB to cover the worst-case
authenticated state frame (~300 KiB across 256 writers x 256-byte ids). Gate
MemorySyncLoop::applyDeltas through the resource governor so a peer cannot
flood deltas on the direct replication path. Account delta/bootstrap wire bytes
using the exact received frame size instead of a compact re-serialization.

Signed-off-by: trvon <git@trevon.dev>
@trvon
trvon marked this pull request as ready for review August 30, 2026 15:48
Copilot AI lite review requested due to automatic review settings August 30, 2026 15:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes security-critical pre-auth P2P parsing/framing and ingress admission behavior, which warrants a final human review.

Pull request overview

This PR hardens the direct-P2P replication path by adding pre-auth JSON depth rejection, correcting receiver-side wire-byte accounting to use exact frame sizes, enforcing resource-governor admission for direct delta ingress, and raising the control-frame cap to accommodate worst-case authenticated state frames.

Changes:

  • Add a JSON nesting-depth pre-scan in control-frame parsing and introduce readJsonFrame() to retain exact received payload size for accounting.
  • Update delta/bootstrap receive paths to account bytes using the original wire size rather than compact re-serialization.
  • Gate MemorySyncLoop::applyDeltas() behind a direct-ingress admission check and expand unit coverage for the new behavior; raise kP2pMaxControlFrameBytes and add a transport regression test.
File summaries
File Description
tests/unit/memory_sync/memory_sync_catch2_test.cpp Adds a unit test ensuring applyDeltas() rejects work when direct-ingress admission is denied.
tests/unit/daemon/p2p_transport_catch2_test.cpp Adds a regression test validating large control frames can round-trip after the cap increase.
tests/unit/daemon/p2p_protocol_catch2_test.cpp Adds a test ensuring handshake control frames reject excessive JSON nesting depth pre-auth.
tests/unit/daemon/p2p_delta_catch2_test.cpp Adds a test verifying receiver byte accounting includes JSON padding/whitespace on the wire.
src/daemon/p2p/p2p_json.h Implements JSON nesting depth pre-scan and readJsonFrame() (JSON + exact wire payload bytes).
src/daemon/p2p/p2p_delta.cpp Switches delta/bootstrap receive accounting to use readJsonFrame().payloadBytes.
include/yams/memory_sync/memory_sync.h Adds an unconditional direct-ingress admission gate for peer-delivered deltas.
include/yams/daemon/p2p/p2p_transport.h Raises the control-frame cap to 512 KiB and documents sizing rationale.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/daemon/p2p/p2p_json.h
Comment on lines +31 to +33
/// Control frames are structurally flat (<= 5 nesting levels). Anything deeper is a
/// pre-authentication stack-exhaustion probe and is rejected before nlohmann descends it.
inline constexpr std::size_t kMaxP2pJsonNestingDepth = 32;
@trvon
trvon merged commit 89d9f7c into experimental Aug 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants