Skip to content

fix(security): verify Ed25519 signatures on inbound PUT and chat (SOC-59)#10

Open
chongkan wants to merge 2 commits into
fix/better-sqlite3-electron39from
fix/soc-59-mesh-signature-verify
Open

fix(security): verify Ed25519 signatures on inbound PUT and chat (SOC-59)#10
chongkan wants to merge 2 commits into
fix/better-sqlite3-electron39from
fix/soc-59-mesh-signature-verify

Conversation

@chongkan

Copy link
Copy Markdown
Member

Summary

Closes SOC-59 (CRITICAL) from the 2026-07-19 workspace audit. The mesh is public — any peer can dial and publish. Inbound handlers verified only hashBlob(blob) === contentHash (integrity), never that the writer controls the claimed didOwner/from DID. verifySignature() existed but was called on no inbound path, allowing namespace squatting, version-overwrite shadowing of a victim's identity state, and forged chat messages.

Base branch note

This PR targets fix/better-sqlite3-electron39, not main. The fix layers on the chat protocol, RPC layer, and chat types that live only on that branch and are not yet in main (handleChat / GossipChatMessage / chat-store don't exist on main). It must merge with or after that feature stack — it cannot be cherry-picked to main alone.

Changes

  • verify.ts (new): pluggable DidResolver, offline did:key resolver, version-tagged canonical serialization (guards cross-type/replay signature reuse), verify/sign helpers.
  • crypto.ts: synchronous verifySignatureSync for the gossip hot path; verifySignature kept as an async wrapper.
  • protocol.ts: handlePut + handleChat/Ack/Delete verify the signature against the resolved DID key before storing/propagating. In handlePut the check runs before version-conflict resolution, so a forged higher version cannot force-win. Constructor gains an optional didResolver (defaults to did:key; fail-closed for methods it cannot resolve).
  • index.ts: export the new surface.
  • Tests: put-chat-signature.test.ts (14 adversarial cases: namespace squat, forged-version overwrite, forged chat/delete, resolver round-trip); existing chat/security tests updated to sign legitimately and to assert unsigned messages are now rejected.

Verification

  • Suite 152 passed / 1 skipped
  • tsup build (ESM + CJS + d.ts) succeeds
  • One pre-existing TS6133 in the untouched src/rpc.ts remains (not caused by this change)

Deploy-blocking dependency: SOC-69

daemon.ts builds MeshProtocol with the default did:key resolver, so once deployed this rejects all did:sns writes until SOC-69 injects a did:sns resolver. Correct fail-closed posture, but did:sns propagation on the anchor node is dark until SOC-69 lands. Also confirm publishers (CORTEX / wallet) attach signatures via the exported signPutMetadata / signChatMessage helpers before rollout.

chongkan added 2 commits July 19, 2026 10:36
…-59)

The mesh is public — any peer can dial and publish. Inbound handlers verified
only hashBlob(blob) === contentHash (integrity), never that the writer controls
the claimed didOwner/from DID. verifySignature() existed but was called on no
inbound path, allowing namespace squatting, version-overwrite shadowing of a
victim's identity state, and forged chat messages.

- verify.ts (new): pluggable DidResolver, offline did:key resolver, version-
  tagged canonical serialization (guards cross-type/replay signature reuse),
  verify/sign helpers.
- crypto.ts: add synchronous verifySignatureSync for the gossip hot path;
  verifySignature kept as an async wrapper.
- protocol.ts: handlePut + handleChat/Ack/Delete verify the signature against
  the claimed DID's key BEFORE storing/propagating, and reject on failure.
  In handlePut the check runs BEFORE version-conflict resolution, so a forged
  higher version cannot force-win. Constructor gains an optional didResolver
  (defaults to did:key; fail-closed for methods it cannot resolve).
- index.ts: export the new surface.
- tests: put-chat-signature.test.ts (14 adversarial cases incl. namespace
  squat, forged-version overwrite, forged chat/delete, resolver round-trip);
  existing chat/security tests updated to sign legitimately and to assert
  unsigned messages are now rejected.

Suite: 152 passed / 1 skipped. Fail-closed consequence: the anchor daemon uses
the default did:key resolver, so did:sns writes are rejected until a did:sns
resolver is injected (SOC-69) — SOC-69 is a deploy-blocking dependency.
Pre-existing type-check failure: the /chat/channels handler fetched
getChatMessages('__list__') into a channels var it never used (the endpoint
returns an empty list until a getChannels API exists). Dropped the dead call so
'pnpm type-check' passes; behavior unchanged.
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