@sym-bot/mesh-channel 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "sym-mesh-channel",
3
+ "version": "0.1.19",
4
+ "description": "Real-time Claude-to-Claude mesh. Peer-to-peer cognitive signals over Bonjour LAN or WebSocket relay.",
5
+ "author": {
6
+ "name": "Hongwei Xu",
7
+ "email": "hongwei@sym.bot",
8
+ "url": "https://sym.bot"
9
+ },
10
+ "homepage": "https://sym.bot/spec/mmp",
11
+ "repository": "https://github.com/sym-bot/sym-mesh-channel",
12
+ "license": "Apache-2.0",
13
+ "keywords": ["mesh", "p2p", "mcp", "channel", "agents", "multi-agent", "bonjour", "cognitive", "svaf", "mmp"],
14
+ "channels": [
15
+ {
16
+ "server": "claude-sym-mesh",
17
+ "userConfig": {
18
+ "relay_url": {
19
+ "description": "SYM relay WebSocket URL for cross-network mesh (leave empty for LAN-only via Bonjour)",
20
+ "sensitive": false
21
+ },
22
+ "relay_token": {
23
+ "description": "Relay authentication token (leave empty for LAN-only)",
24
+ "sensitive": true
25
+ },
26
+ "allowed_peers": {
27
+ "description": "Comma-separated peer node names to accept (leave empty to accept all authenticated peers)",
28
+ "sensitive": false
29
+ }
30
+ }
31
+ }
32
+ ]
33
+ }
package/.mcp.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "mcpServers": {
3
+ "claude-sym-mesh": {
4
+ "command": "node",
5
+ "args": ["./server.js"],
6
+ "cwd": "${CLAUDE_PLUGIN_ROOT}",
7
+ "env": {
8
+ "SYM_RELAY_URL": "${user_config.relay_url}",
9
+ "SYM_RELAY_TOKEN": "${user_config.relay_token}",
10
+ "SYM_ALLOWED_PEERS": "${user_config.allowed_peers}"
11
+ }
12
+ }
13
+ }
14
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.19
4
+
5
+ ### Added
6
+
7
+ - **Claude Code plugin manifest** for Anthropic Channels allowlist
8
+ submission. `.claude-plugin/plugin.json` + `.mcp.json` following the
9
+ official single-repo pattern (Telegram/Discord). Submitted to
10
+ Anthropic Plugin Directory 10 Apr 2026.
11
+ - **`SYM_ALLOWED_PEERS`** — optional peer allowlist (defense-in-depth).
12
+ Comma-separated node names; only listed peers can push to Claude's
13
+ context. Empty = accept all authenticated peers. SVAF still gates on
14
+ content relevance regardless.
15
+ - **`SECURITY.md`** — 3-layer defense model documentation (transport
16
+ auth + SVAF content gate + peer allowlist) for Anthropic review.
17
+ - **17 plugin tests** covering manifest validation, security checks
18
+ (no permission relay, no code execution, self-echo filtering, peer
19
+ allowlist), and lifecycle (shutdown handlers, identity collision).
20
+
3
21
  ## 0.1.18
4
22
 
5
23
  ### Changed
package/SECURITY.md ADDED
@@ -0,0 +1,89 @@
1
+ # Security Model
2
+
3
+ sym-mesh-channel implements defense in depth with three layers. No
4
+ single layer is the sole gate — all three must pass before a mesh
5
+ signal reaches Claude's conversation context.
6
+
7
+ ## Layer 1: Transport Authentication
8
+
9
+ Only authenticated peers can send signals to this node.
10
+
11
+ - **LAN (Bonjour)**: peers discover each other via mDNS on the local
12
+ network. Each peer has an Ed25519 keypair generated at first run
13
+ and stored at `~/.sym/nodes/<name>/identity.json`. Peer identity is
14
+ verified via cryptographic handshake (MMP Section 5).
15
+ - **Relay (WebSocket)**: peers authenticate with a shared relay token
16
+ (`SYM_RELAY_TOKEN`). The relay enforces per-token channel isolation —
17
+ peers on different tokens cannot see each other. Unauthenticated
18
+ connections are rejected at the transport level.
19
+
20
+ No unauthenticated source can reach `pushChannel()`.
21
+
22
+ ## Layer 2: Protocol-Level Content Gating (SVAF)
23
+
24
+ Every incoming CMB is evaluated by Symbolic-Vector Attention Fusion
25
+ before it enters cognitive state. SVAF computes per-field drift across
26
+ 7 semantic dimensions (CAT7: focus, issue, intent, motivation,
27
+ commitment, perspective, mood) and operates in three regimes:
28
+
29
+ - **Aligned** (drift < threshold): CMB is accepted and stored
30
+ - **Guarded** (drift moderate): only the mood field is delivered (protocol guarantee R5)
31
+ - **Rejected** (drift high): CMB is silently dropped
32
+
33
+ This is analogous to a content-aware firewall: it doesn't just check
34
+ who sent the signal — it evaluates whether the signal is semantically
35
+ relevant to the receiver's current context. Low-relevance CMBs are
36
+ gated out so Claude's context window doesn't drown.
37
+
38
+ SVAF field weights are configurable per node (`svafFieldWeights` in
39
+ server.js). The default weights are tuned for engineering-domain
40
+ Claude Code sessions.
41
+
42
+ ## Layer 3: Application-Level Restrictions
43
+
44
+ - **No code execution**: incoming mesh signals are text-only CMB fields.
45
+ No mesh peer can trigger Bash commands, file writes, or tool calls
46
+ on this node.
47
+ - **No permission relay**: the `claude/channel/permission` capability is
48
+ explicitly NOT declared. Mesh peers cannot approve or deny tool
49
+ executions on this node.
50
+ - **No arbitrary content injection**: incoming CMBs are formatted as
51
+ structured `[source] focus (mood)` text before being pushed to
52
+ Claude's context. Raw JSON is never injected.
53
+ - **Self-echo filtering**: CMBs from this node's own identity are
54
+ dropped before `pushChannel()` (prevents feedback loops).
55
+
56
+ ## Optional: Peer Allowlist
57
+
58
+ Set `SYM_ALLOWED_PEERS` (comma-separated node names) to restrict which
59
+ authenticated peers can push to Claude's context. When set, only CMBs
60
+ and messages from listed peers pass the gate. When empty (default), all
61
+ authenticated peers are accepted — SVAF still gates on content relevance.
62
+
63
+ Example:
64
+ ```
65
+ SYM_ALLOWED_PEERS=claude-code-mac,claude-code-win
66
+ ```
67
+
68
+ This is an additional layer, not a replacement for transport auth or
69
+ SVAF. It provides explicit identity-level control for environments
70
+ that require it.
71
+
72
+ ## Token Handling
73
+
74
+ - `SYM_RELAY_TOKEN`: passed via environment variable, never logged,
75
+ never included in CMBs or channel notifications. In the plugin
76
+ manifest, marked `sensitive: true` (stored in system keychain).
77
+ - Ed25519 private key: stored at `~/.sym/nodes/<name>/identity.json`,
78
+ never transmitted. Only the public key is shared during handshake.
79
+
80
+ ## Identity Collision
81
+
82
+ If another process is already running with the same node identity,
83
+ the relay returns close code 4004. The server exits cleanly with
84
+ exit code 2 rather than competing for the identity.
85
+
86
+ ## References
87
+
88
+ - [MMP v0.2.2 Specification](https://sym.bot/spec/mmp) — Sections 5 (Connection), 8 (CAT7), 9 (SVAF)
89
+ - [SVAF Paper](https://arxiv.org/abs/2604.03955) — Xu, 2026
package/package.json CHANGED
@@ -1,17 +1,21 @@
1
1
  {
2
2
  "name": "@sym-bot/mesh-channel",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "MCP server — real-time agent-to-agent cognition for Claude Code remote teams via the SYM mesh.",
5
5
  "main": "server.js",
6
6
  "bin": {
7
7
  "sym-mesh-channel": "server.js"
8
8
  },
9
9
  "scripts": {
10
+ "test": "node test/plugin.test.js",
10
11
  "postinstall": "node bin/install.js init --postinstall"
11
12
  },
12
13
  "files": [
13
14
  "server.js",
14
15
  "bin/",
16
+ ".claude-plugin/",
17
+ ".mcp.json",
18
+ "SECURITY.md",
15
19
  "README.md",
16
20
  "CHANGELOG.md",
17
21
  "LICENSE"
package/server.js CHANGED
@@ -228,6 +228,21 @@ mcp.setRequestHandler(CallToolRequestSchema, async (request) => {
228
228
  }
229
229
  });
230
230
 
231
+ // ── Peer Allowlist (optional, defense-in-depth) ─────────────
232
+ // SYM_ALLOWED_PEERS is a comma-separated list of peer node names.
233
+ // When set, only CMBs and messages from listed peers are pushed to
234
+ // Claude's context. When empty/unset, all authenticated peers are
235
+ // accepted (SVAF still gates on content relevance).
236
+ const ALLOWED_PEERS = (process.env.SYM_ALLOWED_PEERS || '')
237
+ .split(',')
238
+ .map(s => s.trim())
239
+ .filter(Boolean);
240
+
241
+ function isPeerAllowed(peerName) {
242
+ if (ALLOWED_PEERS.length === 0) return true; // no allowlist = accept all
243
+ return ALLOWED_PEERS.includes(peerName);
244
+ }
245
+
231
246
  // ── Mesh Events → Channel Notifications ──────────────────────
232
247
 
233
248
  function pushChannel(eventType, data) {
@@ -247,12 +262,19 @@ node.on('cmb-accepted', (entry) => {
247
262
  if (entry.source === NODE_NAME || entry.cmb?.createdBy === NODE_NAME) return;
248
263
 
249
264
  const source = entry.source || entry.cmb?.createdBy || 'unknown';
265
+
266
+ // Peer allowlist gate (defense-in-depth, see SECURITY.md)
267
+ if (!isPeerAllowed(source)) return;
268
+
250
269
  const focus = entry.cmb?.fields?.focus?.text || entry.content || '';
251
270
  const mood = entry.cmb?.fields?.mood?.text || '';
252
271
  pushChannel('cmb', `[${source}] ${focus}${mood && mood !== 'neutral' ? ` (mood: ${mood})` : ''}`);
253
272
  });
254
273
 
255
274
  node.on('message', (from, content) => {
275
+ // Peer allowlist gate
276
+ if (!isPeerAllowed(from)) return;
277
+
256
278
  pushChannel('message', `[message from ${from}] ${content}`);
257
279
  });
258
280