network-ai 5.12.6 → 5.12.7

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/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Website](https://img.shields.io/badge/website-network--ai.org-4b9df2?style=flat&logo=web&logoColor=white)](https://network-ai.org/)
6
6
  [![CI](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml/badge.svg)](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml)
7
7
  [![CodeQL](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml/badge.svg)](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml)
8
- [![Release](https://img.shields.io/badge/release-v5.12.6-blue.svg)](https://github.com/Jovancoding/Network-AI/releases)
8
+ [![Release](https://img.shields.io/badge/release-v5.12.7-blue.svg)](https://github.com/Jovancoding/Network-AI/releases)
9
9
  [![npm](https://img.shields.io/npm/dw/network-ai.svg?label=npm%20downloads)](https://www.npmjs.com/package/network-ai)
10
10
  [![Tests](https://img.shields.io/badge/tests-3269%20passing-brightgreen.svg)](#testing)
11
11
  [![Adapters](https://img.shields.io/badge/frameworks-29%20supported-blueviolet.svg)](#adapter-system)
package/SKILL.md CHANGED
@@ -787,8 +787,8 @@ This skill is scanned on every publish. The following Notes are flagged by desig
787
787
  | **ASI06** Memory and Context Poisoning (audit log free text) | Low | `justification` field in permission requests and `data/audit_log.jsonl` store agent-provided free-text strings locally — PII or secrets placed there will persist on disk | Do not include PII, secrets, or credentials in justification text; restrict access to `data/` on shared machines; rotate/delete `audit_log.jsonl` when no longer needed |
788
788
  | **ASI07** Insecure Inter-Agent Communication | High | Blackboard is local file-based; origin/identity depends on local file access, not authenticated messaging | Run in a trusted workspace; restrict file permissions on `data/`; review blackboard changes before relying on them for important decisions |
789
789
  | **ASI08** Cascading Failures | ~~High~~ Resolved | `os` was referenced before import in `swarm_guard.py` — fixed in v5.4.4; `import os` now present | Fixed — `swarm_guard.py` now imports `os` at module level; budget/health guard starts correctly |
790
- | **SkillSpector** Description-Behavior Mismatch (`McpStreamableServer` network exposure) | Medium, 94% | SKILL.md content discusses `McpStreamableServer` — a native HTTP/MCP server binding a TCP port. Scanner flags this as contradicting the 'zero network calls' / local-only positioning of the bundle. | The Python skill bundle makes zero network calls. `McpStreamableServer` is in the optional npm package only and is never auto-started. Disclosed in the skill overview: *"Only start the separate npm MCP server if you intentionally want a local network control surface."* The `bundle_scope` YAML field distinguishes the two components. |
791
- | **SkillSpector** Context-Inappropriate Capability (MCP control surface breadth) | Medium, 90% | Same finding — the HTTP MCP server exposes 22 privileged tools (blackboard write, token ops, agent_spawn, fsm_transition, audit_query). Scanner flags this as elevating the attack surface of a local orchestration skill. | Server requires a non-empty bearer secret before `listen()` binds (fail-closed). Running it is opt-in (`NETWORK_AI_MCP_SECRET=<secret> npx network-ai-server`). Binds `127.0.0.1` by default. All controls documented in `SUPPLY_CHAIN.md §5a`. |
790
+ | **SkillSpector** Description-Behavior Mismatch (`McpStreamableServer` network exposure) | ~~Medium~~ Resolved | The trigger was `comment.txt` — an in-progress draft GitHub-issue note describing the optional `McpStreamableServer` HTTP/MCP server (a native server binding a TCP port) being bundled into the published ClawHub skill. Its prose contradicted the bundle's 'zero network calls' / local-only positioning. | Fixed in v5.12.7 `comment.txt` added to `.clawhubignore` (the ignore file ClawHub actually honours; the earlier `.clawignore` entry was never read by the CLI). New `scripts/clawhub-check.js` guard (`npm run clawhub:check`) fails the release if any non-allowlisted file would be bundled, so draft notes can no longer leak. The Python skill bundle itself still makes zero network calls; `McpStreamableServer` is in the optional npm package only and is never auto-started. |
791
+ | **SkillSpector** Context-Inappropriate Capability (MCP control surface breadth) | ~~Medium~~ Resolved | Same root cause `comment.txt` enumerated the HTTP MCP server's 22 privileged tools (blackboard write, token ops, agent_spawn, fsm_transition, audit_query), which the scanner read as a broad remote-control surface inside a local skill. | Fixed in v5.12.7 — `comment.txt` excluded from the bundle (see row above) and enforced by the `clawhub:check` guard. The HTTP MCP server itself remains opt-in: it requires a non-empty bearer secret before `listen()` binds (fail-closed), runs only via `NETWORK_AI_MCP_SECRET=<secret> npx network-ai-server`, binds `127.0.0.1` by default, and is documented in `SUPPLY_CHAIN.md §5a`. |
792
792
  | **SkillSpector** Context-Inappropriate Capability (`_load_signing_key()` token minting) | Medium, 92% | `scripts/check_permission.py` mints, HMAC-signs, persists, and lists grant tokens — a de facto local authorization artifact that downstream components may be tempted to treat as real credentials. | Token advisory-only warnings appear in source, SKILL.md, and SECURITY.md. Every grant response includes the advisory notice. Tokens are labeled `grant_{uuid4().hex}`; the HMAC signature only proves local origin, not external identity. Platform-level authentication is required before any destructive action (PAYMENTS, DATABASE, FILE_EXPORT). See ASI03 rows above. |
793
793
  | **SkillSpector** Intent-Code Divergence (`FILE_EXPORT` missing from `HIGH_RISK_RESOURCES`) | ~~Low~~ Resolved | Comment stated `FILE_EXPORT` requires `--confirm-high-risk` but `HIGH_RISK_RESOURCES` only contained `PAYMENTS` and `DATABASE`; file export requests could receive advisory grants without the extra acknowledgment | Fixed in v5.11.0 — `FILE_EXPORT` added to `HIGH_RISK_RESOURCES` in `check_permission.py`; now requires `--confirm-high-risk` consistent with the documented policy |
794
794
  | **SkillSpector** Description-Behavior Mismatch (`ensure_data_dir()` ignoring env scope) | ~~Medium~~ Resolved | `ensure_data_dir()` always created the fixed top-level `data/` directory instead of the active env-specific path, breaking environment isolation when `NETWORK_AI_ENV` is set | Fixed in v5.11.0 — `ensure_data_dir()` now delegates to `_resolve_data_dir()` so audit log and grant files are always written to the correct env-scoped directory |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "network-ai",
3
- "version": "5.12.6",
3
+ "version": "5.12.7",
4
4
  "description": "AI agent orchestration framework for TypeScript/Node.js - 29 adapters (LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw, A2A, Codex, MiniMax, NemoClaw, APS, Copilot, LangGraph, Anthropic Computer Use, OpenAI Agents SDK, Vertex AI, Pydantic AI, Browser Agent, Hermes, Orchestrator, RLM + streaming variants). Built-in CLI, security, swarm intelligence, real-time streaming, and agentic workflow patterns.",
5
5
  "homepage": "https://network-ai.org",
6
6
  "main": "dist/index.js",
@@ -72,7 +72,8 @@
72
72
  "revoke-token": "python scripts/revoke_token.py",
73
73
  "socket:check": "node scripts/socket-check.js",
74
74
  "socket:check:local": "node scripts/socket-check.js --local",
75
- "codeql:check": "node scripts/codeql-check.js"
75
+ "codeql:check": "node scripts/codeql-check.js",
76
+ "clawhub:check": "node scripts/clawhub-check.js"
76
77
  },
77
78
  "keywords": [
78
79
  "ai-agents",
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * clawhub-check.js — ClawHub bundle hygiene guard for network-ai
4
+ *
5
+ * Verifies that a `clawhub publish .` from the repo root would bundle ONLY the
6
+ * intended Python-skill files — no draft notes, secrets, logs, TypeScript/npm
7
+ * docs, or Node tooling.
8
+ *
9
+ * ClawHub honours `.clawhubignore` (gitignore-style denylist) — NOT `.clawignore`
10
+ * and NOT `.gitignore`. This script replicates that exclusion against the repo,
11
+ * then asserts the surviving set is exactly the allowlist below. Any extra file
12
+ * is a leak that SkillSpector (NVIDIA, run by ClawHub on every publish) will
13
+ * scan — so we fail BEFORE publishing, not after.
14
+ *
15
+ * Usage: node scripts/clawhub-check.js | npm run clawhub:check
16
+ * Exit: 0 = bundle clean 1 = leak, secret, or missing required file
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+
24
+ const ROOT = path.join(__dirname, '..');
25
+
26
+ // Files that SHOULD be in the published ClawHub skill bundle.
27
+ const ALLOW_FILES = new Set([
28
+ 'SKILL.md',
29
+ 'swarm-blackboard.md',
30
+ 'requirements.txt',
31
+ 'skill.json',
32
+ '.clawhubignore',
33
+ '.clawignore',
34
+ 'scripts/blackboard.py',
35
+ 'scripts/check_permission.py',
36
+ 'scripts/context_manager.py',
37
+ 'scripts/revoke_token.py',
38
+ 'scripts/swarm_guard.py',
39
+ 'scripts/validate_token.py',
40
+ ]);
41
+
42
+ // Directories allowed in the bundle (descended into for per-file checks).
43
+ const ALLOW_DIRS = new Set(['scripts']);
44
+
45
+ // Files that MUST be present (and not excluded) for the skill to function.
46
+ const REQUIRED = [
47
+ 'SKILL.md',
48
+ 'swarm-blackboard.md',
49
+ 'requirements.txt',
50
+ 'scripts/blackboard.py',
51
+ 'scripts/check_permission.py',
52
+ 'scripts/context_manager.py',
53
+ 'scripts/revoke_token.py',
54
+ 'scripts/swarm_guard.py',
55
+ 'scripts/validate_token.py',
56
+ ];
57
+
58
+ // Hard-deny: these must NEVER appear in a bundle, even if mis-allowlisted.
59
+ const SECRET_RE = /(^|\/)\.env(\.|$)|\.(log|pem|key|p12|pfx)$/i;
60
+
61
+ // Directories never walked (and excluded by .clawhubignore anyway).
62
+ const SKIP_DIRS = new Set(['.git', 'node_modules', 'dist']);
63
+
64
+ const c = {
65
+ reset: '\x1b[0m', bold: '\x1b[1m', red: '\x1b[31m',
66
+ green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m', gray: '\x1b[90m',
67
+ };
68
+
69
+ // ── Load .clawhubignore ──────────────────────────────────────────────────────
70
+
71
+ const ignPath = path.join(ROOT, '.clawhubignore');
72
+ if (!fs.existsSync(ignPath)) {
73
+ console.error(`${c.red}.clawhubignore not found — cannot verify bundle.${c.reset}`);
74
+ process.exit(1);
75
+ }
76
+ const patterns = fs.readFileSync(ignPath, 'utf8')
77
+ .split(/\r?\n/)
78
+ .map(l => l.trim())
79
+ .filter(l => l && !l.startsWith('#'));
80
+
81
+ /** Convert a simple glob (only `*` is special, no `/`) to an anchored RegExp. */
82
+ function globToRe(glob) {
83
+ let re = '';
84
+ for (const ch of glob) {
85
+ if (ch === '*') re += '[^/]*';
86
+ else re += ch.replace(/[.+^${}()|[\]\\?]/g, '\\$&');
87
+ }
88
+ return new RegExp('^' + re + '$');
89
+ }
90
+
91
+ /** Mirror .clawhubignore (gitignore-style) exclusion for one repo-relative path. */
92
+ function isExcluded(relPath, isDir) {
93
+ const base = relPath.split('/').pop();
94
+ for (const pat of patterns) {
95
+ const dirPat = pat.endsWith('/');
96
+ const core = dirPat ? pat.slice(0, -1) : pat;
97
+ if (core.includes('/')) {
98
+ // Full-path pattern (e.g. "scripts/*.js")
99
+ if (globToRe(core).test(relPath)) return true;
100
+ if (relPath.startsWith(core + '/')) return true;
101
+ } else {
102
+ // Basename pattern (e.g. "*.ts", "comment.txt", "assets/")
103
+ if (core === base || core === relPath) return true;
104
+ if (core.includes('*') && globToRe(core).test(base)) return true;
105
+ if (dirPat && isDir && core === base) return true;
106
+ }
107
+ }
108
+ return false;
109
+ }
110
+
111
+ // ── Walk repo (root depth-1 + allowed subdirs) ───────────────────────────────
112
+
113
+ const bundle = [];
114
+ const leaks = [];
115
+
116
+ for (const name of fs.readdirSync(ROOT)) {
117
+ if (SKIP_DIRS.has(name)) continue;
118
+ const isDir = fs.statSync(path.join(ROOT, name)).isDirectory();
119
+
120
+ if (isExcluded(name, isDir)) continue;
121
+
122
+ if (isDir) {
123
+ if (ALLOW_DIRS.has(name)) {
124
+ for (const child of fs.readdirSync(path.join(ROOT, name))) {
125
+ const crel = `${name}/${child}`;
126
+ const cIsDir = fs.statSync(path.join(ROOT, name, child)).isDirectory();
127
+ if (isExcluded(crel, cIsDir)) continue;
128
+ bundle.push(crel);
129
+ if (!ALLOW_FILES.has(crel)) leaks.push(crel);
130
+ }
131
+ } else {
132
+ // A non-allowlisted directory survived exclusion → leak.
133
+ bundle.push(name + '/');
134
+ leaks.push(name + '/ (directory — exclude in .clawhubignore)');
135
+ }
136
+ } else {
137
+ bundle.push(name);
138
+ if (!ALLOW_FILES.has(name)) leaks.push(name);
139
+ }
140
+ }
141
+
142
+ // ── Report ───────────────────────────────────────────────────────────────────
143
+
144
+ console.log(`\n${c.bold}${c.cyan}ClawHub Bundle Hygiene Check${c.reset}`);
145
+ console.log(`${c.gray}${'─'.repeat(50)}${c.reset}`);
146
+ console.log(`Would publish ${bundle.length} item(s):`);
147
+ const leakSet = new Set(leaks.map(l => l.split(' ')[0]));
148
+ for (const b of [...bundle].sort()) {
149
+ const ok = !leakSet.has(b);
150
+ console.log(` ${ok ? c.green + '✓' + c.reset : c.red + '✗' + c.reset} ${b}`);
151
+ }
152
+
153
+ const secretLeaks = bundle.filter(b => SECRET_RE.test(b));
154
+ const missing = REQUIRED.filter(r => !fs.existsSync(path.join(ROOT, r)) || isExcluded(r, false));
155
+
156
+ console.log(`\n${c.gray}${'─'.repeat(50)}${c.reset}`);
157
+ let fail = false;
158
+
159
+ if (leaks.length) {
160
+ fail = true;
161
+ console.log(`${c.red}${c.bold}LEAK${c.reset} — ${leaks.length} unintended item(s) would be published:`);
162
+ for (const l of leaks) console.log(` ${c.red}• ${l}${c.reset}`);
163
+ console.log(` ${c.gray}Add each to .clawhubignore (or to ALLOW_FILES here if genuinely intended).${c.reset}`);
164
+ }
165
+ if (secretLeaks.length) {
166
+ fail = true;
167
+ console.log(`${c.red}${c.bold}SECRET${c.reset} — secret/log file(s) in bundle: ${secretLeaks.join(', ')}`);
168
+ }
169
+ if (missing.length) {
170
+ fail = true;
171
+ console.log(`${c.red}${c.bold}MISSING${c.reset} — required skill file(s) absent or excluded: ${missing.join(', ')}`);
172
+ }
173
+
174
+ if (fail) process.exit(1);
175
+ console.log(`${c.green}${c.bold}PASS${c.reset} — bundle contains only the intended Python-skill files.`);
176
+ process.exit(0);