@sym-bot/mesh-channel 0.3.10 → 0.3.11

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.
@@ -1,19 +1,19 @@
1
1
  {
2
- "name": "sym-bot",
2
+ "name": "sym-mesh-channel",
3
3
  "owner": {
4
4
  "name": "SYM.BOT",
5
5
  "email": "info@sym.bot"
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Real-time communication and collaboration among Claude Code sessions — agent-to-agent cognitive signals over Bonjour LAN or WebSocket relay, on the Mesh Memory Protocol (MMP).",
9
- "version": "0.3.10"
9
+ "version": "0.3.11"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "sym-mesh-channel",
14
14
  "source": "./",
15
15
  "description": "Real-time communication and collaboration among Claude Code sessions. Turns Claude Code into a peer node on the SYM mesh — two or more sessions discover each other via Bonjour (LAN) or a WebSocket relay (cross-network) and exchange structured cognitive signals as channel notifications. Each peer has its own Ed25519 identity, SVAF content gating, and local memory. Built on the Mesh Memory Protocol (MMP), an open peer-to-peer protocol for multi-agent collective intelligence.",
16
- "version": "0.3.10",
16
+ "version": "0.3.11",
17
17
  "author": {
18
18
  "name": "Hongwei Xu",
19
19
  "email": "hongwei@sym.bot"
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ```
8
8
  # in Claude Code — the first line is one-time setup
9
9
  /plugin marketplace add sym-bot/sym-mesh-channel
10
- /plugin install sym-mesh-channel@sym-bot
10
+ /plugin install sym-mesh-channel@sym-mesh-channel
11
11
  ```
12
12
 
13
13
  [![npm](https://img.shields.io/npm/v/@sym-bot/mesh-channel)](https://www.npmjs.com/package/@sym-bot/mesh-channel)
@@ -66,11 +66,11 @@ They're **not alternatives** — the channel is built *on* sym and speaks the sa
66
66
 
67
67
  ## Quick start
68
68
 
69
- Install the published plugin from the **sym-bot** marketplace — in Claude Code:
69
+ Install the published plugin — in Claude Code:
70
70
 
71
71
  ```
72
72
  /plugin marketplace add sym-bot/sym-mesh-channel
73
- /plugin install sym-mesh-channel@sym-bot
73
+ /plugin install sym-mesh-channel@sym-mesh-channel
74
74
  ```
75
75
 
76
76
  That gives you all **11 MCP tools — no flag, no npm, nothing else to add** — and **one install covers every Claude Code session on the machine**: open as many as you like (one per repo, or one planning while another codes); each gets its own mesh identity and picks up the mesh on resume. The first command is a one-time marketplace registration.
@@ -82,7 +82,7 @@ Also in the official [Anthropic Plugin Directory](https://claude.ai/settings/plu
82
82
  The tools above are pull-based. For a peer's message to **land in Claude's context mid-turn, with no tool call** — the "Claude thinks with the mesh" experience the screenshots show — Claude Code has to load this plugin's *channel*, which is currently gated behind a flag while it awaits Anthropic's approved-channels allowlist:
83
83
 
84
84
  ```
85
- claude --dangerously-load-development-channels plugin:sym-mesh-channel@sym-bot
85
+ claude --dangerously-load-development-channels plugin:sym-mesh-channel@sym-mesh-channel
86
86
  ```
87
87
 
88
88
  The flag becomes unnecessary once the channel is allowlisted — tracked in [anthropics/claude-plugins-official#1512](https://github.com/anthropics/claude-plugins-official/issues/1512).
@@ -258,13 +258,14 @@ Both peers must use the same relay URL and token to land on the same channel. Th
258
258
 
259
259
  ## Security
260
260
 
261
- Defence in depth. Three layers, all must pass before a mesh signal reaches Claude's context:
261
+ Four layers, all must pass before a mesh signal reaches Claude's context:
262
262
 
263
263
  1. **Transport.** Ed25519 peer identity on LAN + relay-token authentication on cross-network. Unauthenticated sources cannot reach `pushChannel()`.
264
264
  2. **Protocol.** [SVAF](https://arxiv.org/abs/2604.03955) per-field content gating — evaluates each incoming CMB across 7 semantic dimensions and rejects irrelevant signals before they enter cognitive state.
265
- 3. **Application.** Text-only context injection, no code execution, no permission relay (`claude/channel/permission` is explicitly not declared).
265
+ 3. **Safety.** Prompt-injection filter — pattern-matches every CAT7 field and payload against a curated blocklist of instruction-override, role-hijacking, system-prompt injection, tool-call fabrication, and privilege-escalation patterns. Matched CMBs are blocked and audit-logged to stderr; no silent drops. Per-peer rate limiting (default 30 CMBs/min, configurable via `SYM_RATE_LIMIT`) and a payload size cap (`SYM_MAX_PAYLOAD_BYTES`, default 8 KB) prevent flood and oversized-payload attacks.
266
+ 4. **Application.** Text-only context injection, no code execution, no permission relay (`claude/channel/permission` is explicitly not declared).
266
267
 
267
- **Optional peer allowlist.** Set `SYM_ALLOWED_PEERS=claude-mac,claude-win` to restrict which authenticated peers can push to Claude's context. When empty (default), all authenticated peers are accepted.
268
+ **Optional peer allowlist.** Set `SYM_ALLOWED_PEERS=claude-mac,claude-win` to restrict which authenticated peers can push to Claude's context. When empty (default), all authenticated peers pass layers 1–4.
268
269
 
269
270
  See [SECURITY.md](SECURITY.md) for the full threat model.
270
271
 
@@ -333,7 +334,7 @@ Some corporate networks block mDNS multicast entirely — try a hotspot or home
333
334
 
334
335
  The 11 tools work without any flag. The real-time `<channel>` **push** is separate: Claude Code only delivers channel notifications for channels on its **approved-channels allowlist**, and sym-mesh-channel isn't on it yet — so push requires the development-channels flag matching your install path:
335
336
 
336
- - plugin install: `--dangerously-load-development-channels plugin:sym-mesh-channel@sym-bot`
337
+ - plugin install: `--dangerously-load-development-channels plugin:sym-mesh-channel@sym-mesh-channel`
337
338
  - npm install: `--dangerously-load-development-channels server:claude-sym-mesh`
338
339
 
339
340
  This is an Anthropic-side gate, not a bug here — once the channel is allowlisted the flag is no longer needed. Tracked in [anthropics/claude-plugins-official#1512](https://github.com/anthropics/claude-plugins-official/issues/1512).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sym-bot/mesh-channel",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
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": {
package/server.js CHANGED
@@ -292,15 +292,18 @@ function registerNodeHandlers(n) {
292
292
  if (entry.source === NODE_NAME || entry.cmb?.createdBy === NODE_NAME) return;
293
293
  const source = entry.source || entry.cmb?.createdBy || 'unknown';
294
294
  if (!isPeerAllowed(source)) return;
295
- const focus = entry.cmb?.fields?.focus?.text || entry.content || '';
296
- const mood = entry.cmb?.fields?.mood?.text || '';
295
+ const fields = entry.cmb?.fields || {};
296
+ const payload = entry.cmb?.payload;
297
+ const sec = checkSecurity(source, fields, payload);
298
+ if (!sec.safe) { securityAudit(sec.reason, source, sec.excerpt); return; }
299
+ const focus = fields?.focus?.text || entry.content || '';
300
+ const mood = fields?.mood?.text || '';
297
301
  const moodSuffix = mood && mood !== 'neutral' ? ` (mood: ${mood})` : '';
298
302
  // Store the rendered CMB body so the agent can sym_fetch it by [mNNN] ID.
299
303
  // When the CMB carries an opaque payload alongside CAT7 fields, append a
300
304
  // PAYLOAD section to the stored body so sym_fetch returns it intact;
301
305
  // header gains a [+payload Nb] indicator so the receiver knows there's
302
306
  // structured data beyond CAT7 and should sym_fetch to consume it.
303
- const payload = entry.cmb?.payload;
304
307
  const hasPayload = payload !== undefined && payload !== null;
305
308
  let body = entry.content || focus;
306
309
  let payloadSuffix = '';
@@ -318,6 +321,8 @@ function registerNodeHandlers(n) {
318
321
 
319
322
  n.on('message', (from, content) => {
320
323
  if (!isPeerAllowed(from)) return;
324
+ const sec = checkSecurity(from, { focus: { text: content } }, null);
325
+ if (!sec.safe) { securityAudit(sec.reason, from, sec.excerpt); return; }
321
326
  const msgId = storeMessage(from, content);
322
327
  const header = extractCompactHeader(from, content);
323
328
  pushChannel('message', `[${from}] ${header} [${msgId}]`);
@@ -904,6 +909,108 @@ function isPeerAllowed(peerName) {
904
909
  return ALLOWED_PEERS.includes(peerName);
905
910
  }
906
911
 
912
+ // ── Security: Prompt-Injection Filter (v0.3.11) ──────────────
913
+ // SVAF gates on semantic relevance; this layer gates on safety.
914
+ // It runs on every CAT7 field and payload before pushChannel —
915
+ // the last line of defence before content enters Claude's context.
916
+ //
917
+ // Attack model: a peer with a valid Ed25519 identity sends a CMB
918
+ // whose fields look topically relevant (passes SVAF) but whose
919
+ // content contains instruction-override patterns designed to hijack
920
+ // the receiving Claude session ("ignore previous instructions",
921
+ // role-play overrides, tool-call fabrication, etc.).
922
+ //
923
+ // Strategy: pattern-match on the serialized content of all CAT7
924
+ // fields and the opaque payload. On match: block + audit-log to
925
+ // stderr. Never silently drop — the operator must be able to see
926
+ // what was rejected and why.
927
+
928
+ const INJECTION_PATTERNS = [
929
+ // Classic instruction overrides
930
+ /ignore\s+(all\s+)?(previous|prior|above|earlier)\s+(instructions?|prompts?|context|rules?|guidelines?)/i,
931
+ /disregard\s+(all\s+)?(previous|prior|above|earlier)\s+(instructions?|prompts?|context|rules?)/i,
932
+ /forget\s+(everything|all)\s+(you('ve)?\s+)?(know|been\s+told|learned)/i,
933
+
934
+ // Role / persona hijacking
935
+ /you\s+are\s+now\s+(a\s+|an\s+)?(new\s+)?(ai|assistant|model|system|gpt|claude|llm)/i,
936
+ /act\s+as\s+(a\s+|an\s+)?(different|new|unrestricted|jailbroken|evil|rogue)/i,
937
+ /pretend\s+(you\s+)?(are|have\s+no)\s+(restrictions?|rules?|guidelines?|ethics?)/i,
938
+ /new\s+(persona|personality|mode|role)\s*:/i,
939
+
940
+ // System prompt injection
941
+ /<\s*system\s*>/i,
942
+ /\[SYSTEM\]/,
943
+ /##\s*system\s+prompt/i,
944
+ /---\s*system\s*---/i,
945
+
946
+ // Tool / function call fabrication
947
+ /<\s*tool_call\s*>/i,
948
+ /<\s*function_calls?\s*>/i,
949
+ /\{"type"\s*:\s*"tool_use"/,
950
+
951
+ // Privilege / capability escalation
952
+ /you\s+(now\s+)?(have|possess)\s+(full|unrestricted|admin|root|elevated)\s+(access|permissions?|capabilities?)/i,
953
+ /override\s+(safety|content|ethical?|policy)\s+(filter|check|guard|restriction)/i,
954
+ /jailbreak/i,
955
+ /DAN\s+mode/i,
956
+ ];
957
+
958
+ const PAYLOAD_SIZE_LIMIT = parseInt(process.env.SYM_MAX_PAYLOAD_BYTES || '8192', 10);
959
+
960
+ // Per-peer rate limiter: sliding window, default 30 CMBs/min.
961
+ const RATE_LIMIT = parseInt(process.env.SYM_RATE_LIMIT || '30', 10);
962
+ const RATE_WINDOW_MS = 60_000;
963
+ const peerWindows = new Map(); // peerName → timestamp[]
964
+
965
+ function isRateLimited(peer) {
966
+ const now = Date.now();
967
+ const window = (peerWindows.get(peer) || []).filter(t => now - t < RATE_WINDOW_MS);
968
+ window.push(now);
969
+ peerWindows.set(peer, window);
970
+ return window.length > RATE_LIMIT;
971
+ }
972
+
973
+ function securityAudit(reason, peer, excerpt) {
974
+ const safe = String(excerpt).replace(/[\r\n]+/g, ' ').slice(0, 120);
975
+ process.stderr.write(`[sym-security] BLOCKED reason=${reason} peer=${peer} excerpt="${safe}"\n`);
976
+ }
977
+
978
+ // Returns { safe: true } or { safe: false, reason, excerpt }.
979
+ function checkSecurity(peer, fields, payload) {
980
+ // 1. Rate limit
981
+ if (isRateLimited(peer)) {
982
+ return { safe: false, reason: 'rate-limit', excerpt: `>${RATE_LIMIT} CMBs/min` };
983
+ }
984
+
985
+ // 2. Payload size cap
986
+ if (payload !== undefined && payload !== null) {
987
+ const size = JSON.stringify(payload).length;
988
+ if (size > PAYLOAD_SIZE_LIMIT) {
989
+ return { safe: false, reason: 'payload-too-large', excerpt: `${size}b > ${PAYLOAD_SIZE_LIMIT}b limit` };
990
+ }
991
+ }
992
+
993
+ // 3. Prompt injection scan across all text surfaces
994
+ const surfaces = [
995
+ ...Object.values(fields || {}).map(v =>
996
+ typeof v === 'string' ? v : (typeof v === 'object' && v?.text ? v.text : '')
997
+ ),
998
+ payload !== undefined && payload !== null
999
+ ? (typeof payload === 'string' ? payload : JSON.stringify(payload))
1000
+ : '',
1001
+ ].filter(Boolean);
1002
+
1003
+ for (const surface of surfaces) {
1004
+ for (const pattern of INJECTION_PATTERNS) {
1005
+ if (pattern.test(surface)) {
1006
+ return { safe: false, reason: 'injection-pattern', excerpt: surface.slice(0, 200) };
1007
+ }
1008
+ }
1009
+ }
1010
+
1011
+ return { safe: true };
1012
+ }
1013
+
907
1014
  // ── Mesh Events → Channel Notifications ──────────────────────
908
1015
 
909
1016
  function pushChannel(eventType, data) {