network-ai 5.8.8 → 5.9.0
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 +2 -2
- package/package.json +1 -1
- package/scripts/check_permission.py +20 -11
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
|
@@ -754,7 +754,7 @@ The following findings are drawn from the **MAESTRO Agent Security Threat** fram
|
|
|
754
754
|
|
|
755
755
|
| Control | How Network-AI addresses it |
|
|
756
756
|
|---|---|
|
|
757
|
-
| **Exact version pinning** | npm `package.json` uses exact `"version": "5.
|
|
757
|
+
| **Exact version pinning** | npm `package.json` uses exact `"version": "5.9.0"` — no semver range specifiers; `clawhub install network-ai` pins to a specific published version |
|
|
758
758
|
| **Zero transitive dependency drift** | All bundled Python scripts use Python stdlib only — `pip install` is never required; there are no third-party packages to drift, be compromised upstream, or introduce CVEs |
|
|
759
759
|
| **Signed, tagged releases** | Every release is committed with a signed Git tag (`v5.7.x`); commit hash is verifiable against CHANGELOG.md; GitHub releases link tag → diff → changelog entry |
|
|
760
760
|
| **Supply chain monitoring** | npm package continuously scored by Socket.dev (score A); any new dependency or permission change triggers an alert |
|
|
@@ -767,7 +767,7 @@ This skill is scanned on every publish. The following Notes are flagged by desig
|
|
|
767
767
|
|
|
768
768
|
| Finding | Severity | Why it recurs | Documented control |
|
|
769
769
|
|---------|----------|---------------|--------------------|
|
|
770
|
-
| **ASI01** Agent Goal Hijack | High | Orchestrator skill
|
|
770
|
+
| **ASI01** Agent Goal Hijack | High | Orchestrator skill performs multi-step decomposition and delegation by design | Use this skill only when multi-agent orchestration is desired; for simple requests the scope guard responds directly without decomposing |
|
|
771
771
|
| **ASI03** Identity and Privilege Abuse (advisory tokens) | Medium | Grant tokens are advisory scoring outputs only — caller-supplied `--agent` identity is not cryptographically verified; skill explicitly warns tokens must not be used as real authorization for PAYMENTS, DATABASE, or FILE_EXPORT | 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 |
|
|
772
772
|
| **ASI03** Identity and Privilege Abuse (local grant state) | Low | The permission system creates persistent local state (`active_grants.json`, `audit_log.jsonl`, `.signing_key`) — security-relevant files that are purpose-aligned but accessible to anyone with `data/` access | Keep the skill directory private; back up or delete local grant state when no longer needed; do not share `data/` casually; restrict OS-level permissions on `data/` on shared machines |
|
|
773
773
|
| **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"` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "network-ai",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0",
|
|
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",
|
|
@@ -620,7 +620,10 @@ def audit_summary(last_n: int = 20, as_json: bool = False) -> int:
|
|
|
620
620
|
Summarize recent permission requests, grants, and denials.
|
|
621
621
|
|
|
622
622
|
Parses data/audit_log.jsonl and produces per-agent and per-resource
|
|
623
|
-
breakdowns plus recent activity.
|
|
623
|
+
breakdowns plus recent activity. Denial counts are read directly from
|
|
624
|
+
``permission_denied`` log entries — they are NOT inferred as
|
|
625
|
+
requests minus grants. This preserves accuracy when log lines are
|
|
626
|
+
missing, replayed, or arrive out of order.
|
|
624
627
|
"""
|
|
625
628
|
if not AUDIT_LOG.exists():
|
|
626
629
|
if as_json:
|
|
@@ -656,29 +659,34 @@ def audit_summary(last_n: int = 20, as_json: bool = False) -> int:
|
|
|
656
659
|
# Aggregate stats
|
|
657
660
|
total_requests = 0
|
|
658
661
|
total_grants = 0
|
|
662
|
+
total_denials = 0
|
|
659
663
|
by_agent: dict[str, dict[str, int]] = {}
|
|
660
664
|
by_resource: dict[str, dict[str, int]] = {}
|
|
661
665
|
|
|
662
666
|
for entry in entries:
|
|
663
667
|
action = entry.get("action", "")
|
|
664
668
|
details = entry.get("details", {})
|
|
665
|
-
agent_id = details.get("agent_id", "unknown")
|
|
666
|
-
resource_type = details.get("resource_type", "unknown")
|
|
669
|
+
agent_id = details.get("agent_id", details.get("agentId", "unknown"))
|
|
670
|
+
resource_type = details.get("resource_type", details.get("resourceType", "unknown"))
|
|
667
671
|
|
|
668
672
|
if action == "permission_request":
|
|
669
673
|
total_requests += 1
|
|
670
|
-
by_agent.setdefault(agent_id, {"requests": 0, "grants": 0})
|
|
674
|
+
by_agent.setdefault(agent_id, {"requests": 0, "grants": 0, "denials": 0})
|
|
671
675
|
by_agent[agent_id]["requests"] += 1
|
|
672
|
-
by_resource.setdefault(resource_type, {"requests": 0, "grants": 0})
|
|
676
|
+
by_resource.setdefault(resource_type, {"requests": 0, "grants": 0, "denials": 0})
|
|
673
677
|
by_resource[resource_type]["requests"] += 1
|
|
674
678
|
elif action == "permission_granted":
|
|
675
679
|
total_grants += 1
|
|
676
|
-
by_agent.setdefault(agent_id, {"requests": 0, "grants": 0})
|
|
680
|
+
by_agent.setdefault(agent_id, {"requests": 0, "grants": 0, "denials": 0})
|
|
677
681
|
by_agent[agent_id]["grants"] += 1
|
|
678
|
-
by_resource.setdefault(resource_type, {"requests": 0, "grants": 0})
|
|
682
|
+
by_resource.setdefault(resource_type, {"requests": 0, "grants": 0, "denials": 0})
|
|
679
683
|
by_resource[resource_type]["grants"] += 1
|
|
680
|
-
|
|
681
|
-
|
|
684
|
+
elif action == "permission_denied":
|
|
685
|
+
total_denials += 1
|
|
686
|
+
by_agent.setdefault(agent_id, {"requests": 0, "grants": 0, "denials": 0})
|
|
687
|
+
by_agent[agent_id]["denials"] += 1
|
|
688
|
+
by_resource.setdefault(resource_type, {"requests": 0, "grants": 0, "denials": 0})
|
|
689
|
+
by_resource[resource_type]["denials"] += 1
|
|
682
690
|
|
|
683
691
|
# Recent entries (last N)
|
|
684
692
|
recent = entries[-last_n:]
|
|
@@ -700,6 +708,7 @@ def audit_summary(last_n: int = 20, as_json: bool = False) -> int:
|
|
|
700
708
|
"total_requests": total_requests,
|
|
701
709
|
"total_grants": total_grants,
|
|
702
710
|
"total_denials": total_denials,
|
|
711
|
+
"denial_source": "explicit_permission_denied_events",
|
|
703
712
|
"time_range": {"first": first_ts, "last": last_ts},
|
|
704
713
|
"by_agent": by_agent,
|
|
705
714
|
"by_resource": by_resource,
|
|
@@ -725,7 +734,7 @@ def audit_summary(last_n: int = 20, as_json: bool = False) -> int:
|
|
|
725
734
|
print(f" {'Agent':<20} {'Requests':>10} {'Grants':>10} {'Denials':>10}")
|
|
726
735
|
print(f" {'-'*50}")
|
|
727
736
|
for agent_id, stats in sorted(by_agent.items()):
|
|
728
|
-
denials = stats["
|
|
737
|
+
denials = stats["denials"]
|
|
729
738
|
print(f" {agent_id:<20} {stats['requests']:>10} {stats['grants']:>10} {denials:>10}")
|
|
730
739
|
|
|
731
740
|
if by_resource:
|
|
@@ -734,7 +743,7 @@ def audit_summary(last_n: int = 20, as_json: bool = False) -> int:
|
|
|
734
743
|
print(f" {'Resource':<20} {'Requests':>10} {'Grants':>10} {'Denials':>10}")
|
|
735
744
|
print(f" {'-'*50}")
|
|
736
745
|
for resource_type, stats in sorted(by_resource.items()):
|
|
737
|
-
denials = stats["
|
|
746
|
+
denials = stats["denials"]
|
|
738
747
|
print(f" {resource_type:<20} {stats['requests']:>10} {stats['grants']:>10} {denials:>10}")
|
|
739
748
|
|
|
740
749
|
print(f"\n Recent Activity (last {min(last_n, len(recent))}):")
|