Skip to content

feat(ipc): wire-protocol version handshake — refuse a skewed cef_host#18

Merged
wenkaifan0720 merged 1 commit into
mainfrom
feat/protocol-version-handshake
Jul 3, 2026
Merged

feat(ipc): wire-protocol version handshake — refuse a skewed cef_host#18
wenkaifan0720 merged 1 commit into
mainfrom
feat/protocol-version-handshake

Conversation

@wenkaifan0720

Copy link
Copy Markdown
Collaborator

Architecture-audit follow-up (finding #4): the framed kOp protocol had no version negotiation — a plugin paired with a protocol-skewed host (FLUTTER_CEF_HOST override, stale from-source build, stale embedded copy) mis-parsed or silently dropped frames: frozen/blank tiles with zero breadcrumb.

Changes

  • cef_host: opReady payload is now [readyFlags, kCefHostProtocolVersion] (pre-handshake hosts sent 1 byte → read as v0). Unknown opcodes in the reader now log once per opcode via SendLog instead of a silent default: break.
  • CefProfileHost: expected-version constant beside the opcode table (bump both sides on any wire change); the opReady arm refuses a mismatched host before flushing any queued create (F.5 refuse-at-ready pattern) and fires onProtocolMismatch.
  • FlutterCefPlugin: host-death teardown extracted to failHost(_:reason:) and shared; a mismatch fails every attached session with processGone("protocolMismatch(host=vN)") and tears the host down. Deliberately no auto-respawn (it would re-resolve the same binary and loop).

Validated (live, example app)

  • v1 plugin + v0 host (the currently-published prebuilt): refused at handshakewire-protocol version 0 != expected 1, zero renders ✅
  • v1 plugin + from-source v1 host: renders flutter.dev normally ✅
  • Dart unit suite: all 132 pass ✅

Post-merge: native/cef_host changed → new content hash. Run work_canvas make publish-cef-host, then make pin-cef (the bump also delivers #17's codesign-verified fetch to consumers).

🤖 Generated with Claude Code

… instead of silently mis-parsing

Architecture-audit follow-up (finding #4): the framed kOp protocol had no version negotiation —
opReady carried a single ad-hoc-flag byte, so a plugin paired with a protocol-skewed host (a
FLUTTER_CEF_HOST override, a stale from-source build, a stale embedded copy) mis-parsed or silently
dropped frames: frozen/blank tiles with zero breadcrumb. The content-hash distribution keeps the
pair matched on the normal path; this closes the bypass vectors.

- cef_host (main.mm): opReady's payload is now [readyFlags, kCefHostProtocolVersion]. Pre-handshake
  hosts sent 1 byte and read as v0. Unknown opcodes in the reader are now logged once per opcode
  via SendLog (previously a silent `default: break`) — a newer-plugin/older-host frame drop leaves
  a breadcrumb.
- CefProfileHost.swift: expected-version constant beside the opcode table (bump BOTH on any wire
  change); the opReady arm refuses a mismatched host BEFORE flushing any queued create, clears
  pendingCreates, and fires onProtocolMismatch — reusing the F.5 refuse-at-ready pattern.
- FlutterCefPlugin.swift: the host-death teardown is extracted to failHost(_:reason:) and shared;
  onProtocolMismatch fails every attached session with processGone("protocolMismatch(host=vN)") and
  tears the host down. Deliberately NO auto-respawn (it would re-resolve the same binary and loop);
  the consumer's bounded recovery surfaces it.

Validated live with the example app: v1 plugin + v0 host (the currently-published prebuilt) is
REFUSED at the handshake ("wire-protocol version 0 != expected 1", zero renders); v1 plugin +
from-source v1 host renders flutter.dev normally; Dart unit suite green.

NOTE: changes native/cef_host -> new content hash. After merge, publish the new artifact
(work_canvas `make publish-cef-host`) and bump the consumer pin (picks up #17's fetch
codesign-verify as well).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@wenkaifan0720 wenkaifan0720 merged commit 286f39f into main Jul 3, 2026
1 check 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.

1 participant