network-ai 3.2.2 → 3.2.3
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 +135 -9
- package/package.json +1 -1
- package/scripts/check_permission.py +284 -20
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**The plug-and-play AI agent orchestrator for TypeScript/Node.js -- connect 12 agent frameworks with zero glue code**
|
|
4
4
|
|
|
5
|
-
[](https://github.com/jovanSAPFIONEER/Network-AI/releases)
|
|
6
6
|
[](https://clawhub.ai/skills/network-ai)
|
|
7
7
|
[](https://nodejs.org)
|
|
8
8
|
[](https://typescriptlang.org)
|
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
> **Legacy Users:** This skill works with **Clawdbot** and **Moltbot** (now OpenClaw). If you're searching for *Moltbot Security*, *Clawdbot Swarm*, or *Moltbot multi-agent* -- you're in the right place!
|
|
17
17
|
|
|
18
|
-
Network-AI is a framework-agnostic multi-agent orchestrator that connects LLM agents across **12 frameworks** -- LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw, and custom adapters. It provides shared blackboard coordination, built-in security (AES-256, HMAC tokens, rate limiting), content quality gates with hallucination detection, and agentic workflow patterns (parallel
|
|
18
|
+
Network-AI is a framework-agnostic multi-agent orchestrator and **behavioral control plane** that connects LLM agents across **12 frameworks** -- LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw, and custom adapters. It provides shared blackboard coordination with atomic commits, built-in security (AES-256, HMAC tokens, rate limiting), content quality gates with hallucination detection, compliance enforcement, and agentic workflow patterns (parallel fan-out/fan-in, voting, chaining). Zero dependencies per adapter -- bring your own framework SDK and start building governed multi-agent systems in minutes.
|
|
19
19
|
|
|
20
20
|
**Why Network-AI?**
|
|
21
21
|
- **Framework-agnostic** -- Not locked to one LLM provider or agent SDK
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
22
|
+
- **Governance layer** -- Permission gating, audit trails, budget ceilings, and compliance enforcement across all agents
|
|
23
|
+
- **Shared state** -- Atomic blackboard with conflict resolution for safe parallel agent coordination (fan-out/fan-in)
|
|
24
|
+
- **Production security** -- AES-256 encryption, HMAC audit logs, rate limiting, input sanitization
|
|
24
25
|
- **Zero config** -- Works out of the box with `createSwarmOrchestrator()`
|
|
25
26
|
|
|
26
27
|
## Hello World -- Get Running in 60 Seconds
|
|
@@ -131,12 +132,15 @@ Network-AI wraps your agent swarm with **file-system mutexes**, **atomic commits
|
|
|
131
132
|
- **Cryptographic Audit Logs** -- Tamper-evident signed audit trail with chain continuation
|
|
132
133
|
- **Secure Gateway** -- Integrated security layer wrapping all operations
|
|
133
134
|
|
|
134
|
-
### Operational Safety
|
|
135
|
+
### Operational Safety & Governance
|
|
135
136
|
- **Swarm Guard** -- Prevents "Handoff Tax" (wasted tokens) and detects silent agent failures
|
|
136
137
|
- **Atomic Commits** -- File-system mutexes prevent split-brain in concurrent writes
|
|
137
138
|
- **Priority-Based Preemption** -- Higher-priority agents preempt lower-priority writes on same-key conflicts (`priority-wins` strategy)
|
|
138
139
|
- **Cost Awareness** -- Token budget tracking with automatic SafetyShutdown
|
|
139
140
|
- **Budget-Aware Handoffs** -- `intercept-handoff` command wraps `sessions_send` with budget checks
|
|
141
|
+
- **`--active-grants` Observability** -- Real-time view of which agents hold access to which APIs, with TTL countdown
|
|
142
|
+
- **`--audit-summary` Observability** -- Per-agent and per-resource breakdown of permission requests, grants, and denials
|
|
143
|
+
- **Justification Hardening** -- 16-pattern prompt-injection detector, keyword-stuffing defense, structural coherence scoring
|
|
140
144
|
|
|
141
145
|
## Project Structure
|
|
142
146
|
|
|
@@ -190,6 +194,7 @@ Network-AI/
|
|
|
190
194
|
|-- test-standalone.ts # Core orchestrator tests (79 tests)
|
|
191
195
|
|-- test-security.ts # Security module tests (33 tests)
|
|
192
196
|
|-- test-adapters.ts # Adapter system tests (139 tests)
|
|
197
|
+
|-- test-priority.ts # Priority & preemption tests (64 tests)
|
|
193
198
|
|-- test-ai-quality.ts # AI quality gate demo
|
|
194
199
|
|-- test.ts # Full integration test suite
|
|
195
200
|
```
|
|
@@ -341,6 +346,65 @@ Expires: 2026-02-04T15:30:00Z
|
|
|
341
346
|
Restrictions: read_only, max_records:100
|
|
342
347
|
```
|
|
343
348
|
|
|
349
|
+
#### 3a. View Active Grants
|
|
350
|
+
|
|
351
|
+
See which agents currently hold access to which APIs:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
# Human-readable
|
|
355
|
+
python scripts/check_permission.py --active-grants
|
|
356
|
+
|
|
357
|
+
# Filter by agent
|
|
358
|
+
python scripts/check_permission.py --active-grants --agent data_analyst
|
|
359
|
+
|
|
360
|
+
# Machine-readable JSON
|
|
361
|
+
python scripts/check_permission.py --active-grants --json
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Output:
|
|
365
|
+
```
|
|
366
|
+
Active Grants:
|
|
367
|
+
======================================================================
|
|
368
|
+
Agent: data_analyst
|
|
369
|
+
Resource: DATABASE
|
|
370
|
+
Scope: read:orders
|
|
371
|
+
Token: grant_c1ea828897...
|
|
372
|
+
Remaining: 4.4 min
|
|
373
|
+
Restrictions: read_only, max_records:100
|
|
374
|
+
------------------------------------------------------------------
|
|
375
|
+
|
|
376
|
+
Total: 1 active, 0 expired
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### 3b. Audit Summary
|
|
380
|
+
|
|
381
|
+
Summarize permission activity across all agents:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
# Human-readable
|
|
385
|
+
python scripts/check_permission.py --audit-summary
|
|
386
|
+
|
|
387
|
+
# Last 50 entries, JSON output
|
|
388
|
+
python scripts/check_permission.py --audit-summary --last 50 --json
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Output:
|
|
392
|
+
```
|
|
393
|
+
Audit Summary
|
|
394
|
+
======================================================================
|
|
395
|
+
Requests: 12
|
|
396
|
+
Grants: 9
|
|
397
|
+
Denials: 3
|
|
398
|
+
Grant Rate: 75%
|
|
399
|
+
|
|
400
|
+
By Agent:
|
|
401
|
+
--------------------------------------------------
|
|
402
|
+
Agent Requests Grants Denials
|
|
403
|
+
data_analyst 4 3 1
|
|
404
|
+
orchestrator 5 4 1
|
|
405
|
+
strategy_advisor 3 2 1
|
|
406
|
+
```
|
|
407
|
+
|
|
344
408
|
#### 4. Use the Blackboard
|
|
345
409
|
|
|
346
410
|
```bash
|
|
@@ -359,7 +423,44 @@ python scripts/blackboard.py commit "chg_001"
|
|
|
359
423
|
python scripts/blackboard.py list
|
|
360
424
|
```
|
|
361
425
|
|
|
362
|
-
#### 5.
|
|
426
|
+
#### 5. Fan-Out / Fan-In with Shared Blackboard
|
|
427
|
+
|
|
428
|
+
Coordinate multiple specialized agents working on independent subtasks, then merge results:
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import { LockedBlackboard } from 'network-ai';
|
|
432
|
+
import { Logger } from 'network-ai';
|
|
433
|
+
|
|
434
|
+
const logger = Logger.create('fan-out');
|
|
435
|
+
const board = new LockedBlackboard('.', logger, { conflictResolution: 'first-commit-wins' });
|
|
436
|
+
|
|
437
|
+
// Fan-out: each agent writes to its own section
|
|
438
|
+
const agents = ['reliability', 'security', 'cost', 'operations', 'performance'];
|
|
439
|
+
|
|
440
|
+
for (const pillar of agents) {
|
|
441
|
+
// Each agent evaluates independently, writes to its own key
|
|
442
|
+
const id = board.propose(`eval:${pillar}`, { score: Math.random(), findings: [] }, pillar);
|
|
443
|
+
board.validate(id, 'orchestrator');
|
|
444
|
+
board.commit(id);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Fan-in: orchestrator reads all results and merges
|
|
448
|
+
const results = agents.map(pillar => ({
|
|
449
|
+
pillar,
|
|
450
|
+
...board.read(`eval:${pillar}`)
|
|
451
|
+
}));
|
|
452
|
+
|
|
453
|
+
const summary = board.propose('eval:summary', {
|
|
454
|
+
overall: results.reduce((sum, r) => sum + r.score, 0) / results.length,
|
|
455
|
+
pillars: results
|
|
456
|
+
}, 'orchestrator');
|
|
457
|
+
board.validate(summary, 'orchestrator');
|
|
458
|
+
board.commit(summary);
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
This pattern works with any framework adapter -- LangChain agents, AutoGen agents, CrewAI crews, or any mix. The blackboard ensures no agent overwrites another's results.
|
|
462
|
+
|
|
463
|
+
#### 6. Priority-Based Conflict Resolution (Phase 3)
|
|
363
464
|
|
|
364
465
|
```typescript
|
|
365
466
|
import { LockedBlackboard } from 'network-ai';
|
|
@@ -384,7 +485,7 @@ board.commit(highId); // success
|
|
|
384
485
|
board.read('shared:config'); // { mode: 'final' } -- supervisor wins
|
|
385
486
|
```
|
|
386
487
|
|
|
387
|
-
####
|
|
488
|
+
#### 7. Check Budget Status
|
|
388
489
|
|
|
389
490
|
```bash
|
|
390
491
|
python scripts/swarm_guard.py budget-check --task-id "task_001"
|
|
@@ -628,18 +729,43 @@ If you find Network-AI useful, **give it a star** -- it helps others discover th
|
|
|
628
729
|
|
|
629
730
|
**Compatible with 12 agent frameworks: OpenClaw, LangChain, AutoGen, CrewAI, MCP, LlamaIndex, Semantic Kernel, OpenAI Assistants, Haystack, DSPy, Agno, and any custom adapter**
|
|
630
731
|
|
|
732
|
+
## Competitive Comparison
|
|
733
|
+
|
|
734
|
+
How Network-AI compares to other multi-agent frameworks:
|
|
735
|
+
|
|
736
|
+
| Capability | Network-AI | LangChain/LangGraph | AutoGen/AG2 | CrewAI | Claude SDK |
|
|
737
|
+
|---|---|---|---|---|---|
|
|
738
|
+
| **Multi-framework support** | 12 adapters | LangChain only | AutoGen only | CrewAI only | Claude only |
|
|
739
|
+
| **Shared state (blackboard)** | Atomic commits, TTL, priority | LangGraph state | Shared context | Shared memory | Project memory |
|
|
740
|
+
| **Conflict resolution** | Priority preemption, last-write-wins | None | None | None | None |
|
|
741
|
+
| **Fan-out / fan-in** | Native (parallel + merge) | LangGraph branches | Group chat | Parallel tasks | Subagents |
|
|
742
|
+
| **Permission gating** | AuthGuardian (weighted scoring) | None | None | None | None |
|
|
743
|
+
| **Budget tracking** | Token ceiling + per-task budgets | Callbacks only | None | None | None |
|
|
744
|
+
| **Audit trail** | HMAC-signed, tamper-evident | None | None | None | None |
|
|
745
|
+
| **Encryption at rest** | AES-256-GCM | None | None | None | None |
|
|
746
|
+
| **Observability** | `--active-grants`, `--audit-summary` | LangSmith (SaaS) | None | None | None |
|
|
747
|
+
| **Rate limiting** | Per-agent with lockout | None | None | None | None |
|
|
748
|
+
| **Justification hardening** | 16-pattern injection defense | None | None | None | None |
|
|
749
|
+
| **Language** | TypeScript/Node.js | Python | Python | Python | Python |
|
|
750
|
+
| **Dependencies** | Zero (per adapter) | Heavy | Heavy | Heavy | Moderate |
|
|
751
|
+
| **License** | MIT | MIT | CC-BY-4.0 | MIT | MIT |
|
|
752
|
+
|
|
753
|
+
**Key differentiator:** Network-AI is the only framework that combines multi-framework orchestration with a governance layer (permissions, audit, encryption, budget enforcement). Other frameworks focus on one LLM provider; Network-AI wraps all of them.
|
|
754
|
+
|
|
631
755
|
## Related Concepts
|
|
632
756
|
|
|
633
757
|
Network-AI fits into the broader AI agent ecosystem:
|
|
634
758
|
|
|
635
759
|
- **Multi-Agent Systems** -- Coordinate multiple AI agents working together on complex tasks
|
|
636
760
|
- **Agentic AI** -- Build autonomous agents that reason, plan, and execute using LLMs
|
|
637
|
-
- **
|
|
761
|
+
- **Behavioral Control Plane** -- Govern agent behavior with permission gating, compliance enforcement, and audit trails
|
|
762
|
+
- **Swarm Intelligence** -- Parallel fan-out/fan-in patterns with voting, merging, and chain strategies
|
|
638
763
|
- **Model Context Protocol (MCP)** -- Standard protocol support for LLM tool integration
|
|
639
764
|
- **Agent-to-Agent (A2A)** -- Inter-agent communication via shared blackboard and handoff protocol
|
|
640
765
|
- **Context Engineering** -- Manage and share context across agent boundaries
|
|
641
766
|
- **Agentic Workflows** -- Task decomposition, parallel processing, and synthesis pipelines
|
|
642
767
|
- **LLM Orchestration** -- Route tasks to the right agent framework automatically
|
|
768
|
+
- **Agent Governance** -- Permission gating, budget enforcement, audit logging, and compliance monitoring
|
|
643
769
|
|
|
644
770
|
If you're using LangGraph, Dify, Flowise, PraisonAI, AutoGen/AG2, CrewAI, or any other agent framework, Network-AI can integrate with it through the adapter system.
|
|
645
771
|
|
|
@@ -648,6 +774,6 @@ If you're using LangGraph, Dify, Flowise, PraisonAI, AutoGen/AG2, CrewAI, or any
|
|
|
648
774
|
<details>
|
|
649
775
|
<summary>Keywords (for search)</summary>
|
|
650
776
|
|
|
651
|
-
ai-agents, agentic-ai, multi-agent, multi-agent-systems, multi-agent-system, agent-framework, ai-agent-framework, agentic-framework, agentic-workflow, llm, llm-agents, llm-agent, large-language-models, generative-ai, genai, orchestration, ai-orchestration, swarm, swarm-intelligence, autonomous-agents, agents, ai, typescript, nodejs, mcp, model-context-protocol, a2a, agent-to-agent, function-calling, tool-integration, context-engineering, rag, ai-safety, multi-agents-collaboration, multi-agents, aiagents, aiagentframework, plug-and-play, adapter-registry, blackboard-pattern, agent-coordination, agent-handoffs, token-permissions, budget-tracking, cost-awareness, atomic-commits, hallucination-detection, content-quality-gate, OpenClaw, Clawdbot, Moltbot, Clawdbot Swarm, Moltbot Security, Moltbot multi-agent, OpenClaw skills, AgentSkills, LangChain adapter, LangGraph, AutoGen adapter, AG2, CrewAI adapter, MCP adapter, LlamaIndex adapter, Semantic Kernel adapter, OpenAI Assistants adapter, Haystack adapter, DSPy adapter, Agno adapter, Phidata adapter, Dify, Flowise, PraisonAI, custom-adapter, AES-256 encryption, HMAC tokens, rate limiting, input sanitization, privilege escalation prevention, ClawHub, clawhub, agentic-rag, deep-research, workflow-orchestration, ai-assistant, ai-tools, developer-tools, open-source
|
|
777
|
+
ai-agents, agentic-ai, multi-agent, multi-agent-systems, multi-agent-system, agent-framework, ai-agent-framework, agentic-framework, agentic-workflow, llm, llm-agents, llm-agent, large-language-models, generative-ai, genai, orchestration, ai-orchestration, swarm, swarm-intelligence, autonomous-agents, agents, ai, typescript, nodejs, mcp, model-context-protocol, a2a, agent-to-agent, function-calling, tool-integration, context-engineering, rag, ai-safety, multi-agents-collaboration, multi-agents, aiagents, aiagentframework, plug-and-play, adapter-registry, blackboard-pattern, agent-coordination, agent-handoffs, token-permissions, budget-tracking, cost-awareness, atomic-commits, hallucination-detection, content-quality-gate, behavioral-control-plane, governance-layer, compliance-enforcement, fan-out-fan-in, agent-observability, permission-gating, audit-trail, OpenClaw, Clawdbot, Moltbot, Clawdbot Swarm, Moltbot Security, Moltbot multi-agent, OpenClaw skills, AgentSkills, LangChain adapter, LangGraph, AutoGen adapter, AG2, CrewAI adapter, MCP adapter, LlamaIndex adapter, Semantic Kernel adapter, OpenAI Assistants adapter, Haystack adapter, DSPy adapter, Agno adapter, Phidata adapter, Dify, Flowise, PraisonAI, custom-adapter, AES-256 encryption, HMAC tokens, rate limiting, input sanitization, privilege escalation prevention, ClawHub, clawhub, agentic-rag, deep-research, workflow-orchestration, ai-assistant, ai-tools, developer-tools, open-source
|
|
652
778
|
|
|
653
779
|
</details>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "network-ai",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.3",
|
|
4
4
|
"description": "AI agent orchestration framework for TypeScript/Node.js - plug-and-play multi-agent coordination with 12 frameworks (LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw). Built-in security, swarm intelligence, and agentic workflow patterns.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,11 +8,17 @@ Evaluates permission requests for accessing sensitive resources
|
|
|
8
8
|
Usage:
|
|
9
9
|
python check_permission.py --agent AGENT_ID --resource RESOURCE_TYPE \
|
|
10
10
|
--justification "REASON" [--scope SCOPE]
|
|
11
|
+
python check_permission.py --active-grants [--agent AGENT_ID] [--json]
|
|
12
|
+
python check_permission.py --audit-summary [--last N] [--json]
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
Examples:
|
|
13
15
|
python check_permission.py --agent data_analyst --resource DATABASE \
|
|
14
16
|
--justification "Need customer order history for sales report" \
|
|
15
17
|
--scope "read:orders"
|
|
18
|
+
|
|
19
|
+
python check_permission.py --active-grants
|
|
20
|
+
python check_permission.py --active-grants --agent data_analyst --json
|
|
21
|
+
python check_permission.py --audit-summary --last 20 --json
|
|
16
22
|
"""
|
|
17
23
|
|
|
18
24
|
import argparse
|
|
@@ -355,35 +361,277 @@ def evaluate_permission(agent_id: str, resource_type: str,
|
|
|
355
361
|
}
|
|
356
362
|
|
|
357
363
|
|
|
364
|
+
def list_active_grants(agent_filter: Optional[str] = None, as_json: bool = False) -> int:
|
|
365
|
+
"""
|
|
366
|
+
Show which agents currently hold access to which APIs with expiry times.
|
|
367
|
+
|
|
368
|
+
Reads data/active_grants.json, filters out expired grants,
|
|
369
|
+
and displays remaining grants with TTL.
|
|
370
|
+
"""
|
|
371
|
+
if not GRANTS_FILE.exists():
|
|
372
|
+
if as_json:
|
|
373
|
+
print(json.dumps({"grants": [], "total": 0, "expired_cleaned": 0}))
|
|
374
|
+
else:
|
|
375
|
+
print("No active grants. (No grants file found.)")
|
|
376
|
+
return 0
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
grants = json.loads(GRANTS_FILE.read_text())
|
|
380
|
+
except (json.JSONDecodeError, OSError):
|
|
381
|
+
if as_json:
|
|
382
|
+
print(json.dumps({"error": "Could not read grants file"}))
|
|
383
|
+
else:
|
|
384
|
+
print("Error: Could not read grants file.")
|
|
385
|
+
return 1
|
|
386
|
+
|
|
387
|
+
now = datetime.now(timezone.utc)
|
|
388
|
+
active: list[dict[str, Any]] = []
|
|
389
|
+
expired_count = 0
|
|
390
|
+
|
|
391
|
+
for token, grant in grants.items():
|
|
392
|
+
try:
|
|
393
|
+
expires_at = datetime.fromisoformat(grant["expires_at"])
|
|
394
|
+
except (KeyError, ValueError):
|
|
395
|
+
expired_count += 1
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
if expires_at <= now:
|
|
399
|
+
expired_count += 1
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
if agent_filter and grant.get("agent_id") != agent_filter:
|
|
403
|
+
continue
|
|
404
|
+
|
|
405
|
+
remaining = expires_at - now
|
|
406
|
+
minutes_left = remaining.total_seconds() / 60
|
|
407
|
+
|
|
408
|
+
active.append({
|
|
409
|
+
"token": token[:16] + "..." if len(token) > 16 else token,
|
|
410
|
+
"token_full": token,
|
|
411
|
+
"agent_id": grant.get("agent_id", "unknown"),
|
|
412
|
+
"resource_type": grant.get("resource_type", "unknown"),
|
|
413
|
+
"scope": grant.get("scope"),
|
|
414
|
+
"granted_at": grant.get("granted_at", "unknown"),
|
|
415
|
+
"expires_at": grant["expires_at"],
|
|
416
|
+
"minutes_remaining": round(minutes_left, 1),
|
|
417
|
+
"restrictions": grant.get("restrictions", []),
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
# Sort by expiry (soonest first)
|
|
421
|
+
active.sort(key=lambda g: g["expires_at"])
|
|
422
|
+
|
|
423
|
+
if as_json:
|
|
424
|
+
# In JSON mode, include full tokens
|
|
425
|
+
output: dict[str, Any] = {
|
|
426
|
+
"grants": active,
|
|
427
|
+
"total": len(active),
|
|
428
|
+
"expired_cleaned": expired_count,
|
|
429
|
+
}
|
|
430
|
+
print(json.dumps(output, indent=2))
|
|
431
|
+
else:
|
|
432
|
+
if not active:
|
|
433
|
+
filter_msg = f" for agent '{agent_filter}'" if agent_filter else ""
|
|
434
|
+
print(f"No active grants{filter_msg}. ({expired_count} expired.)")
|
|
435
|
+
else:
|
|
436
|
+
filter_msg = f" (agent: {agent_filter})" if agent_filter else ""
|
|
437
|
+
print(f"Active Grants{filter_msg}:")
|
|
438
|
+
print(f"{'='*70}")
|
|
439
|
+
for g in active:
|
|
440
|
+
print(f" Agent: {g['agent_id']}")
|
|
441
|
+
print(f" Resource: {g['resource_type']}")
|
|
442
|
+
if g["scope"]:
|
|
443
|
+
print(f" Scope: {g['scope']}")
|
|
444
|
+
print(f" Token: {g['token']}")
|
|
445
|
+
print(f" Granted: {g['granted_at']}")
|
|
446
|
+
print(f" Expires: {g['expires_at']}")
|
|
447
|
+
print(f" Remaining: {g['minutes_remaining']} min")
|
|
448
|
+
if g["restrictions"]:
|
|
449
|
+
print(f" Restrictions: {', '.join(g['restrictions'])}")
|
|
450
|
+
print(f" {'-'*66}")
|
|
451
|
+
print(f"\nTotal: {len(active)} active, {expired_count} expired")
|
|
452
|
+
|
|
453
|
+
return 0
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def audit_summary(last_n: int = 20, as_json: bool = False) -> int:
|
|
457
|
+
"""
|
|
458
|
+
Summarize recent permission requests, grants, and denials.
|
|
459
|
+
|
|
460
|
+
Parses data/audit_log.jsonl and produces per-agent and per-resource
|
|
461
|
+
breakdowns plus recent activity.
|
|
462
|
+
"""
|
|
463
|
+
if not AUDIT_LOG.exists():
|
|
464
|
+
if as_json:
|
|
465
|
+
print(json.dumps({"entries": 0, "summary": {}, "recent": []}))
|
|
466
|
+
else:
|
|
467
|
+
print("No audit log found. (No permission requests recorded yet.)")
|
|
468
|
+
return 0
|
|
469
|
+
|
|
470
|
+
entries: list[dict[str, Any]] = []
|
|
471
|
+
try:
|
|
472
|
+
with open(AUDIT_LOG, "r") as f:
|
|
473
|
+
for line in f:
|
|
474
|
+
line = line.strip()
|
|
475
|
+
if line:
|
|
476
|
+
try:
|
|
477
|
+
entries.append(json.loads(line))
|
|
478
|
+
except json.JSONDecodeError:
|
|
479
|
+
continue
|
|
480
|
+
except OSError:
|
|
481
|
+
if as_json:
|
|
482
|
+
print(json.dumps({"error": "Could not read audit log"}))
|
|
483
|
+
else:
|
|
484
|
+
print("Error: Could not read audit log.")
|
|
485
|
+
return 1
|
|
486
|
+
|
|
487
|
+
if not entries:
|
|
488
|
+
if as_json:
|
|
489
|
+
print(json.dumps({"entries": 0, "summary": {}, "recent": []}))
|
|
490
|
+
else:
|
|
491
|
+
print("Audit log is empty.")
|
|
492
|
+
return 0
|
|
493
|
+
|
|
494
|
+
# Aggregate stats
|
|
495
|
+
total_requests = 0
|
|
496
|
+
total_grants = 0
|
|
497
|
+
by_agent: dict[str, dict[str, int]] = {}
|
|
498
|
+
by_resource: dict[str, dict[str, int]] = {}
|
|
499
|
+
|
|
500
|
+
for entry in entries:
|
|
501
|
+
action = entry.get("action", "")
|
|
502
|
+
details = entry.get("details", {})
|
|
503
|
+
agent_id = details.get("agent_id", "unknown")
|
|
504
|
+
resource_type = details.get("resource_type", "unknown")
|
|
505
|
+
|
|
506
|
+
if action == "permission_request":
|
|
507
|
+
total_requests += 1
|
|
508
|
+
by_agent.setdefault(agent_id, {"requests": 0, "grants": 0})
|
|
509
|
+
by_agent[agent_id]["requests"] += 1
|
|
510
|
+
by_resource.setdefault(resource_type, {"requests": 0, "grants": 0})
|
|
511
|
+
by_resource[resource_type]["requests"] += 1
|
|
512
|
+
elif action == "permission_granted":
|
|
513
|
+
total_grants += 1
|
|
514
|
+
by_agent.setdefault(agent_id, {"requests": 0, "grants": 0})
|
|
515
|
+
by_agent[agent_id]["grants"] += 1
|
|
516
|
+
by_resource.setdefault(resource_type, {"requests": 0, "grants": 0})
|
|
517
|
+
by_resource[resource_type]["grants"] += 1
|
|
518
|
+
|
|
519
|
+
total_denials = total_requests - total_grants
|
|
520
|
+
|
|
521
|
+
# Recent entries (last N)
|
|
522
|
+
recent = entries[-last_n:]
|
|
523
|
+
|
|
524
|
+
# Time range
|
|
525
|
+
first_ts = entries[0].get("timestamp", "unknown")
|
|
526
|
+
last_ts = entries[-1].get("timestamp", "unknown")
|
|
527
|
+
|
|
528
|
+
if as_json:
|
|
529
|
+
output: dict[str, Any] = {
|
|
530
|
+
"total_entries": len(entries),
|
|
531
|
+
"total_requests": total_requests,
|
|
532
|
+
"total_grants": total_grants,
|
|
533
|
+
"total_denials": total_denials,
|
|
534
|
+
"time_range": {"first": first_ts, "last": last_ts},
|
|
535
|
+
"by_agent": by_agent,
|
|
536
|
+
"by_resource": by_resource,
|
|
537
|
+
"recent": recent[-last_n:],
|
|
538
|
+
}
|
|
539
|
+
print(json.dumps(output, indent=2))
|
|
540
|
+
else:
|
|
541
|
+
print("Audit Summary")
|
|
542
|
+
print(f"{'='*70}")
|
|
543
|
+
print(f" Log entries: {len(entries)}")
|
|
544
|
+
print(f" Time range: {first_ts}")
|
|
545
|
+
print(f" {last_ts}")
|
|
546
|
+
print(f"")
|
|
547
|
+
print(f" Requests: {total_requests}")
|
|
548
|
+
print(f" Grants: {total_grants}")
|
|
549
|
+
print(f" Denials: {total_denials}")
|
|
550
|
+
grant_rate = (total_grants / total_requests * 100) if total_requests > 0 else 0
|
|
551
|
+
print(f" Grant Rate: {grant_rate:.0f}%")
|
|
552
|
+
|
|
553
|
+
if by_agent:
|
|
554
|
+
print(f"\n By Agent:")
|
|
555
|
+
print(f" {'-'*50}")
|
|
556
|
+
print(f" {'Agent':<20} {'Requests':>10} {'Grants':>10} {'Denials':>10}")
|
|
557
|
+
print(f" {'-'*50}")
|
|
558
|
+
for agent_id, stats in sorted(by_agent.items()):
|
|
559
|
+
denials = stats["requests"] - stats["grants"]
|
|
560
|
+
print(f" {agent_id:<20} {stats['requests']:>10} {stats['grants']:>10} {denials:>10}")
|
|
561
|
+
|
|
562
|
+
if by_resource:
|
|
563
|
+
print(f"\n By Resource:")
|
|
564
|
+
print(f" {'-'*50}")
|
|
565
|
+
print(f" {'Resource':<20} {'Requests':>10} {'Grants':>10} {'Denials':>10}")
|
|
566
|
+
print(f" {'-'*50}")
|
|
567
|
+
for resource_type, stats in sorted(by_resource.items()):
|
|
568
|
+
denials = stats["requests"] - stats["grants"]
|
|
569
|
+
print(f" {resource_type:<20} {stats['requests']:>10} {stats['grants']:>10} {denials:>10}")
|
|
570
|
+
|
|
571
|
+
print(f"\n Recent Activity (last {min(last_n, len(recent))}):")
|
|
572
|
+
print(f" {'-'*66}")
|
|
573
|
+
for entry in recent:
|
|
574
|
+
ts = entry.get("timestamp", "?")[:19]
|
|
575
|
+
action = entry.get("action", "?")
|
|
576
|
+
details = entry.get("details", {})
|
|
577
|
+
agent_id = details.get("agent_id", "?")
|
|
578
|
+
resource_type = details.get("resource_type", "?")
|
|
579
|
+
symbol = "GRANT" if action == "permission_granted" else "REQ" if action == "permission_request" else action.upper()
|
|
580
|
+
print(f" {ts} [{symbol:>5}] {agent_id} -> {resource_type}")
|
|
581
|
+
|
|
582
|
+
return 0
|
|
583
|
+
|
|
584
|
+
|
|
358
585
|
def main():
|
|
359
586
|
parser = argparse.ArgumentParser(
|
|
360
587
|
description="AuthGuardian Permission Checker",
|
|
361
588
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
362
589
|
epilog="""
|
|
363
590
|
Examples:
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
591
|
+
Check permission:
|
|
592
|
+
%(prog)s --agent data_analyst --resource DATABASE \\
|
|
593
|
+
--justification "Need Q4 invoice data for quarterly report"
|
|
594
|
+
|
|
595
|
+
List active grants:
|
|
596
|
+
%(prog)s --active-grants
|
|
597
|
+
%(prog)s --active-grants --agent data_analyst --json
|
|
598
|
+
|
|
599
|
+
View audit summary:
|
|
600
|
+
%(prog)s --audit-summary
|
|
601
|
+
%(prog)s --audit-summary --last 50 --json
|
|
370
602
|
"""
|
|
371
603
|
)
|
|
372
|
-
|
|
604
|
+
|
|
605
|
+
# Action flags
|
|
606
|
+
parser.add_argument(
|
|
607
|
+
"--active-grants",
|
|
608
|
+
action="store_true",
|
|
609
|
+
help="List all active (non-expired) permission grants"
|
|
610
|
+
)
|
|
611
|
+
parser.add_argument(
|
|
612
|
+
"--audit-summary",
|
|
613
|
+
action="store_true",
|
|
614
|
+
help="Show audit log summary with per-agent and per-resource breakdowns"
|
|
615
|
+
)
|
|
616
|
+
parser.add_argument(
|
|
617
|
+
"--last",
|
|
618
|
+
type=int,
|
|
619
|
+
default=20,
|
|
620
|
+
help="Number of recent audit entries to show (default: 20)"
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
# Permission check args (required only for check mode)
|
|
373
624
|
parser.add_argument(
|
|
374
625
|
"--agent", "-a",
|
|
375
|
-
required
|
|
376
|
-
help="Agent ID requesting permission"
|
|
626
|
+
help="Agent ID requesting permission (required for check; optional filter for --active-grants)"
|
|
377
627
|
)
|
|
378
628
|
parser.add_argument(
|
|
379
629
|
"--resource", "-r",
|
|
380
|
-
required=True,
|
|
381
630
|
choices=["DATABASE", "PAYMENTS", "EMAIL", "FILE_EXPORT"],
|
|
382
631
|
help="Resource type to access"
|
|
383
632
|
)
|
|
384
633
|
parser.add_argument(
|
|
385
634
|
"--justification", "-j",
|
|
386
|
-
required=True,
|
|
387
635
|
help="Business justification for the request"
|
|
388
636
|
)
|
|
389
637
|
parser.add_argument(
|
|
@@ -395,29 +643,45 @@ Examples:
|
|
|
395
643
|
action="store_true",
|
|
396
644
|
help="Output result as JSON"
|
|
397
645
|
)
|
|
398
|
-
|
|
646
|
+
|
|
399
647
|
args = parser.parse_args()
|
|
400
|
-
|
|
648
|
+
|
|
649
|
+
# --- Action: --active-grants ---
|
|
650
|
+
if args.active_grants:
|
|
651
|
+
sys.exit(list_active_grants(agent_filter=args.agent, as_json=args.json))
|
|
652
|
+
|
|
653
|
+
# --- Action: --audit-summary ---
|
|
654
|
+
if args.audit_summary:
|
|
655
|
+
sys.exit(audit_summary(last_n=args.last, as_json=args.json))
|
|
656
|
+
|
|
657
|
+
# --- Default action: permission check ---
|
|
658
|
+
if not args.agent:
|
|
659
|
+
parser.error("--agent is required for permission checks")
|
|
660
|
+
if not args.resource:
|
|
661
|
+
parser.error("--resource is required for permission checks")
|
|
662
|
+
if not args.justification:
|
|
663
|
+
parser.error("--justification is required for permission checks")
|
|
664
|
+
|
|
401
665
|
result = evaluate_permission(
|
|
402
666
|
agent_id=args.agent,
|
|
403
667
|
resource_type=args.resource,
|
|
404
668
|
justification=args.justification,
|
|
405
669
|
scope=args.scope
|
|
406
670
|
)
|
|
407
|
-
|
|
671
|
+
|
|
408
672
|
if args.json:
|
|
409
673
|
print(json.dumps(result, indent=2))
|
|
410
674
|
else:
|
|
411
675
|
if result["granted"]:
|
|
412
|
-
print("
|
|
676
|
+
print("GRANTED")
|
|
413
677
|
print(f"Token: {result['token']}")
|
|
414
678
|
print(f"Expires: {result['expires_at']}")
|
|
415
679
|
print(f"Restrictions: {', '.join(result['restrictions'])}")
|
|
416
680
|
else:
|
|
417
|
-
print("
|
|
681
|
+
print("DENIED")
|
|
418
682
|
print(f"Reason: {result['reason']}")
|
|
419
|
-
|
|
420
|
-
print("\
|
|
683
|
+
|
|
684
|
+
print("\nEvaluation Scores:")
|
|
421
685
|
scores = result["scores"]
|
|
422
686
|
if scores.get("justification") is not None:
|
|
423
687
|
print(f" Justification: {scores['justification']:.2f}")
|
|
@@ -427,7 +691,7 @@ Examples:
|
|
|
427
691
|
print(f" Risk Score: {scores['risk']:.2f}")
|
|
428
692
|
if scores.get("weighted") is not None:
|
|
429
693
|
print(f" Weighted: {scores['weighted']:.2f}")
|
|
430
|
-
|
|
694
|
+
|
|
431
695
|
sys.exit(0 if result["granted"] else 1)
|
|
432
696
|
|
|
433
697
|
|