network-ai 5.5.0 → 5.5.2
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/INTEGRATION_GUIDE.md +1 -1
- package/README.md +1 -1
- package/SKILL.md +3 -1
- package/package.json +1 -1
- package/scripts/check_permission.py +56 -4
- package/scripts/revoke_token.py +32 -7
- package/scripts/validate_token.py +57 -5
package/INTEGRATION_GUIDE.md
CHANGED
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://network-ai.org/)
|
|
6
6
|
[](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml)
|
|
7
7
|
[](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml)
|
|
8
|
-
[](https://github.com/Jovancoding/Network-AI/releases)
|
|
9
9
|
[](https://www.npmjs.com/package/network-ai)
|
|
10
10
|
[](#testing)
|
|
11
11
|
[](#adapter-system)
|
package/SKILL.md
CHANGED
|
@@ -710,7 +710,9 @@ This skill is scanned on every publish. The following Notes are flagged by desig
|
|
|
710
710
|
| Finding | Confidence | Why it recurs | Documented control |
|
|
711
711
|
|---------|------------|---------------|--------------------|
|
|
712
712
|
| **ASI01** Agent Goal Hijack | High | Orchestrator skill forces 3-sub-task decomposition by design | Use this skill only when multi-agent orchestration is desired; disable for simple one-shot tasks |
|
|
713
|
-
| **ASI03** Identity and Privilege Abuse | High | Grant tokens are advisory only — caller identity is not cryptographically verified | Tokens are explicitly marked advisory in SKILL.md and source; require separate platform auth and human approval before any real database, payment, email, or export action |
|
|
713
|
+
| **ASI03** Identity and Privilege Abuse (advisory tokens) | High | Grant tokens are advisory only — caller identity is not cryptographically verified | Tokens are explicitly marked advisory in SKILL.md and source; require separate platform auth and human approval before any real database, payment, email, or export action |
|
|
714
|
+
| **ASI03** Identity and Privilege Abuse (token integrity) | ~~High~~ Resolved | Token payload had no integrity protection — active_grants.json could be edited to forge elevated grants | Fixed in v5.5.2 — `check_permission.py` HMAC-SHA256 signs each grant (`_sig` field, stdlib `hmac`+`hashlib`, key at `data/.signing_key`); `validate_token.py` verifies before accepting; tampered tokens rejected with `"Token signature invalid"` |
|
|
715
|
+
| **ASI03** Identity and Privilege Abuse (env-scoped paths) | ~~High~~ Resolved | `revoke_token.py` resolved `GRANTS_FILE`/`AUDIT_LOG` at module load from root `data/`, ignoring `NETWORK_AI_ENV` — revoking tokens in one env could silently miss env-specific grant files | Fixed in v5.5.1 — `_resolve_data_dir()` added, `--env` CLI argument introduced, paths re-resolved in `main()` before file I/O; consistent with `check_permission.py` and `validate_token.py` |
|
|
714
716
|
| **ASI06** Memory and Context Poisoning | High | Persistent `data/project-context.json` is injected into agent sessions by design | `_validate_context()` runs injection-pattern detection before every inject; do not store secrets/credentials; review `data/project-context.json` before use; clear `data/` between projects |
|
|
715
717
|
| **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 |
|
|
716
718
|
| **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 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "network-ai",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.2",
|
|
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",
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# SECURITY: This script makes NO network calls and spawns NO subprocesses.
|
|
3
3
|
# All I/O is local file operations only:
|
|
4
|
-
# READS: data/active_grants.json, data/audit_log.jsonl
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# READS: data[/<env>]/active_grants.json, data[/<env>]/audit_log.jsonl,
|
|
5
|
+
# data[/<env>]/.signing_key (HMAC key; auto-created on first run)
|
|
6
|
+
# WRITES: data[/<env>]/active_grants.json, data[/<env>]/audit_log.jsonl,
|
|
7
|
+
# data[/<env>]/.signing_key (on first run only; chmod 0o600)
|
|
8
|
+
# Imports used: argparse, json, re, sys, uuid, hmac, hashlib, datetime, pathlib, typing
|
|
7
9
|
# No imports of: requests, socket, subprocess, urllib, http, ssl, ftplib, smtplib
|
|
8
10
|
"""
|
|
9
11
|
AuthGuardian Permission Checker
|
|
@@ -28,6 +30,8 @@ Examples:
|
|
|
28
30
|
"""
|
|
29
31
|
|
|
30
32
|
import argparse
|
|
33
|
+
import hashlib
|
|
34
|
+
import hmac
|
|
31
35
|
import json
|
|
32
36
|
import re
|
|
33
37
|
import sys
|
|
@@ -264,6 +268,51 @@ def generate_grant_token() -> str:
|
|
|
264
268
|
return f"grant_{uuid.uuid4().hex}"
|
|
265
269
|
|
|
266
270
|
|
|
271
|
+
def _load_signing_key() -> bytes:
|
|
272
|
+
"""
|
|
273
|
+
Load or create the local HMAC-SHA256 signing key for grant tokens.
|
|
274
|
+
|
|
275
|
+
The key is stored at data[/<env>]/.signing_key as a 32-byte hex string.
|
|
276
|
+
It is auto-generated with os.urandom(32) on first use and restricted to
|
|
277
|
+
mode 0o600. It lives inside the env-scoped data dir so multi-environment
|
|
278
|
+
deployments each have an independent key.
|
|
279
|
+
"""
|
|
280
|
+
key_file = _DATA_DIR / ".signing_key"
|
|
281
|
+
if key_file.exists():
|
|
282
|
+
try:
|
|
283
|
+
return bytes.fromhex(key_file.read_text().strip())
|
|
284
|
+
except (ValueError, OSError):
|
|
285
|
+
pass # Corrupt key file — regenerate below
|
|
286
|
+
key = os.urandom(32)
|
|
287
|
+
_DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
288
|
+
key_file.write_text(key.hex())
|
|
289
|
+
try:
|
|
290
|
+
key_file.chmod(0o600) # Restrict to owner; no-op on Windows
|
|
291
|
+
except OSError:
|
|
292
|
+
pass
|
|
293
|
+
return key
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _sign_grant(grant: dict[str, Any]) -> str:
|
|
297
|
+
"""
|
|
298
|
+
Return an HMAC-SHA256 hex signature over the canonical grant fields.
|
|
299
|
+
|
|
300
|
+
Covered fields: token, agent_id, resource_type, scope, expires_at, granted_at.
|
|
301
|
+
Storing this in the grant record lets validate_token.py detect any
|
|
302
|
+
post-issuance tampering of active_grants.json.
|
|
303
|
+
"""
|
|
304
|
+
payload = "|".join([
|
|
305
|
+
grant.get("token", ""),
|
|
306
|
+
grant.get("agent_id", ""),
|
|
307
|
+
grant.get("resource_type", ""),
|
|
308
|
+
grant.get("scope") or "",
|
|
309
|
+
grant.get("expires_at", ""),
|
|
310
|
+
grant.get("granted_at", ""),
|
|
311
|
+
])
|
|
312
|
+
key = _load_signing_key()
|
|
313
|
+
return hmac.new(key, payload.encode("utf-8"), hashlib.sha256).hexdigest()
|
|
314
|
+
|
|
315
|
+
|
|
267
316
|
def log_audit(action: str, details: dict[str, Any]) -> None:
|
|
268
317
|
"""Append entry to audit log."""
|
|
269
318
|
ensure_data_dir()
|
|
@@ -412,7 +461,7 @@ def evaluate_permission(
|
|
|
412
461
|
token = generate_grant_token()
|
|
413
462
|
expires_at = (datetime.now(timezone.utc) + timedelta(minutes=GRANT_TOKEN_TTL_MINUTES)).isoformat()
|
|
414
463
|
restrictions = RESTRICTIONS.get(resource_type, [])
|
|
415
|
-
|
|
464
|
+
|
|
416
465
|
grant: dict[str, Any] = {
|
|
417
466
|
"token": token,
|
|
418
467
|
"agent_id": agent_id,
|
|
@@ -425,6 +474,9 @@ def evaluate_permission(
|
|
|
425
474
|
"unknown_agent": unknown_agent,
|
|
426
475
|
}
|
|
427
476
|
|
|
477
|
+
# Sign the grant — lets validate_token.py detect tampered grant records
|
|
478
|
+
grant["_sig"] = _sign_grant(grant)
|
|
479
|
+
|
|
428
480
|
# Save grant and log
|
|
429
481
|
save_grant(grant)
|
|
430
482
|
log_audit("permission_granted", grant)
|
package/scripts/revoke_token.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# SECURITY: This script makes NO network calls and spawns NO subprocesses.
|
|
3
3
|
# All I/O is local file operations only:
|
|
4
|
-
# READS: data/active_grants.json, data/audit_log.jsonl
|
|
5
|
-
# WRITES: data/active_grants.json, data/audit_log.jsonl
|
|
6
|
-
# Imports used: argparse, json, sys, datetime, pathlib, typing
|
|
4
|
+
# READS: data[/<env>]/active_grants.json, data[/<env>]/audit_log.jsonl
|
|
5
|
+
# WRITES: data[/<env>]/active_grants.json, data[/<env>]/audit_log.jsonl
|
|
6
|
+
# Imports used: argparse, json, os, re, sys, datetime, pathlib, typing
|
|
7
7
|
# No imports of: requests, socket, subprocess, urllib, http, ssl, ftplib, smtplib
|
|
8
8
|
"""
|
|
9
9
|
Revoke Grant Token & TTL Enforcement
|
|
@@ -22,13 +22,27 @@ Example:
|
|
|
22
22
|
|
|
23
23
|
import argparse
|
|
24
24
|
import json
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
25
27
|
import sys
|
|
26
28
|
from datetime import datetime, timezone
|
|
27
29
|
from pathlib import Path
|
|
28
30
|
from typing import Any
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
|
|
33
|
+
def _resolve_data_dir(env: str = "") -> Path:
|
|
34
|
+
"""Return the active data directory, scoped to <env> when set."""
|
|
35
|
+
_env = env or os.environ.get("NETWORK_AI_ENV", "")
|
|
36
|
+
base = Path(__file__).parent.parent / "data"
|
|
37
|
+
if _env:
|
|
38
|
+
if not re.match(r'^[a-zA-Z0-9_-]+$', _env):
|
|
39
|
+
raise ValueError(f"Invalid NETWORK_AI_ENV value: {_env!r}")
|
|
40
|
+
return base / _env
|
|
41
|
+
return base
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
GRANTS_FILE = _resolve_data_dir() / "active_grants.json"
|
|
45
|
+
AUDIT_LOG = _resolve_data_dir() / "audit_log.jsonl"
|
|
32
46
|
|
|
33
47
|
|
|
34
48
|
def log_audit(action: str, details: dict[str, Any]) -> None:
|
|
@@ -186,9 +200,20 @@ def main():
|
|
|
186
200
|
parser.add_argument("--list-expired", action="store_true",
|
|
187
201
|
help="List expired tokens without removing")
|
|
188
202
|
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
189
|
-
|
|
203
|
+
parser.add_argument(
|
|
204
|
+
"--env",
|
|
205
|
+
default="",
|
|
206
|
+
help="Target environment (dev|st|sit|qa|sandbox|preprod|prod). Overrides NETWORK_AI_ENV."
|
|
207
|
+
)
|
|
208
|
+
|
|
190
209
|
args = parser.parse_args()
|
|
191
|
-
|
|
210
|
+
|
|
211
|
+
# Re-resolve data paths if --env was provided explicitly
|
|
212
|
+
global GRANTS_FILE, AUDIT_LOG
|
|
213
|
+
if args.env:
|
|
214
|
+
GRANTS_FILE = _resolve_data_dir(args.env) / "active_grants.json"
|
|
215
|
+
AUDIT_LOG = _resolve_data_dir(args.env) / "audit_log.jsonl"
|
|
216
|
+
|
|
192
217
|
# Handle --list-expired
|
|
193
218
|
if args.list_expired:
|
|
194
219
|
result = list_expired_tokens()
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# SECURITY: This script makes NO network calls and spawns NO subprocesses.
|
|
3
3
|
# All I/O is local file operations only:
|
|
4
|
-
# READS: data/active_grants.json
|
|
4
|
+
# READS: data[/<env>]/active_grants.json, data[/<env>]/.signing_key
|
|
5
5
|
# WRITES: none
|
|
6
|
-
# Imports used: argparse, json, sys, datetime, pathlib, typing
|
|
6
|
+
# Imports used: argparse, json, sys, hmac, hashlib, datetime, pathlib, typing
|
|
7
7
|
# No imports of: requests, socket, subprocess, urllib, http, ssl, ftplib, smtplib
|
|
8
8
|
"""
|
|
9
9
|
Validate Grant Token
|
|
@@ -18,11 +18,13 @@ Example:
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
import argparse
|
|
21
|
+
import hashlib
|
|
22
|
+
import hmac
|
|
21
23
|
import json
|
|
22
24
|
import sys
|
|
23
25
|
from datetime import datetime, timezone
|
|
24
26
|
from pathlib import Path
|
|
25
|
-
from typing import Any
|
|
27
|
+
from typing import Any, Optional
|
|
26
28
|
|
|
27
29
|
def _resolve_data_dir(env: str = "") -> Path:
|
|
28
30
|
"""Return the active data directory, scoped to <env> when set."""
|
|
@@ -38,6 +40,47 @@ def _resolve_data_dir(env: str = "") -> Path:
|
|
|
38
40
|
GRANTS_FILE = _resolve_data_dir() / "active_grants.json"
|
|
39
41
|
|
|
40
42
|
|
|
43
|
+
def _load_signing_key() -> "Optional[bytes]":
|
|
44
|
+
"""
|
|
45
|
+
Load the local HMAC-SHA256 signing key used by check_permission.py.
|
|
46
|
+
Returns None if the key file does not exist or cannot be read.
|
|
47
|
+
"""
|
|
48
|
+
key_file = GRANTS_FILE.parent / ".signing_key"
|
|
49
|
+
if not key_file.exists():
|
|
50
|
+
return None
|
|
51
|
+
try:
|
|
52
|
+
return bytes.fromhex(key_file.read_text().strip())
|
|
53
|
+
except (ValueError, OSError):
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _verify_grant_sig(grant: "dict[str, Any]") -> "Optional[bool]":
|
|
58
|
+
"""
|
|
59
|
+
Verify the HMAC-SHA256 signature stored in a grant record.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
True — signature present and valid
|
|
63
|
+
False — signature present but invalid (tampered)
|
|
64
|
+
None — no signature (pre-v5.5.2 token) or key unavailable
|
|
65
|
+
"""
|
|
66
|
+
stored_sig = grant.get("_sig")
|
|
67
|
+
if not stored_sig:
|
|
68
|
+
return None # Backward-compatible: unsigned token from before v5.5.2
|
|
69
|
+
key = _load_signing_key()
|
|
70
|
+
if key is None:
|
|
71
|
+
return None # Key not present — cannot verify; treat as unverified
|
|
72
|
+
payload = "|".join([
|
|
73
|
+
grant.get("token", ""),
|
|
74
|
+
grant.get("agent_id", ""),
|
|
75
|
+
grant.get("resource_type", ""),
|
|
76
|
+
grant.get("scope") or "",
|
|
77
|
+
grant.get("expires_at", ""),
|
|
78
|
+
grant.get("granted_at", ""),
|
|
79
|
+
])
|
|
80
|
+
expected = hmac.new(key, payload.encode("utf-8"), hashlib.sha256).hexdigest()
|
|
81
|
+
return hmac.compare_digest(expected, stored_sig)
|
|
82
|
+
|
|
83
|
+
|
|
41
84
|
def validate_token(token: str) -> dict[str, Any]:
|
|
42
85
|
"""Validate a grant token and return its details."""
|
|
43
86
|
if not GRANTS_FILE.exists():
|
|
@@ -61,7 +104,15 @@ def validate_token(token: str) -> dict[str, Any]:
|
|
|
61
104
|
}
|
|
62
105
|
|
|
63
106
|
grant = grants[token]
|
|
64
|
-
|
|
107
|
+
|
|
108
|
+
# Guard against tampered grant records (checks HMAC-SHA256 signature)
|
|
109
|
+
sig_result = _verify_grant_sig(grant)
|
|
110
|
+
if sig_result is False:
|
|
111
|
+
return {
|
|
112
|
+
"valid": False,
|
|
113
|
+
"reason": "Token signature invalid — grant record may have been tampered with",
|
|
114
|
+
}
|
|
115
|
+
|
|
65
116
|
# Check expiration
|
|
66
117
|
expires_at = grant.get("expires_at")
|
|
67
118
|
if expires_at:
|
|
@@ -80,7 +131,8 @@ def validate_token(token: str) -> dict[str, Any]:
|
|
|
80
131
|
|
|
81
132
|
return {
|
|
82
133
|
"valid": True,
|
|
83
|
-
"grant": grant
|
|
134
|
+
"grant": grant,
|
|
135
|
+
"sig_verified": sig_result is True,
|
|
84
136
|
}
|
|
85
137
|
|
|
86
138
|
|