fix(security): verify Ed25519 signatures on inbound PUT and chat (SOC-59)#10
Open
chongkan wants to merge 2 commits into
Open
fix(security): verify Ed25519 signatures on inbound PUT and chat (SOC-59)#10chongkan wants to merge 2 commits into
chongkan wants to merge 2 commits into
Conversation
…-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.
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.
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 claimeddidOwner/fromDID.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, notmain. The fix layers on the chat protocol, RPC layer, and chat types that live only on that branch and are not yet inmain(handleChat/GossipChatMessage/chat-storedon't exist onmain). It must merge with or after that feature stack — it cannot be cherry-picked tomainalone.Changes
verify.ts(new): pluggableDidResolver, offlinedid:keyresolver, version-tagged canonical serialization (guards cross-type/replay signature reuse), verify/sign helpers.crypto.ts: synchronousverifySignatureSyncfor the gossip hot path;verifySignaturekept as an async wrapper.protocol.ts:handlePut+handleChat/Ack/Deleteverify the signature against the resolved DID key before storing/propagating. InhandlePutthe check runs before version-conflict resolution, so a forged higherversioncannot force-win. Constructor gains an optionaldidResolver(defaults todid:key; fail-closed for methods it cannot resolve).index.ts: export the new surface.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
tsupbuild (ESM + CJS + d.ts) succeedsTS6133in the untouchedsrc/rpc.tsremains (not caused by this change)Deploy-blocking dependency: SOC-69
daemon.tsbuildsMeshProtocolwith the defaultdid:keyresolver, so once deployed this rejects alldid:snswrites until SOC-69 injects adid:snsresolver. Correct fail-closed posture, butdid:snspropagation on the anchor node is dark until SOC-69 lands. Also confirm publishers (CORTEX / wallet) attach signatures via the exportedsignPutMetadata/signChatMessagehelpers before rollout.