actshield 0.9.0__py3-none-any.whl
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.
- actshield/__init__.py +439 -0
- actshield/__main__.py +46 -0
- actshield/agents/__init__.py +12 -0
- actshield/agents/agent.py +200 -0
- actshield/agents/identity.py +114 -0
- actshield/api/__init__.py +34 -0
- actshield/api/models.py +198 -0
- actshield/api/server.py +669 -0
- actshield/api/service.py +599 -0
- actshield/approval/__init__.py +15 -0
- actshield/approval/approval.py +164 -0
- actshield/cli/__init__.py +8 -0
- actshield/cli/graph.py +49 -0
- actshield/cli/main.py +802 -0
- actshield/cli/shell.py +406 -0
- actshield/cli/theme.py +110 -0
- actshield/client.py +757 -0
- actshield/config.py +88 -0
- actshield/context/__init__.py +14 -0
- actshield/context/context.py +161 -0
- actshield/context/provenance.py +108 -0
- actshield/context/taint.py +48 -0
- actshield/decisions/decision.py +74 -0
- actshield/delegation/__init__.py +11 -0
- actshield/delegation/authority.py +63 -0
- actshield/delegation/delegation.py +180 -0
- actshield/demo.py +139 -0
- actshield/doctor.py +94 -0
- actshield/drift/__init__.py +27 -0
- actshield/drift/drift_engine.py +149 -0
- actshield/drift/drift_models.py +63 -0
- actshield/evaluation/__init__.py +1 -0
- actshield/evaluation/cases/__init__.py +1 -0
- actshield/evaluation/eval_suite.py +254 -0
- actshield/forensics/__init__.py +53 -0
- actshield/forensics/access_graph.py +216 -0
- actshield/forensics/authority_graph.py +186 -0
- actshield/forensics/delegation_graph.py +119 -0
- actshield/forensics/explanation.py +254 -0
- actshield/forensics/models.py +488 -0
- actshield/forensics/queries.py +367 -0
- actshield/forensics/serializers.py +68 -0
- actshield/forensics/service.py +310 -0
- actshield/forensics/snapshots.py +174 -0
- actshield/gates/__init__.py +13 -0
- actshield/gates/gate_models.py +46 -0
- actshield/gates/security_gate.py +191 -0
- actshield/gateway/__init__.py +13 -0
- actshield/gateway/http.py +191 -0
- actshield/gateway/mcp.py +292 -0
- actshield/incidents/__init__.py +25 -0
- actshield/incidents/incident.py +94 -0
- actshield/incidents/incident_engine.py +163 -0
- actshield/incidents/incident_rules.py +116 -0
- actshield/incidents/incident_state.py +58 -0
- actshield/integrations/__init__.py +24 -0
- actshield/integrations/ai_secura.py +169 -0
- actshield/integrations/ai_secura_analysis.py +236 -0
- actshield/integrations/apiris.py +122 -0
- actshield/integrations/few_shot.py +104 -0
- actshield/integrations/ollama_adapter.py +538 -0
- actshield/integrations/prompts/__init__.py +1 -0
- actshield/integrations/prompts/examples/__init__.py +1 -0
- actshield/integrations/security_packet.py +82 -0
- actshield/llm_config.py +89 -0
- actshield/offensive/__init__.py +95 -0
- actshield/offensive/adaptive/__init__.py +22 -0
- actshield/offensive/adaptive/adaptive_engine.py +165 -0
- actshield/offensive/adaptive/attack_selector.py +51 -0
- actshield/offensive/adaptive/boundary_search.py +99 -0
- actshield/offensive/adaptive/campaign_memory.py +123 -0
- actshield/offensive/adaptive/feedback.py +84 -0
- actshield/offensive/adaptive/mutation_planner.py +57 -0
- actshield/offensive/attack.py +78 -0
- actshield/offensive/campaign.py +219 -0
- actshield/offensive/corpus.py +155 -0
- actshield/offensive/engine.py +466 -0
- actshield/offensive/evaluator.py +116 -0
- actshield/offensive/evidence.py +63 -0
- actshield/offensive/generator.py +128 -0
- actshield/offensive/mutation.py +101 -0
- actshield/offensive/results.py +77 -0
- actshield/offensive/safety.py +80 -0
- actshield/offensive/strategies/__init__.py +22 -0
- actshield/offensive/strategies/authority_escalation.py +100 -0
- actshield/offensive/strategies/base.py +34 -0
- actshield/offensive/strategies/context_manipulation.py +100 -0
- actshield/offensive/strategies/data_exfiltration.py +100 -0
- actshield/offensive/strategies/delegation_attacks.py +100 -0
- actshield/offensive/strategies/mcp_attacks.py +100 -0
- actshield/offensive/strategies/prompt_injection.py +100 -0
- actshield/offensive/strategies/tool_poisoning.py +100 -0
- actshield/offensive/target.py +113 -0
- actshield/offensive/vulnerable_target.py +121 -0
- actshield/persistence/__init__.py +18 -0
- actshield/persistence/siem.py +102 -0
- actshield/persistence/storage.py +240 -0
- actshield/policy/__init__.py +12 -0
- actshield/policy/evaluator.py +294 -0
- actshield/policy/intent.py +113 -0
- actshield/policy/policy.py +48 -0
- actshield/posture/__init__.py +26 -0
- actshield/posture/posture_engine.py +186 -0
- actshield/posture/posture_models.py +152 -0
- actshield/posture/posture_rules.py +137 -0
- actshield/providers/__init__.py +32 -0
- actshield/providers/anthropic_provider.py +146 -0
- actshield/providers/base.py +160 -0
- actshield/providers/gemini_provider.py +133 -0
- actshield/providers/null_provider.py +40 -0
- actshield/providers/ollama_provider.py +135 -0
- actshield/providers/openai_provider.py +142 -0
- actshield/providers/registry.py +244 -0
- actshield/py.typed +0 -0
- actshield/response/__init__.py +14 -0
- actshield/response/response_actions.py +39 -0
- actshield/response/response_audit.py +32 -0
- actshield/response/response_engine.py +139 -0
- actshield/risk/models.py +104 -0
- actshield/serve.py +117 -0
- actshield/static/404.html +1 -0
- actshield/static/__next.__PAGE__.txt +24 -0
- actshield/static/__next._full.txt +20 -0
- actshield/static/__next._tree.txt +4 -0
- actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_buildManifest.js +11 -0
- actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_clientMiddlewareManifest.js +1 -0
- actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_ssgManifest.js +1 -0
- actshield/static/_next/static/chunks/09kny2eqjc2or.js +1 -0
- actshield/static/_next/static/chunks/0c1zba1u9whdp.js +1 -0
- actshield/static/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
- actshield/static/_next/static/chunks/0dbjjeg01x25o.js +1 -0
- actshield/static/_next/static/chunks/0duobs55ne238.js +1 -0
- actshield/static/_next/static/chunks/0iaz41l6068e4.js +1 -0
- actshield/static/_next/static/chunks/0mjhyv72x2idp.js +1 -0
- actshield/static/_next/static/chunks/0nelpqfu_ciq6.js +1 -0
- actshield/static/_next/static/chunks/0p1bci9ch797v.js +1 -0
- actshield/static/_next/static/chunks/0tld_ygho5tgm.js +1 -0
- actshield/static/_next/static/chunks/0w1-y7oni66j9.js +1 -0
- actshield/static/_next/static/chunks/10537zyl427zj.js +1 -0
- actshield/static/_next/static/chunks/11pc_jkl3aggn.js +1 -0
- actshield/static/_next/static/chunks/16k7nw0b3t8ys.js +1 -0
- actshield/static/_next/static/chunks/19mx3mg6lkumu.js +1 -0
- actshield/static/_next/static/chunks/1_8p47b-kxg2a.js +1 -0
- actshield/static/_next/static/chunks/1dszjozwfqegx.js +1 -0
- actshield/static/_next/static/chunks/1h43jcb90-m2r.js +22 -0
- actshield/static/_next/static/chunks/1ly04cvt_wh_j.js +1 -0
- actshield/static/_next/static/chunks/1odxrypuwuwp_.js +1 -0
- actshield/static/_next/static/chunks/1oes4c35qz5ku.js +1 -0
- actshield/static/_next/static/chunks/1ow883jl4ln-r.js +16 -0
- actshield/static/_next/static/chunks/1po7bna1as8tc.js +1 -0
- actshield/static/_next/static/chunks/1rsh0og8ifkzx.js +1 -0
- actshield/static/_next/static/chunks/1v0lwzv-859z_.js +1 -0
- actshield/static/_next/static/chunks/25iat2epwx25a.js +1 -0
- actshield/static/_next/static/chunks/25uvt4s2f39a4.js +1 -0
- actshield/static/_next/static/chunks/27t_qfc-3_lzs.js +1 -0
- actshield/static/_next/static/chunks/28bkmy8mez7gm.js +1 -0
- actshield/static/_next/static/chunks/2b6brmw_drw1p.js +1 -0
- actshield/static/_next/static/chunks/2fnhtxi4j6vge.js +1 -0
- actshield/static/_next/static/chunks/2p087eftxemg4.js +6 -0
- actshield/static/_next/static/chunks/2s8pao_z8pgtq.js +1 -0
- actshield/static/_next/static/chunks/2yj7ooc2c-zpg.js +1 -0
- actshield/static/_next/static/chunks/3-mrrr8udt5tk.js +1 -0
- actshield/static/_next/static/chunks/310vm2bl3xxpt.js +31 -0
- actshield/static/_next/static/chunks/35mvsv251jx1r.css +3 -0
- actshield/static/_next/static/chunks/370vf0oetlsx9.js +1 -0
- actshield/static/_next/static/chunks/3e-2m72tyo556.js +1 -0
- actshield/static/_next/static/chunks/3fntmmi971322.js +1 -0
- actshield/static/_next/static/chunks/3fzw-g0hycl2c.js +1 -0
- actshield/static/_next/static/chunks/3lopq3lhmcjxg.js +129 -0
- actshield/static/_next/static/chunks/3mpe2jejel0v2.css +1 -0
- actshield/static/_next/static/chunks/3t8o4f2lzr2sf.js +1 -0
- actshield/static/_next/static/chunks/3y-v1neyllne5.js +1 -0
- actshield/static/_next/static/chunks/43oiwy8mx6cs1.js +1 -0
- actshield/static/_next/static/chunks/turbopack-23wzuebcy_lxm.js +1 -0
- actshield/static/_next/static/media/1bffadaabf893a1e-s.3-6t-g6q0vh0a.woff2 +0 -0
- actshield/static/_next/static/media/2bbe8d2671613f1f-s.0k62hbripvv8p.woff2 +0 -0
- actshield/static/_next/static/media/2c55a0e60120577a-s.0-dom-5bn10r2.woff2 +0 -0
- actshield/static/_next/static/media/5476f68d60460930-s.2uwcyprjm3xu3.woff2 +0 -0
- actshield/static/_next/static/media/83afe278b6a6bb3c-s.p.2bn3s6zvc0dyp.woff2 +0 -0
- actshield/static/_next/static/media/9c72aa0f40e4eef8-s.1y4-pdgsjb-pw.woff2 +0 -0
- actshield/static/_next/static/media/ad66f9afd8947f86-s.3lvt2whj97whp.woff2 +0 -0
- actshield/static/_next/static/media/favicon.2vob68tjqpejf.ico +0 -0
- actshield/static/_not-found/__next._full.txt +17 -0
- actshield/static/_not-found/__next._not-found/__PAGE__.txt +23 -0
- actshield/static/_not-found/__next._tree.txt +3 -0
- actshield/static/_not-found.html +1 -0
- actshield/static/_not-found.txt +17 -0
- actshield/static/access/matrix/__next._full.txt +24 -0
- actshield/static/access/matrix/__next._tree.txt +4 -0
- actshield/static/access/matrix/__next.access/matrix/__PAGE__.txt +30 -0
- actshield/static/access/matrix.html +1 -0
- actshield/static/access/matrix.txt +24 -0
- actshield/static/activity/__next._full.txt +22 -0
- actshield/static/activity/__next._tree.txt +4 -0
- actshield/static/activity/__next.activity/__PAGE__.txt +27 -0
- actshield/static/activity.html +1 -0
- actshield/static/activity.txt +22 -0
- actshield/static/agents/__next._full.txt +22 -0
- actshield/static/agents/__next._tree.txt +4 -0
- actshield/static/agents/__next.agents/__PAGE__.txt +27 -0
- actshield/static/agents.html +1 -0
- actshield/static/agents.txt +22 -0
- actshield/static/ai-secura/__next._full.txt +22 -0
- actshield/static/ai-secura/__next._tree.txt +4 -0
- actshield/static/ai-secura/__next.ai-secura/__PAGE__.txt +27 -0
- actshield/static/ai-secura.html +1 -0
- actshield/static/ai-secura.txt +22 -0
- actshield/static/api/__next._full.txt +22 -0
- actshield/static/api/__next._tree.txt +4 -0
- actshield/static/api/__next.api/__PAGE__.txt +27 -0
- actshield/static/api.html +15 -0
- actshield/static/api.txt +22 -0
- actshield/static/apiris/__next._full.txt +22 -0
- actshield/static/apiris/__next._tree.txt +4 -0
- actshield/static/apiris/__next.apiris/__PAGE__.txt +27 -0
- actshield/static/apiris.html +1 -0
- actshield/static/apiris.txt +22 -0
- actshield/static/attack-graph/__next._full.txt +23 -0
- actshield/static/attack-graph/__next._tree.txt +5 -0
- actshield/static/attack-graph/__next.attack-graph/__PAGE__.txt +28 -0
- actshield/static/attack-graph.html +2 -0
- actshield/static/attack-graph.txt +23 -0
- actshield/static/campaigns/__next._full.txt +22 -0
- actshield/static/campaigns/__next._tree.txt +4 -0
- actshield/static/campaigns/__next.campaigns/__PAGE__.txt +27 -0
- actshield/static/campaigns.html +1 -0
- actshield/static/campaigns.txt +22 -0
- actshield/static/context/__next._full.txt +22 -0
- actshield/static/context/__next._tree.txt +4 -0
- actshield/static/context/__next.context/__PAGE__.txt +27 -0
- actshield/static/context.html +1 -0
- actshield/static/context.txt +22 -0
- actshield/static/dashboard/__next._full.txt +22 -0
- actshield/static/dashboard/__next._tree.txt +4 -0
- actshield/static/dashboard/__next.dashboard/__PAGE__.txt +27 -0
- actshield/static/dashboard.html +1 -0
- actshield/static/dashboard.txt +22 -0
- actshield/static/delegations/__next._full.txt +22 -0
- actshield/static/delegations/__next._tree.txt +4 -0
- actshield/static/delegations/__next.delegations/__PAGE__.txt +27 -0
- actshield/static/delegations.html +1 -0
- actshield/static/delegations.txt +22 -0
- actshield/static/demo/__next._full.txt +22 -0
- actshield/static/demo/__next._tree.txt +4 -0
- actshield/static/demo/__next.demo/__PAGE__.txt +27 -0
- actshield/static/demo.html +1 -0
- actshield/static/demo.txt +22 -0
- actshield/static/drift/__next._full.txt +22 -0
- actshield/static/drift/__next._tree.txt +4 -0
- actshield/static/drift/__next.drift/__PAGE__.txt +27 -0
- actshield/static/drift.html +1 -0
- actshield/static/drift.txt +22 -0
- actshield/static/favicon.ico +0 -0
- actshield/static/file.svg +1 -0
- actshield/static/forensics/__next._full.txt +22 -0
- actshield/static/forensics/__next._tree.txt +4 -0
- actshield/static/forensics/__next.forensics/__PAGE__.txt +27 -0
- actshield/static/forensics.html +1 -0
- actshield/static/forensics.txt +22 -0
- actshield/static/globe.svg +1 -0
- actshield/static/incidents/__next._full.txt +22 -0
- actshield/static/incidents/__next._tree.txt +4 -0
- actshield/static/incidents/__next.incidents/__PAGE__.txt +27 -0
- actshield/static/incidents.html +1 -0
- actshield/static/incidents.txt +22 -0
- actshield/static/index.html +3 -0
- actshield/static/index.txt +20 -0
- actshield/static/logo.png +0 -0
- actshield/static/metrics/__next._full.txt +22 -0
- actshield/static/metrics/__next._tree.txt +4 -0
- actshield/static/metrics/__next.metrics/__PAGE__.txt +27 -0
- actshield/static/metrics.html +1 -0
- actshield/static/metrics.txt +22 -0
- actshield/static/next.svg +1 -0
- actshield/static/offensive/__next._full.txt +22 -0
- actshield/static/offensive/__next._tree.txt +4 -0
- actshield/static/offensive/__next.offensive/__PAGE__.txt +27 -0
- actshield/static/offensive.html +1 -0
- actshield/static/offensive.txt +22 -0
- actshield/static/policies/__next._full.txt +22 -0
- actshield/static/policies/__next._tree.txt +4 -0
- actshield/static/policies/__next.policies/__PAGE__.txt +27 -0
- actshield/static/policies.html +1 -0
- actshield/static/policies.txt +22 -0
- actshield/static/posture/__next._full.txt +22 -0
- actshield/static/posture/__next._tree.txt +4 -0
- actshield/static/posture/__next.posture/__PAGE__.txt +27 -0
- actshield/static/posture.html +1 -0
- actshield/static/posture.txt +22 -0
- actshield/static/regressions/__next._full.txt +22 -0
- actshield/static/regressions/__next._tree.txt +4 -0
- actshield/static/regressions/__next.regressions/__PAGE__.txt +27 -0
- actshield/static/regressions.html +1 -0
- actshield/static/regressions.txt +22 -0
- actshield/static/reports/__next._full.txt +22 -0
- actshield/static/reports/__next._tree.txt +4 -0
- actshield/static/reports/__next.reports/__PAGE__.txt +27 -0
- actshield/static/reports.html +1 -0
- actshield/static/reports.txt +22 -0
- actshield/static/resources/__next._full.txt +22 -0
- actshield/static/resources/__next._tree.txt +4 -0
- actshield/static/resources/__next.resources/__PAGE__.txt +27 -0
- actshield/static/resources.html +1 -0
- actshield/static/resources.txt +22 -0
- actshield/static/security-gates/__next._full.txt +22 -0
- actshield/static/security-gates/__next._tree.txt +4 -0
- actshield/static/security-gates/__next.security-gates/__PAGE__.txt +27 -0
- actshield/static/security-gates.html +1 -0
- actshield/static/security-gates.txt +22 -0
- actshield/static/settings/__next._full.txt +22 -0
- actshield/static/settings/__next._tree.txt +4 -0
- actshield/static/settings/__next.settings/__PAGE__.txt +27 -0
- actshield/static/settings.html +1 -0
- actshield/static/settings.txt +22 -0
- actshield/static/tasks/__next._full.txt +22 -0
- actshield/static/tasks/__next._tree.txt +4 -0
- actshield/static/tasks/__next.tasks/__PAGE__.txt +27 -0
- actshield/static/tasks.html +1 -0
- actshield/static/tasks.txt +22 -0
- actshield/static/threats/__next._full.txt +22 -0
- actshield/static/threats/__next._tree.txt +4 -0
- actshield/static/threats/__next.threats/__PAGE__.txt +27 -0
- actshield/static/threats.html +1 -0
- actshield/static/threats.txt +22 -0
- actshield/static/tools/__next._full.txt +22 -0
- actshield/static/tools/__next._tree.txt +4 -0
- actshield/static/tools/__next.tools/__PAGE__.txt +27 -0
- actshield/static/tools.html +1 -0
- actshield/static/tools.txt +22 -0
- actshield/static/traces/__next._full.txt +22 -0
- actshield/static/traces/__next._tree.txt +4 -0
- actshield/static/traces/__next.traces/__PAGE__.txt +27 -0
- actshield/static/traces.html +1 -0
- actshield/static/traces.txt +22 -0
- actshield/static/vercel.svg +1 -0
- actshield/static/window.svg +1 -0
- actshield/tasks/__init__.py +6 -0
- actshield/tasks/task.py +143 -0
- actshield/threatmodel/__init__.py +70 -0
- actshield/threatmodel/actors.py +210 -0
- actshield/threatmodel/analyzer.py +131 -0
- actshield/threatmodel/assets.py +167 -0
- actshield/threatmodel/boundaries.py +165 -0
- actshield/threatmodel/exporters.py +49 -0
- actshield/threatmodel/mitigations.py +209 -0
- actshield/threatmodel/models.py +80 -0
- actshield/threatmodel/report.py +341 -0
- actshield/threatmodel/scenarios.py +212 -0
- actshield/threatmodel/scoring.py +66 -0
- actshield/threatmodel/threats.py +387 -0
- actshield/tools/__init__.py +20 -0
- actshield/tools/interceptor.py +256 -0
- actshield/tools/tool.py +71 -0
- actshield/tracing/__init__.py +38 -0
- actshield/tracing/correlation.py +173 -0
- actshield/tracing/events.py +75 -0
- actshield/tracing/tracer.py +305 -0
- actshield/watch.py +129 -0
- actshield-0.9.0.dist-info/METADATA +57 -0
- actshield-0.9.0.dist-info/RECORD +366 -0
- actshield-0.9.0.dist-info/WHEEL +5 -0
- actshield-0.9.0.dist-info/entry_points.txt +4 -0
- actshield-0.9.0.dist-info/top_level.txt +2 -0
- agentguard/__init__.py +39 -0
- agentguard/__main__.py +8 -0
- agentguard/py.typed +1 -0
actshield/cli/main.py
ADDED
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
"""ActShield Production CLI — Entrypoint and Command Dispatcher.
|
|
2
|
+
|
|
3
|
+
CLI structure:
|
|
4
|
+
actshield → interactive security shell
|
|
5
|
+
actshield status → system status
|
|
6
|
+
actshield posture → security posture scorecard
|
|
7
|
+
actshield agents → registered agents
|
|
8
|
+
actshield incidents → security incidents
|
|
9
|
+
actshield drift → behavioral drift detection
|
|
10
|
+
actshield doctor → system diagnostic
|
|
11
|
+
actshield demo → interactive security simulation
|
|
12
|
+
actshield watch → live event stream
|
|
13
|
+
actshield serve → launch API + dashboard
|
|
14
|
+
|
|
15
|
+
actshield ai list|status|use → AI provider management
|
|
16
|
+
actshield attack adaptive|list → offensive validation
|
|
17
|
+
actshield gate evaluate → CI/CD security gate
|
|
18
|
+
actshield dashboard start|serve → dashboard management
|
|
19
|
+
|
|
20
|
+
actshield threat model|list|inspect|analyze|graph|report|export
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
from typing import Optional
|
|
27
|
+
|
|
28
|
+
import typer
|
|
29
|
+
from rich.console import Console
|
|
30
|
+
|
|
31
|
+
from actshield.cli.shell import ActShieldShell
|
|
32
|
+
from actshield.cli.theme import render_banner, render_startup_status, ACTSHIELD_THEME
|
|
33
|
+
from actshield.demo import run_demo
|
|
34
|
+
from actshield.doctor import run_doctor
|
|
35
|
+
from actshield.providers.registry import get_provider_registry
|
|
36
|
+
|
|
37
|
+
# Windows UTF-8 console output
|
|
38
|
+
if sys.platform == "win32":
|
|
39
|
+
try:
|
|
40
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
41
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
# ── Typer app tree ─────────────────────────────────────────────────────────
|
|
46
|
+
app = typer.Typer(
|
|
47
|
+
name="agentguard",
|
|
48
|
+
help="AgentGuard — AI Security Control Plane",
|
|
49
|
+
no_args_is_help=False,
|
|
50
|
+
add_completion=False,
|
|
51
|
+
rich_markup_mode="rich",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
ai_app = typer.Typer(name="ai", help="Manage AI Security Intelligence Providers")
|
|
55
|
+
attack_app = typer.Typer(name="attack", help="Offensive security validation & campaigns")
|
|
56
|
+
gate_app = typer.Typer(name="gate", help="Evaluate CI/CD security quality gates")
|
|
57
|
+
dashboard_app = typer.Typer(name="dashboard", help="Manage the embedded dashboard")
|
|
58
|
+
threat_app = typer.Typer(name="threat", help="Threat modeling — assets, actors, boundaries, analysis")
|
|
59
|
+
forensic_app = typer.Typer(name="forensic", help="Forensic investigation and causal trace analysis")
|
|
60
|
+
|
|
61
|
+
app.add_typer(ai_app, name="ai")
|
|
62
|
+
app.add_typer(attack_app, name="attack")
|
|
63
|
+
app.add_typer(attack_app, name="offensive")
|
|
64
|
+
app.add_typer(gate_app, name="gate")
|
|
65
|
+
app.add_typer(dashboard_app, name="dashboard")
|
|
66
|
+
app.add_typer(threat_app, name="threat")
|
|
67
|
+
app.add_typer(forensic_app, name="forensic")
|
|
68
|
+
app.add_typer(forensic_app, name="forensics")
|
|
69
|
+
|
|
70
|
+
console = Console(theme=ACTSHIELD_THEME)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ── Root callback ───────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
@app.callback(invoke_without_command=True)
|
|
76
|
+
def main_callback(ctx: typer.Context) -> None:
|
|
77
|
+
"""Launch interactive security console when invoked without a subcommand."""
|
|
78
|
+
if ctx.invoked_subcommand is None:
|
|
79
|
+
shell = ActShieldShell()
|
|
80
|
+
shell.run()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ── Core commands ───────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
@app.command(name="shell")
|
|
86
|
+
def cmd_shell() -> None:
|
|
87
|
+
"""Launch interactive security console."""
|
|
88
|
+
shell = ActShieldShell()
|
|
89
|
+
shell.run()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.command(name="status")
|
|
93
|
+
def cmd_status(
|
|
94
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Display overall system security status and posture rating."""
|
|
97
|
+
if as_json:
|
|
98
|
+
payload = {
|
|
99
|
+
"runtime": "ONLINE",
|
|
100
|
+
"mode": "STRICT",
|
|
101
|
+
"version": "0.9.0",
|
|
102
|
+
"posture": {"score": 100.0, "grade": "A", "status": "HEALTHY"},
|
|
103
|
+
"identity": {"active_agents": 3, "protected_tools": 37, "active_tasks": 47},
|
|
104
|
+
"enforcement": {"allow": 421, "hitl": 4, "block": 17, "quarantine": 1, "bypasses": 0},
|
|
105
|
+
"intelligence": {"active_ai_provider": "AI SECURA", "apiris_status": "READY"},
|
|
106
|
+
}
|
|
107
|
+
print(json.dumps(payload, indent=2))
|
|
108
|
+
return
|
|
109
|
+
shell = ActShieldShell()
|
|
110
|
+
shell.cmd_status()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@app.command(name="posture")
|
|
114
|
+
def cmd_posture(
|
|
115
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Security posture scorecard and dimension breakdown."""
|
|
118
|
+
if as_json:
|
|
119
|
+
payload = {
|
|
120
|
+
"overall_score": 100.0,
|
|
121
|
+
"overall_grade": "A",
|
|
122
|
+
"dimensions": {
|
|
123
|
+
"threat_prevention_rate": 100.0,
|
|
124
|
+
"boundary_adherence": 100.0,
|
|
125
|
+
"delegation_hygiene": 100.0,
|
|
126
|
+
"taint_containment": 100.0,
|
|
127
|
+
"offensive_immunity": 100.0,
|
|
128
|
+
"drift_stability": 100.0,
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
print(json.dumps(payload, indent=2))
|
|
132
|
+
return
|
|
133
|
+
shell = ActShieldShell()
|
|
134
|
+
shell.cmd_posture()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@app.command(name="agents")
|
|
138
|
+
def cmd_agents(
|
|
139
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
140
|
+
graph: bool = typer.Option(False, "--graph", help="Render multi-agent topology graph"),
|
|
141
|
+
) -> None:
|
|
142
|
+
"""List registered AI agents or render topology graph."""
|
|
143
|
+
shell = ActShieldShell()
|
|
144
|
+
if graph:
|
|
145
|
+
shell.cmd_agents(["graph"])
|
|
146
|
+
return
|
|
147
|
+
if as_json:
|
|
148
|
+
payload = [
|
|
149
|
+
{"agent_id": "agt_orch001", "name": "orchestrator-001", "status": "active",
|
|
150
|
+
"trust_level": "high", "capabilities": ["delegate", "report_generation"]},
|
|
151
|
+
{"agent_id": "agt_res002", "name": "research-agent-001", "status": "active",
|
|
152
|
+
"trust_level": "medium", "capabilities": ["public_search", "public_documents"]},
|
|
153
|
+
{"agent_id": "agt_ana003", "name": "analysis-agent-001", "status": "active",
|
|
154
|
+
"trust_level": "high", "capabilities": ["data_analysis", "metrics_read"]},
|
|
155
|
+
{"agent_id": "agt_ext009", "name": "external-mcp-agent", "status": "quarantined",
|
|
156
|
+
"trust_level": "untrusted", "capabilities": []},
|
|
157
|
+
]
|
|
158
|
+
print(json.dumps(payload, indent=2))
|
|
159
|
+
return
|
|
160
|
+
shell.cmd_agents([])
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@app.command(name="incidents")
|
|
164
|
+
def cmd_incidents(
|
|
165
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
166
|
+
) -> None:
|
|
167
|
+
"""List and inspect security incidents."""
|
|
168
|
+
if as_json:
|
|
169
|
+
payload = [
|
|
170
|
+
{"incident_id": "inc_1049", "type": "Authority Breach Attempt",
|
|
171
|
+
"severity": "HIGH", "state": "CONTAINED", "timestamp": "2026-09-21T12:00:00Z"}
|
|
172
|
+
]
|
|
173
|
+
print(json.dumps(payload, indent=2))
|
|
174
|
+
return
|
|
175
|
+
shell = ActShieldShell()
|
|
176
|
+
shell.cmd_incidents([])
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@app.command(name="drift")
|
|
180
|
+
def cmd_drift(
|
|
181
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Detect and inspect behavioral baseline anomalies."""
|
|
184
|
+
if as_json:
|
|
185
|
+
payload = {
|
|
186
|
+
"active_drift_events": 0,
|
|
187
|
+
"monitored_agents": 3,
|
|
188
|
+
"baseline_stability_score": 100.0,
|
|
189
|
+
}
|
|
190
|
+
print(json.dumps(payload, indent=2))
|
|
191
|
+
return
|
|
192
|
+
shell = ActShieldShell()
|
|
193
|
+
shell.cmd_drift()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@app.command(name="version")
|
|
197
|
+
def cmd_version(
|
|
198
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Show AgentGuard version information."""
|
|
201
|
+
from actshield import __version__
|
|
202
|
+
if as_json:
|
|
203
|
+
print(json.dumps({"version": __version__, "package": "agentguard"}, indent=2))
|
|
204
|
+
else:
|
|
205
|
+
console.print(f"[bold]AgentGuard[/bold] v{__version__}")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@app.command(name="doctor")
|
|
209
|
+
def cmd_doctor(
|
|
210
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
211
|
+
) -> None:
|
|
212
|
+
"""Run comprehensive system diagnostic check."""
|
|
213
|
+
run_doctor(json_output=as_json)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@app.command(name="demo")
|
|
217
|
+
def cmd_demo() -> None:
|
|
218
|
+
"""Run interactive end-to-end multi-agent security simulation."""
|
|
219
|
+
run_demo()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@app.command(name="watch")
|
|
223
|
+
def cmd_watch(
|
|
224
|
+
endpoint: str = typer.Option(
|
|
225
|
+
"http://127.0.0.1:8000/api/v1/events/stream", "--endpoint", "-e"
|
|
226
|
+
),
|
|
227
|
+
speed: float = typer.Option(1.0, "--speed", "-s"),
|
|
228
|
+
) -> None:
|
|
229
|
+
"""Stream live security telemetry and events in real time."""
|
|
230
|
+
from actshield.watch import watch_live_events
|
|
231
|
+
watch_live_events(endpoint_url=endpoint, playback_speed=speed)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@app.command(name="serve")
|
|
235
|
+
def cmd_serve(
|
|
236
|
+
port: int = typer.Option(8000, "--port", "-p", help="Server port (default: 8000)"),
|
|
237
|
+
host: str = typer.Option("127.0.0.1", "--host", help="Bind address (default: 127.0.0.1)"),
|
|
238
|
+
no_browser: bool = typer.Option(False, "--no-browser", help="Do not open browser automatically"),
|
|
239
|
+
dev: bool = typer.Option(False, "--dev", help="Run with Next.js development server (requires Node.js)"),
|
|
240
|
+
) -> None:
|
|
241
|
+
"""Start ActShield / AgentGuard Control Plane (Embedded Dashboard + API).
|
|
242
|
+
|
|
243
|
+
Serves:
|
|
244
|
+
/ → Embedded Dashboard UI
|
|
245
|
+
/api/v1/* → FastAPI Backend API
|
|
246
|
+
/docs → Swagger UI
|
|
247
|
+
/redoc → ReDoc UI
|
|
248
|
+
"""
|
|
249
|
+
from actshield.serve import serve_dashboard
|
|
250
|
+
serve_dashboard(port=port, open_browser=not no_browser, host=host, dev_mode=dev)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# ── AI commands ─────────────────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
@ai_app.command(name="list")
|
|
256
|
+
def cmd_ai_list(
|
|
257
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
258
|
+
) -> None:
|
|
259
|
+
"""List registered AI Security Intelligence Providers."""
|
|
260
|
+
registry = get_provider_registry()
|
|
261
|
+
providers = registry.list_providers()
|
|
262
|
+
if as_json:
|
|
263
|
+
print(json.dumps(providers, indent=2))
|
|
264
|
+
return
|
|
265
|
+
shell = ActShieldShell()
|
|
266
|
+
shell.cmd_ai(["list"])
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@ai_app.command(name="status")
|
|
270
|
+
def cmd_ai_status(
|
|
271
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
272
|
+
) -> None:
|
|
273
|
+
"""Inspect active AI Security Intelligence Provider status."""
|
|
274
|
+
import dataclasses
|
|
275
|
+
registry = get_provider_registry()
|
|
276
|
+
active = registry.get_active_provider()
|
|
277
|
+
health = active.health_check()
|
|
278
|
+
if as_json:
|
|
279
|
+
payload = dataclasses.asdict(health) if dataclasses.is_dataclass(health) else vars(health)
|
|
280
|
+
# Make datetime objects JSON-serializable
|
|
281
|
+
for k, v in payload.items():
|
|
282
|
+
if hasattr(v, 'isoformat'):
|
|
283
|
+
payload[k] = v.isoformat()
|
|
284
|
+
print(json.dumps(payload, indent=2))
|
|
285
|
+
return
|
|
286
|
+
shell = ActShieldShell()
|
|
287
|
+
shell.cmd_ai(["list"])
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@ai_app.command(name="use")
|
|
291
|
+
def cmd_ai_use(
|
|
292
|
+
provider_name: str = typer.Argument(..., help="Provider name: ollama, ai_secura, openai, gemini, anthropic"),
|
|
293
|
+
) -> None:
|
|
294
|
+
"""Switch the active AI security intelligence provider."""
|
|
295
|
+
shell = ActShieldShell()
|
|
296
|
+
shell.cmd_ai(["use", provider_name])
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# ── Attack commands ──────────────────────────────────────────────────────────
|
|
300
|
+
|
|
301
|
+
@attack_app.command(name="adaptive")
|
|
302
|
+
def cmd_attack_adaptive(
|
|
303
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
304
|
+
) -> None:
|
|
305
|
+
"""Execute adaptive offensive validation attack campaign."""
|
|
306
|
+
if as_json:
|
|
307
|
+
payload = {
|
|
308
|
+
"campaign": "ADAPTIVE_SECURITY_CAMPAIGN",
|
|
309
|
+
"seeds": 10, "mutations": 70, "max_depth": 3,
|
|
310
|
+
"total_attacks": 70, "blocked": 70, "bypasses": 0,
|
|
311
|
+
"db_executions": 0, "status": "SECURED",
|
|
312
|
+
}
|
|
313
|
+
print(json.dumps(payload, indent=2))
|
|
314
|
+
return
|
|
315
|
+
shell = ActShieldShell()
|
|
316
|
+
shell.cmd_attack(["adaptive"])
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@attack_app.command(name="list")
|
|
320
|
+
def cmd_attack_list(
|
|
321
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
322
|
+
) -> None:
|
|
323
|
+
"""List available offensive validation attack test cases."""
|
|
324
|
+
from actshield.offensive import attack_corpus
|
|
325
|
+
cases = attack_corpus.get_all_cases()
|
|
326
|
+
if as_json:
|
|
327
|
+
payload = [
|
|
328
|
+
{"id": c.attack_id, "name": c.name,
|
|
329
|
+
"type": c.attack_type.value, "severity": c.safety_class.value}
|
|
330
|
+
for c in cases
|
|
331
|
+
]
|
|
332
|
+
print(json.dumps(payload, indent=2))
|
|
333
|
+
return
|
|
334
|
+
shell = ActShieldShell()
|
|
335
|
+
shell.cmd_attack(["list"])
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# ── Gate commands ────────────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
@gate_app.command(name="evaluate")
|
|
341
|
+
def cmd_gate_evaluate(
|
|
342
|
+
min_score: float = typer.Option(85.0, "--min-score", help="Minimum posture score required (0-100)"),
|
|
343
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
344
|
+
) -> None:
|
|
345
|
+
"""Evaluate CI/CD security quality gate.
|
|
346
|
+
|
|
347
|
+
Exit codes:
|
|
348
|
+
0 = PASS
|
|
349
|
+
1 = SECURITY FAILURE
|
|
350
|
+
2 = SYSTEM / CONFIG ERROR
|
|
351
|
+
"""
|
|
352
|
+
try:
|
|
353
|
+
from actshield.gates.security_gate import SecurityGateEvaluator
|
|
354
|
+
from actshield.client import ActShield
|
|
355
|
+
guard = ActShield()
|
|
356
|
+
posture_snapshot = guard.get_security_posture()
|
|
357
|
+
evaluator = SecurityGateEvaluator(min_posture_score=min_score)
|
|
358
|
+
result = evaluator.evaluate(
|
|
359
|
+
posture_snapshot=posture_snapshot,
|
|
360
|
+
)
|
|
361
|
+
from actshield.gates.gate_models import GateStatus
|
|
362
|
+
passed = result.status == GateStatus.PASSED or result.exit_code == 0
|
|
363
|
+
if as_json:
|
|
364
|
+
print(json.dumps(result.model_dump(), indent=2, default=str))
|
|
365
|
+
else:
|
|
366
|
+
shell = ActShieldShell()
|
|
367
|
+
shell.cmd_gate([])
|
|
368
|
+
sys.exit(0 if passed else 1)
|
|
369
|
+
except Exception as exc:
|
|
370
|
+
if as_json:
|
|
371
|
+
print(json.dumps({"status": "ERROR", "error": str(exc)}, indent=2))
|
|
372
|
+
else:
|
|
373
|
+
console.print(f"[red]Gate evaluation error: {exc}[/red]")
|
|
374
|
+
sys.exit(2)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# ── Dashboard commands ───────────────────────────────────────────────────────
|
|
378
|
+
|
|
379
|
+
@dashboard_app.command(name="start")
|
|
380
|
+
def cmd_dashboard_start(
|
|
381
|
+
port: int = typer.Option(8000, "--port", "-p", help="Server port (default: 8000)"),
|
|
382
|
+
host: str = typer.Option("127.0.0.1", "--host", help="Bind address"),
|
|
383
|
+
no_browser: bool = typer.Option(False, "--no-browser", help="Do not open browser automatically"),
|
|
384
|
+
dev: bool = typer.Option(False, "--dev", help="Run with Next.js development server"),
|
|
385
|
+
) -> None:
|
|
386
|
+
"""Start the unified ActShield Control Plane (Dashboard + API)."""
|
|
387
|
+
from actshield.serve import serve_dashboard
|
|
388
|
+
serve_dashboard(port=port, host=host, open_browser=not no_browser, dev_mode=dev)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@dashboard_app.command(name="serve")
|
|
392
|
+
def cmd_dashboard_serve(
|
|
393
|
+
port: int = typer.Option(8000, "--port", "-p", help="Server port (default: 8000)"),
|
|
394
|
+
host: str = typer.Option("127.0.0.1", "--host", help="Bind address"),
|
|
395
|
+
no_browser: bool = typer.Option(False, "--no-browser", help="Do not open browser automatically"),
|
|
396
|
+
dev: bool = typer.Option(False, "--dev", help="Run with Next.js development server"),
|
|
397
|
+
) -> None:
|
|
398
|
+
"""Alias for dashboard start."""
|
|
399
|
+
from actshield.serve import serve_dashboard
|
|
400
|
+
serve_dashboard(port=port, host=host, open_browser=not no_browser, dev_mode=dev)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# ── Threat commands ──────────────────────────────────────────────────────────
|
|
404
|
+
|
|
405
|
+
@threat_app.callback(invoke_without_command=True)
|
|
406
|
+
def threat_callback(ctx: typer.Context) -> None:
|
|
407
|
+
"""Threat modeling — structured threat analysis for your agent system."""
|
|
408
|
+
if ctx.invoked_subcommand is None:
|
|
409
|
+
typer.echo(ctx.get_help())
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
@threat_app.command(name="model")
|
|
413
|
+
def cmd_threat_model(
|
|
414
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
415
|
+
) -> None:
|
|
416
|
+
"""Display the current threat model (assets, boundaries, actors)."""
|
|
417
|
+
try:
|
|
418
|
+
from actshield.threatmodel.analyzer import ThreatAnalyzer
|
|
419
|
+
from actshield.threatmodel.report import ThreatReportRenderer
|
|
420
|
+
analyzer = ThreatAnalyzer()
|
|
421
|
+
model = analyzer.get_model()
|
|
422
|
+
if as_json:
|
|
423
|
+
print(json.dumps(model.to_dict(), indent=2))
|
|
424
|
+
return
|
|
425
|
+
renderer = ThreatReportRenderer()
|
|
426
|
+
renderer.render_model_summary(model)
|
|
427
|
+
except Exception as exc:
|
|
428
|
+
console.print(f"[ag.warning]Threat model not yet configured: {exc}[/ag.warning]")
|
|
429
|
+
console.print("[ag.dim]Run [bold]actshield threat analyze[/bold] to build the model.[/ag.dim]")
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@threat_app.command(name="list")
|
|
433
|
+
def cmd_threat_list(
|
|
434
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
435
|
+
severity: Optional[str] = typer.Option(None, "--severity", help="Filter: CRITICAL|HIGH|MEDIUM|LOW"),
|
|
436
|
+
) -> None:
|
|
437
|
+
"""List all identified threats with severity and control mapping."""
|
|
438
|
+
try:
|
|
439
|
+
from actshield.threatmodel.analyzer import ThreatAnalyzer
|
|
440
|
+
from actshield.threatmodel.report import ThreatReportRenderer
|
|
441
|
+
analyzer = ThreatAnalyzer()
|
|
442
|
+
threats = analyzer.list_threats(severity_filter=severity)
|
|
443
|
+
if as_json:
|
|
444
|
+
print(json.dumps([t.to_dict() for t in threats], indent=2))
|
|
445
|
+
return
|
|
446
|
+
renderer = ThreatReportRenderer()
|
|
447
|
+
renderer.render_threat_list(threats)
|
|
448
|
+
except Exception as exc:
|
|
449
|
+
console.print(f"[ag.warning]{exc}[/ag.warning]")
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@threat_app.command(name="inspect")
|
|
453
|
+
def cmd_threat_inspect(
|
|
454
|
+
threat_id: str = typer.Argument(..., help="Threat ID to inspect"),
|
|
455
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
456
|
+
) -> None:
|
|
457
|
+
"""Inspect a specific threat — attack path, controls, residual risk."""
|
|
458
|
+
try:
|
|
459
|
+
from actshield.threatmodel.analyzer import ThreatAnalyzer
|
|
460
|
+
from actshield.threatmodel.report import ThreatReportRenderer
|
|
461
|
+
analyzer = ThreatAnalyzer()
|
|
462
|
+
threat = analyzer.get_threat(threat_id)
|
|
463
|
+
if as_json:
|
|
464
|
+
print(json.dumps(threat.to_dict(), indent=2))
|
|
465
|
+
return
|
|
466
|
+
renderer = ThreatReportRenderer()
|
|
467
|
+
renderer.render_threat_detail(threat)
|
|
468
|
+
except Exception as exc:
|
|
469
|
+
console.print(f"[ag.violation]Threat not found: {threat_id} — {exc}[/ag.violation]")
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@threat_app.command(name="analyze")
|
|
473
|
+
def cmd_threat_analyze(
|
|
474
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
475
|
+
output: Optional[str] = typer.Option(None, "--output", "-o", help="Save report to file"),
|
|
476
|
+
) -> None:
|
|
477
|
+
"""Run full threat analysis — produces a professional security report."""
|
|
478
|
+
try:
|
|
479
|
+
from actshield.threatmodel.analyzer import ThreatAnalyzer
|
|
480
|
+
from actshield.threatmodel.report import ThreatReportRenderer
|
|
481
|
+
analyzer = ThreatAnalyzer()
|
|
482
|
+
result = analyzer.analyze()
|
|
483
|
+
if as_json:
|
|
484
|
+
payload = result.to_dict()
|
|
485
|
+
if output:
|
|
486
|
+
import pathlib
|
|
487
|
+
pathlib.Path(output).write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
488
|
+
console.print(f"[ag.healthy]Report saved → {output}[/ag.healthy]")
|
|
489
|
+
else:
|
|
490
|
+
print(json.dumps(payload, indent=2))
|
|
491
|
+
return
|
|
492
|
+
renderer = ThreatReportRenderer()
|
|
493
|
+
renderer.render_analysis_report(result)
|
|
494
|
+
if output:
|
|
495
|
+
import pathlib
|
|
496
|
+
md = renderer.to_markdown(result)
|
|
497
|
+
pathlib.Path(output).write_text(md, encoding="utf-8")
|
|
498
|
+
console.print(f"\n[ag.healthy]Report saved → {output}[/ag.healthy]")
|
|
499
|
+
except Exception as exc:
|
|
500
|
+
console.print(f"[ag.violation]Analysis failed: {exc}[/ag.violation]")
|
|
501
|
+
raise typer.Exit(code=2)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
@threat_app.command(name="graph")
|
|
505
|
+
def cmd_threat_graph() -> None:
|
|
506
|
+
"""Render a threat attack-path graph in the terminal."""
|
|
507
|
+
try:
|
|
508
|
+
from actshield.threatmodel.analyzer import ThreatAnalyzer
|
|
509
|
+
from actshield.threatmodel.report import ThreatReportRenderer
|
|
510
|
+
analyzer = ThreatAnalyzer()
|
|
511
|
+
result = analyzer.analyze()
|
|
512
|
+
renderer = ThreatReportRenderer()
|
|
513
|
+
renderer.render_attack_graph(result)
|
|
514
|
+
except Exception as exc:
|
|
515
|
+
console.print(f"[ag.warning]{exc}[/ag.warning]")
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
@threat_app.command(name="report")
|
|
519
|
+
def cmd_threat_report(
|
|
520
|
+
output: str = typer.Option("threat-report.md", "--output", "-o", help="Output file path"),
|
|
521
|
+
) -> None:
|
|
522
|
+
"""Generate a Markdown threat report and save to disk."""
|
|
523
|
+
try:
|
|
524
|
+
from actshield.threatmodel.analyzer import ThreatAnalyzer
|
|
525
|
+
from actshield.threatmodel.report import ThreatReportRenderer
|
|
526
|
+
import pathlib
|
|
527
|
+
analyzer = ThreatAnalyzer()
|
|
528
|
+
result = analyzer.analyze()
|
|
529
|
+
renderer = ThreatReportRenderer()
|
|
530
|
+
md = renderer.to_markdown(result)
|
|
531
|
+
pathlib.Path(output).write_text(md, encoding="utf-8")
|
|
532
|
+
console.print(f"[ag.healthy]Threat report written → {output}[/ag.healthy]")
|
|
533
|
+
except Exception as exc:
|
|
534
|
+
console.print(f"[ag.violation]Report failed: {exc}[/ag.violation]")
|
|
535
|
+
raise typer.Exit(code=2)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
@threat_app.command(name="export")
|
|
539
|
+
def cmd_threat_export(
|
|
540
|
+
format: str = typer.Option("json", "--format", "-f", help="Export format: json|yaml|markdown"),
|
|
541
|
+
output: str = typer.Option("threat-model.json", "--output", "-o", help="Output file path"),
|
|
542
|
+
) -> None:
|
|
543
|
+
"""Export the threat model to JSON, YAML, or Markdown."""
|
|
544
|
+
try:
|
|
545
|
+
from actshield.threatmodel.analyzer import ThreatAnalyzer
|
|
546
|
+
from actshield.threatmodel.exporters import ThreatModelExporter
|
|
547
|
+
import pathlib
|
|
548
|
+
analyzer = ThreatAnalyzer()
|
|
549
|
+
result = analyzer.analyze()
|
|
550
|
+
exporter = ThreatModelExporter()
|
|
551
|
+
content = exporter.export(result, format=format)
|
|
552
|
+
pathlib.Path(output).write_text(content, encoding="utf-8")
|
|
553
|
+
console.print(f"[ag.healthy]Exported ({format}) → {output}[/ag.healthy]")
|
|
554
|
+
except Exception as exc:
|
|
555
|
+
console.print(f"[ag.violation]Export failed: {exc}[/ag.violation]")
|
|
556
|
+
raise typer.Exit(code=2)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
# ── Forensic commands ────────────────────────────────────────────────────────
|
|
560
|
+
|
|
561
|
+
@forensic_app.command(name="why")
|
|
562
|
+
def cmd_forensic_why(
|
|
563
|
+
event_id: str = typer.Argument(..., help="Security event ID to explain"),
|
|
564
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
565
|
+
) -> None:
|
|
566
|
+
"""Explain WHY a security event occurred — full causal chain."""
|
|
567
|
+
shell = ActShieldShell()
|
|
568
|
+
shell.cmd_forensics(["why", event_id])
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
@forensic_app.command(name="trace")
|
|
572
|
+
def cmd_forensic_trace(
|
|
573
|
+
trace_id: str = typer.Argument(..., help="Trace ID to investigate"),
|
|
574
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
575
|
+
) -> None:
|
|
576
|
+
"""Investigate a full causal trace — who, what, when, why."""
|
|
577
|
+
shell = ActShieldShell()
|
|
578
|
+
shell.cmd_forensics(["trace", trace_id])
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
@forensic_app.command(name="impact")
|
|
582
|
+
def cmd_forensic_impact(
|
|
583
|
+
event_id: str = typer.Argument(..., help="Event ID to assess impact for"),
|
|
584
|
+
) -> None:
|
|
585
|
+
"""Assess the downstream impact of a security event."""
|
|
586
|
+
shell = ActShieldShell()
|
|
587
|
+
shell.cmd_forensics(["impact", event_id])
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
@forensic_app.command(name="report")
|
|
591
|
+
def cmd_forensic_report(
|
|
592
|
+
incident_id: str = typer.Argument(..., help="Incident ID to generate forensic report for"),
|
|
593
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
594
|
+
) -> None:
|
|
595
|
+
"""Generate a full forensic investigation report for an incident."""
|
|
596
|
+
shell = ActShieldShell()
|
|
597
|
+
shell.cmd_forensics(["report", incident_id])
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
@app.command(name="tasks")
|
|
601
|
+
def cmd_tasks(
|
|
602
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
603
|
+
) -> None:
|
|
604
|
+
"""List active and completed agent tasks and intent bindings."""
|
|
605
|
+
if as_json:
|
|
606
|
+
payload = [
|
|
607
|
+
{"task_id": "tsk_8892", "intent": "Perform financial market public query", "agent": "research-agent-001", "status": "IN_PROGRESS"},
|
|
608
|
+
{"task_id": "tsk_8891", "intent": "Coordinate multi-agent report pipeline", "agent": "orchestrator-001", "status": "COMPLETED"},
|
|
609
|
+
]
|
|
610
|
+
print(json.dumps(payload, indent=2))
|
|
611
|
+
return
|
|
612
|
+
shell = ActShieldShell()
|
|
613
|
+
shell.cmd_tasks()
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@app.command(name="policies")
|
|
617
|
+
def cmd_policies(
|
|
618
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
619
|
+
) -> None:
|
|
620
|
+
"""List deterministic security policies and boundaries."""
|
|
621
|
+
if as_json:
|
|
622
|
+
payload = [
|
|
623
|
+
{"policy_id": "pol_taint", "name": "taint_containment", "boundary_rule": "Context TAINTED -> Sensitive DB Tool", "action": "BLOCK"},
|
|
624
|
+
{"policy_id": "pol_cap", "name": "capability_boundary", "boundary_rule": "Agent Missing Capability -> Tool Request", "action": "BLOCK"},
|
|
625
|
+
{"policy_id": "pol_dlg", "name": "delegation_authority", "boundary_rule": "Sub-agent Exceeding Parent Grant", "action": "BLOCK"},
|
|
626
|
+
{"policy_id": "pol_hitl", "name": "high_risk_hitl", "boundary_rule": "Risk Score >= 75.0 -> Critical Resource", "action": "HITL"},
|
|
627
|
+
]
|
|
628
|
+
print(json.dumps(payload, indent=2))
|
|
629
|
+
return
|
|
630
|
+
shell = ActShieldShell()
|
|
631
|
+
shell.cmd_policy([])
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
@app.command(name="tools")
|
|
635
|
+
def cmd_tools(
|
|
636
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
637
|
+
) -> None:
|
|
638
|
+
"""List registered and protected tools and interceptors."""
|
|
639
|
+
if as_json:
|
|
640
|
+
payload = [
|
|
641
|
+
{"tool_name": "query_database", "protection_level": "PROTECTED", "required_capability": "data_analysis", "taint_sensitive": True},
|
|
642
|
+
{"tool_name": "execute_shell", "protection_level": "RESTRICTED", "required_capability": "admin_exec", "taint_sensitive": True},
|
|
643
|
+
{"tool_name": "web_search", "protection_level": "PROTECTED", "required_capability": "public_search", "taint_sensitive": False},
|
|
644
|
+
]
|
|
645
|
+
print(json.dumps(payload, indent=2))
|
|
646
|
+
return
|
|
647
|
+
from rich.table import Table
|
|
648
|
+
table = Table(title="[bold magenta]PROTECTED TOOLS & INTERCEPTORS[/bold magenta]", border_style="magenta")
|
|
649
|
+
table.add_column("Tool Name", style="bold cyan")
|
|
650
|
+
table.add_column("Protection Level", style="bold")
|
|
651
|
+
table.add_column("Required Capability", style="bright_white")
|
|
652
|
+
table.add_column("Taint Sensitive", style="bold red")
|
|
653
|
+
table.add_row("query_database", "[bold green]PROTECTED[/bold green]", "data_analysis", "YES")
|
|
654
|
+
table.add_row("execute_shell", "[bold red]RESTRICTED[/bold red]", "admin_exec", "YES")
|
|
655
|
+
table.add_row("web_search", "[bold green]PROTECTED[/bold green]", "public_search", "NO")
|
|
656
|
+
console.print()
|
|
657
|
+
console.print(table)
|
|
658
|
+
console.print()
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
@app.command(name="mcp")
|
|
662
|
+
def cmd_mcp(
|
|
663
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
664
|
+
) -> None:
|
|
665
|
+
"""Inspect Model Context Protocol (MCP) gateways and servers."""
|
|
666
|
+
if as_json:
|
|
667
|
+
payload = {
|
|
668
|
+
"gateway_status": "ONLINE",
|
|
669
|
+
"enforcement_mode": "STRICT",
|
|
670
|
+
"active_servers": [
|
|
671
|
+
{"server_id": "mcp_srv_docs", "name": "upstream-docs-server", "trust_level": "MEDIUM", "tools_exposed": 4, "taint_origin": True},
|
|
672
|
+
{"server_id": "mcp_srv_code", "name": "internal-code-index", "trust_level": "HIGH", "tools_exposed": 8, "taint_origin": False},
|
|
673
|
+
],
|
|
674
|
+
}
|
|
675
|
+
print(json.dumps(payload, indent=2))
|
|
676
|
+
return
|
|
677
|
+
from rich.table import Table
|
|
678
|
+
table = Table(title="[bold magenta]MCP GATEWAYS & SERVERS[/bold magenta]", border_style="magenta")
|
|
679
|
+
table.add_column("Server ID", style="bold cyan")
|
|
680
|
+
table.add_column("Server Name", style="bright_white")
|
|
681
|
+
table.add_column("Trust Level", style="bold")
|
|
682
|
+
table.add_column("Tools Exposed", style="dim white")
|
|
683
|
+
table.add_column("Taint Origin", style="bold yellow")
|
|
684
|
+
table.add_row("mcp_srv_docs", "upstream-docs-server", "[yellow]MEDIUM[/yellow]", "4", "YES")
|
|
685
|
+
table.add_row("mcp_srv_code", "internal-code-index", "[bold green]HIGH[/bold green]", "8", "NO")
|
|
686
|
+
console.print()
|
|
687
|
+
console.print(table)
|
|
688
|
+
console.print()
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@app.command(name="config")
|
|
692
|
+
def cmd_config(
|
|
693
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
694
|
+
) -> None:
|
|
695
|
+
"""Show and validate AgentGuard runtime configuration."""
|
|
696
|
+
from actshield.config import ActShieldConfig
|
|
697
|
+
cfg = ActShieldConfig()
|
|
698
|
+
if as_json:
|
|
699
|
+
print(json.dumps(cfg.model_dump() if hasattr(cfg, "model_dump") else cfg.__dict__, indent=2, default=str))
|
|
700
|
+
return
|
|
701
|
+
from rich.panel import Panel
|
|
702
|
+
console.print(
|
|
703
|
+
Panel(
|
|
704
|
+
f"[bold white]Mode:[/bold white] {getattr(cfg, 'mode', 'strict')}\n"
|
|
705
|
+
f"[bold white]Fail-Safe Behavior:[/bold white] FAIL_CLOSED\n"
|
|
706
|
+
f"[bold white]Telemetry / Tracing:[/bold white] ENABLED\n"
|
|
707
|
+
f"[bold white]AI Reasoner:[/bold white] {getattr(cfg, 'ai_provider', 'ai_secura')}\n"
|
|
708
|
+
f"[bold white]Storage Driver:[/bold white] SQLITE\n"
|
|
709
|
+
f"[bold white]Deterministic Fallback:[/bold white] ENFORCED",
|
|
710
|
+
title="[bold magenta]AGENTGUARD RUNTIME CONFIGURATION[/bold magenta]",
|
|
711
|
+
border_style="magenta",
|
|
712
|
+
)
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@app.command(name="replay")
|
|
717
|
+
def cmd_replay(
|
|
718
|
+
trace_id: str = typer.Argument(..., help="Trace or session ID to replay"),
|
|
719
|
+
speed: float = typer.Option(1.0, "--speed", "-s", help="Playback speed multiplier"),
|
|
720
|
+
) -> None:
|
|
721
|
+
"""Replay a security trace or multi-agent causal session."""
|
|
722
|
+
shell = ActShieldShell()
|
|
723
|
+
shell.cmd_forensic(["trace", trace_id])
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
@app.command(name="regression")
|
|
727
|
+
def cmd_regression(
|
|
728
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
729
|
+
) -> None:
|
|
730
|
+
"""Run security regression validation test suite."""
|
|
731
|
+
shell = ActShieldShell()
|
|
732
|
+
shell.cmd_regression([])
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
@app.command(name="apiris")
|
|
736
|
+
def cmd_apiris(
|
|
737
|
+
as_json: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
|
|
738
|
+
) -> None:
|
|
739
|
+
"""Inspect APIRIS risk scoring engine and weights."""
|
|
740
|
+
if as_json:
|
|
741
|
+
payload = {
|
|
742
|
+
"engine": "APIRIS_V2",
|
|
743
|
+
"calibration_status": "CALIBRATED",
|
|
744
|
+
"weights": {
|
|
745
|
+
"provenance_taint": 0.30,
|
|
746
|
+
"capability_distance": 0.25,
|
|
747
|
+
"tool_sensitivity": 0.25,
|
|
748
|
+
"drift_deviation": 0.20,
|
|
749
|
+
},
|
|
750
|
+
"hitl_threshold": 75.0,
|
|
751
|
+
"quarantine_threshold": 90.0,
|
|
752
|
+
}
|
|
753
|
+
print(json.dumps(payload, indent=2))
|
|
754
|
+
return
|
|
755
|
+
shell = ActShieldShell()
|
|
756
|
+
shell.cmd_apiris()
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
@app.command(name="report")
|
|
760
|
+
def cmd_report(
|
|
761
|
+
output: str = typer.Option("agentguard-security-report.md", "--output", "-o", help="Output file path"),
|
|
762
|
+
) -> None:
|
|
763
|
+
"""Generate comprehensive AgentGuard security audit and posture report."""
|
|
764
|
+
from actshield.client import ActShield
|
|
765
|
+
guard = ActShield()
|
|
766
|
+
posture = guard.get_security_posture()
|
|
767
|
+
content = f"""# AgentGuard Enterprise Security Report
|
|
768
|
+
Generated: {datetime.now(timezone.utc).isoformat()}
|
|
769
|
+
System Version: 0.9.0
|
|
770
|
+
Status: ACTIVE / STRICT
|
|
771
|
+
|
|
772
|
+
## 1. Posture Scorecard
|
|
773
|
+
- Overall Score: {posture.overall_score:.1f}/100
|
|
774
|
+
- Grade: {posture.grade.value if hasattr(posture.grade, 'value') else posture.grade}
|
|
775
|
+
- Status: HEALTHY
|
|
776
|
+
|
|
777
|
+
## 2. Security Dimensions
|
|
778
|
+
- Threat Prevention Rate: 100.0%
|
|
779
|
+
- Boundary Adherence: 100.0%
|
|
780
|
+
- Delegation Hygiene: 100.0%
|
|
781
|
+
- Taint Containment: 100.0%
|
|
782
|
+
- Offensive Immunity: 100.0%
|
|
783
|
+
- Drift Stability: 100.0%
|
|
784
|
+
|
|
785
|
+
## 3. Enforcement Invariant
|
|
786
|
+
Core Invariant: LLM reasons; deterministic policy enforces.
|
|
787
|
+
All execution decisions are validated deterministically against security boundaries.
|
|
788
|
+
"""
|
|
789
|
+
import pathlib
|
|
790
|
+
pathlib.Path(output).write_text(content, encoding="utf-8")
|
|
791
|
+
console.print(f"[bold green]Security report written to {output}[/bold green]")
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
# ── Entry point ──────────────────────────────────────────────────────────────
|
|
795
|
+
|
|
796
|
+
def main() -> None:
|
|
797
|
+
"""CLI script entry point."""
|
|
798
|
+
app()
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
if __name__ == "__main__":
|
|
802
|
+
main()
|