@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.
- package/.claude-plugin/marketplace.json +3 -3
- package/README.md +9 -8
- package/package.json +1 -1
- package/server.js +110 -3
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "sym-
|
|
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.
|
|
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.
|
|
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-
|
|
10
|
+
/plugin install sym-mesh-channel@sym-mesh-channel
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
[](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
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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. **
|
|
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
|
|
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-
|
|
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
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
|
|
296
|
-
const
|
|
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) {
|