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
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Causal trace manager, event repository, and causal graph reconstructor."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from actshield.tracing.events import EventType, SecurityEvent
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from actshield.client import ActShield
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CausalNode(BaseModel):
|
|
15
|
+
"""A discrete entity in the multi-agent causal graph."""
|
|
16
|
+
node_id: str
|
|
17
|
+
node_type: str # USER, TASK, AGENT, DELEGATION, CONTEXT, TOOL, RESOURCE, DECISION, EVENT
|
|
18
|
+
label: str
|
|
19
|
+
details: Dict[str, Any] = Field(default_factory=dict)
|
|
20
|
+
timestamp: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CausalEdge(BaseModel):
|
|
24
|
+
"""A directed causal dependency or data-flow relationship."""
|
|
25
|
+
source_id: str
|
|
26
|
+
target_id: str
|
|
27
|
+
relation: str # INITIATED, DELEGATED_TO, INGESTED_OR_PROPAGATED, INVOKED, TARGETS, GOVERNED_BY
|
|
28
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CausalGraph(BaseModel):
|
|
32
|
+
"""Reconstructed end-to-end causal chain and provenance graph for a trace."""
|
|
33
|
+
trace_id: str
|
|
34
|
+
nodes: List[CausalNode] = Field(default_factory=list)
|
|
35
|
+
edges: List[CausalEdge] = Field(default_factory=list)
|
|
36
|
+
|
|
37
|
+
def to_json(self, indent: int = 2) -> str:
|
|
38
|
+
"""Export graph as machine-readable JSON."""
|
|
39
|
+
return self.model_dump_json(indent=indent)
|
|
40
|
+
|
|
41
|
+
def get_influencing_contexts(self, tool_name_or_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
42
|
+
"""Query and return all context nodes that causally influenced tool requests in this trace."""
|
|
43
|
+
context_nodes = [n for n in self.nodes if n.node_type == "CONTEXT"]
|
|
44
|
+
results: List[Dict[str, Any]] = []
|
|
45
|
+
for cn in context_nodes:
|
|
46
|
+
cid = cn.details.get("context_id") or (cn.node_id[4:] if cn.node_id.startswith("ctx_") else cn.node_id)
|
|
47
|
+
results.append({
|
|
48
|
+
"context_id": cid,
|
|
49
|
+
"label": cn.label,
|
|
50
|
+
"details": cn.details,
|
|
51
|
+
"timestamp": cn.timestamp,
|
|
52
|
+
})
|
|
53
|
+
return results
|
|
54
|
+
|
|
55
|
+
def get_propagation_chain(self) -> List[str]:
|
|
56
|
+
"""Extract chronological agent propagation chain from graph nodes."""
|
|
57
|
+
agent_nodes = [n for n in self.nodes if n.node_type == "AGENT"]
|
|
58
|
+
seen = set()
|
|
59
|
+
chain = []
|
|
60
|
+
for n in agent_nodes:
|
|
61
|
+
# Extract clean agent name from label e.g. "Agent: PlannerAgent"
|
|
62
|
+
name = n.label.split("(")[0].replace("Agent:", "").strip()
|
|
63
|
+
if name not in seen:
|
|
64
|
+
seen.add(name)
|
|
65
|
+
chain.append(name)
|
|
66
|
+
return chain
|
|
67
|
+
|
|
68
|
+
def render_tree(self) -> str:
|
|
69
|
+
"""Render a formatted human-readable causal execution tree using ASCII characters for universal terminal support."""
|
|
70
|
+
lines: List[str] = [
|
|
71
|
+
f"=== ActShield CAUSAL TRACE: {self.trace_id} ===",
|
|
72
|
+
""
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
node_map = {n.node_id: n for n in self.nodes}
|
|
76
|
+
# Find root nodes (nodes with no incoming edges)
|
|
77
|
+
targets = {e.target_id for e in self.edges}
|
|
78
|
+
roots = [n for n in self.nodes if n.node_id not in targets]
|
|
79
|
+
|
|
80
|
+
if not roots and self.nodes:
|
|
81
|
+
roots = [self.nodes[0]]
|
|
82
|
+
|
|
83
|
+
# Build adjacency list: source -> list of (target_id, relation)
|
|
84
|
+
adj: Dict[str, List[tuple[str, str]]] = {}
|
|
85
|
+
for e in self.edges:
|
|
86
|
+
adj.setdefault(e.source_id, []).append((e.target_id, e.relation))
|
|
87
|
+
|
|
88
|
+
visited = set()
|
|
89
|
+
|
|
90
|
+
def _dfs(current_id: str, prefix: str = "", is_last: bool = True) -> None:
|
|
91
|
+
if current_id in visited:
|
|
92
|
+
return
|
|
93
|
+
visited.add(current_id)
|
|
94
|
+
node = node_map.get(current_id)
|
|
95
|
+
if not node:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
connector = "\\-- " if is_last else "+-- "
|
|
99
|
+
type_tag = f"[{node.node_type}]"
|
|
100
|
+
lines.append(f"{prefix}{connector}{type_tag} {node.label}")
|
|
101
|
+
|
|
102
|
+
child_prefix = prefix + (" " if is_last else "| ")
|
|
103
|
+
children = adj.get(current_id, [])
|
|
104
|
+
for i, (child_id, rel) in enumerate(children):
|
|
105
|
+
child_is_last = (i == len(children) - 1)
|
|
106
|
+
_dfs(child_id, child_prefix, child_is_last)
|
|
107
|
+
|
|
108
|
+
for i, root in enumerate(roots):
|
|
109
|
+
_dfs(root.node_id, "", is_last=(i == len(roots) - 1))
|
|
110
|
+
|
|
111
|
+
# Add unvisited nodes if any disconnected events exist
|
|
112
|
+
unvisited = [n for n in self.nodes if n.node_id not in visited]
|
|
113
|
+
if unvisited:
|
|
114
|
+
lines.append("")
|
|
115
|
+
lines.append("--- Additional Correlated Events ---")
|
|
116
|
+
for node in unvisited:
|
|
117
|
+
lines.append(f" * [{node.node_type}] {node.label}")
|
|
118
|
+
|
|
119
|
+
return "\n".join(lines)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class TraceManager:
|
|
123
|
+
"""Thread-safe and async-safe in-memory security event repository."""
|
|
124
|
+
|
|
125
|
+
def __init__(self) -> None:
|
|
126
|
+
self._events: List[SecurityEvent] = []
|
|
127
|
+
|
|
128
|
+
def record_event(self, event: SecurityEvent) -> None:
|
|
129
|
+
"""Store an emitted security event."""
|
|
130
|
+
self._events.append(event)
|
|
131
|
+
|
|
132
|
+
def get_events(self, trace_id: Optional[str] = None) -> List[SecurityEvent]:
|
|
133
|
+
"""Query events matching optional trace ID."""
|
|
134
|
+
if trace_id is None:
|
|
135
|
+
return list(self._events)
|
|
136
|
+
return [e for e in self._events if e.trace_id == trace_id]
|
|
137
|
+
|
|
138
|
+
def get_propagation_chain_for_context(self, context_id: str) -> List[str]:
|
|
139
|
+
"""Return the sequence of agent IDs that propagated a specific context."""
|
|
140
|
+
events = [e for e in self._events if e.context_id == context_id or e.payload.get("parent_context_id") == context_id]
|
|
141
|
+
agents = []
|
|
142
|
+
for e in events:
|
|
143
|
+
if e.agent_id and e.agent_id not in agents:
|
|
144
|
+
agents.append(e.agent_id)
|
|
145
|
+
return agents
|
|
146
|
+
|
|
147
|
+
def reconstruct_causal_graph(self, trace_id: str, guard: Optional["ActShield"] = None) -> CausalGraph:
|
|
148
|
+
"""Reconstruct the complete causal chain from stored events and registries."""
|
|
149
|
+
events = self.get_events(trace_id)
|
|
150
|
+
nodes: List[CausalNode] = []
|
|
151
|
+
edges: List[CausalEdge] = []
|
|
152
|
+
node_ids = set()
|
|
153
|
+
|
|
154
|
+
def add_node(n: CausalNode) -> None:
|
|
155
|
+
if n.node_id not in node_ids:
|
|
156
|
+
node_ids.add(n.node_id)
|
|
157
|
+
nodes.append(n)
|
|
158
|
+
|
|
159
|
+
def add_edge(src: str, dst: str, relation: str) -> None:
|
|
160
|
+
edges.append(CausalEdge(source_id=src, target_id=dst, relation=relation))
|
|
161
|
+
|
|
162
|
+
# Process Task Started
|
|
163
|
+
user_node_id = None
|
|
164
|
+
task_node_id = None
|
|
165
|
+
last_entity_id = None
|
|
166
|
+
|
|
167
|
+
for evt in events:
|
|
168
|
+
evt_time_str = evt.timestamp.isoformat() if hasattr(evt.timestamp, "isoformat") else str(evt.timestamp)
|
|
169
|
+
|
|
170
|
+
if evt.event_type == EventType.TASK_STARTED:
|
|
171
|
+
user_name = evt.payload.get("initiating_user") or "User/Client"
|
|
172
|
+
user_node_id = f"user_{evt.trace_id}"
|
|
173
|
+
add_node(CausalNode(
|
|
174
|
+
node_id=user_node_id,
|
|
175
|
+
node_type="USER",
|
|
176
|
+
label=f"User: {user_name}",
|
|
177
|
+
details={"initiating_user": user_name},
|
|
178
|
+
timestamp=evt_time_str,
|
|
179
|
+
))
|
|
180
|
+
|
|
181
|
+
task_id = evt.task_id or f"task_{evt.trace_id}"
|
|
182
|
+
task_node_id = task_id
|
|
183
|
+
add_node(CausalNode(
|
|
184
|
+
node_id=task_id,
|
|
185
|
+
node_type="TASK",
|
|
186
|
+
label=f"Task: {evt.payload.get('intent', 'Task')}",
|
|
187
|
+
details=evt.payload,
|
|
188
|
+
timestamp=evt_time_str,
|
|
189
|
+
))
|
|
190
|
+
add_edge(user_node_id, task_id, "INITIATED")
|
|
191
|
+
last_entity_id = task_id
|
|
192
|
+
|
|
193
|
+
elif evt.event_type == EventType.AGENT_DELEGATED:
|
|
194
|
+
delegator_id = evt.payload.get("delegator_id")
|
|
195
|
+
delegator_name = evt.payload.get("delegator_name", delegator_id)
|
|
196
|
+
delegate_id = evt.payload.get("delegate_id")
|
|
197
|
+
delegate_name = evt.payload.get("delegate_name", delegate_id)
|
|
198
|
+
caps = evt.payload.get("granted_capabilities", [])
|
|
199
|
+
|
|
200
|
+
delegator_node_id = f"agent_{delegator_id}"
|
|
201
|
+
delegate_node_id = f"agent_{delegate_id}"
|
|
202
|
+
|
|
203
|
+
add_node(CausalNode(
|
|
204
|
+
node_id=delegator_node_id,
|
|
205
|
+
node_type="AGENT",
|
|
206
|
+
label=f"Agent: {delegator_name}",
|
|
207
|
+
details={"agent_id": delegator_id},
|
|
208
|
+
timestamp=evt_time_str,
|
|
209
|
+
))
|
|
210
|
+
|
|
211
|
+
add_node(CausalNode(
|
|
212
|
+
node_id=delegate_node_id,
|
|
213
|
+
node_type="AGENT",
|
|
214
|
+
label=f"Agent: {delegate_name} (Granted: {caps})",
|
|
215
|
+
details={"agent_id": delegate_id, "granted_capabilities": caps},
|
|
216
|
+
timestamp=evt_time_str,
|
|
217
|
+
))
|
|
218
|
+
|
|
219
|
+
# Link from task or delegator
|
|
220
|
+
if last_entity_id and last_entity_id == task_node_id:
|
|
221
|
+
add_edge(task_node_id, delegator_node_id, "ENGAGED")
|
|
222
|
+
|
|
223
|
+
add_edge(delegator_node_id, delegate_node_id, "DELEGATED_TO")
|
|
224
|
+
last_entity_id = delegate_node_id
|
|
225
|
+
|
|
226
|
+
elif evt.event_type in (EventType.CONTEXT_RECEIVED, EventType.CONTEXT_PROPAGATED, EventType.CONTEXT_SANITIZED):
|
|
227
|
+
ctx_id = evt.context_id or f"ctx_{len(nodes)}"
|
|
228
|
+
taint = evt.payload.get("taint_state", evt.payload.get("new_taint", "CLEAN"))
|
|
229
|
+
src = evt.payload.get("source", "external")
|
|
230
|
+
action = evt.payload.get("action", evt.event_type.value)
|
|
231
|
+
ctx_node_id = f"ctx_{ctx_id}"
|
|
232
|
+
node_details = dict(evt.payload)
|
|
233
|
+
node_details["context_id"] = ctx_id
|
|
234
|
+
|
|
235
|
+
add_node(CausalNode(
|
|
236
|
+
node_id=ctx_node_id,
|
|
237
|
+
node_type="CONTEXT",
|
|
238
|
+
label=f"Context: {src} [Taint: {taint}]",
|
|
239
|
+
details=node_details,
|
|
240
|
+
timestamp=evt_time_str,
|
|
241
|
+
))
|
|
242
|
+
|
|
243
|
+
acting_agent_id = f"agent_{evt.agent_id}" if evt.agent_id else last_entity_id
|
|
244
|
+
if acting_agent_id:
|
|
245
|
+
add_edge(acting_agent_id, ctx_node_id, "INGESTED_OR_PROPAGATED")
|
|
246
|
+
last_entity_id = ctx_node_id
|
|
247
|
+
|
|
248
|
+
elif evt.event_type == EventType.TOOL_REQUESTED:
|
|
249
|
+
tool_name = evt.payload.get("tool_name", "Tool")
|
|
250
|
+
tool_id = evt.payload.get("tool_id", "tool")
|
|
251
|
+
sens = evt.payload.get("sensitivity", "MEDIUM")
|
|
252
|
+
tool_node_id = f"tool_req_{tool_id}_{evt.event_id}"
|
|
253
|
+
|
|
254
|
+
add_node(CausalNode(
|
|
255
|
+
node_id=tool_node_id,
|
|
256
|
+
node_type="TOOL",
|
|
257
|
+
label=f"Tool: {tool_name} (Sensitivity: {sens})",
|
|
258
|
+
details=evt.payload,
|
|
259
|
+
timestamp=evt_time_str,
|
|
260
|
+
))
|
|
261
|
+
|
|
262
|
+
agent_id = f"agent_{evt.agent_id}" if evt.agent_id else last_entity_id
|
|
263
|
+
if agent_id:
|
|
264
|
+
add_edge(agent_id, tool_node_id, "INVOKED")
|
|
265
|
+
last_entity_id = tool_node_id
|
|
266
|
+
|
|
267
|
+
elif evt.event_type == EventType.RESOURCE_ACCESS_REQUESTED:
|
|
268
|
+
rsc_name = evt.payload.get("resource_name", "Resource")
|
|
269
|
+
rsc_id = evt.payload.get("resource_id", "rsc")
|
|
270
|
+
sens = evt.payload.get("sensitivity", "MEDIUM")
|
|
271
|
+
rsc_node_id = f"rsc_{rsc_id}_{evt.event_id}"
|
|
272
|
+
|
|
273
|
+
add_node(CausalNode(
|
|
274
|
+
node_id=rsc_node_id,
|
|
275
|
+
node_type="RESOURCE",
|
|
276
|
+
label=f"Resource: {rsc_name} [{sens}]",
|
|
277
|
+
details=evt.payload,
|
|
278
|
+
timestamp=evt_time_str,
|
|
279
|
+
))
|
|
280
|
+
|
|
281
|
+
if last_entity_id:
|
|
282
|
+
add_edge(last_entity_id, rsc_node_id, "TARGETS")
|
|
283
|
+
last_entity_id = rsc_node_id
|
|
284
|
+
|
|
285
|
+
elif evt.event_type == EventType.SECURITY_DECISION:
|
|
286
|
+
action = evt.payload.get("action", "DECISION")
|
|
287
|
+
reason = evt.payload.get("reason_code", "")
|
|
288
|
+
score = evt.payload.get("risk_score", 0.0)
|
|
289
|
+
dec_node_id = f"dec_{evt.payload.get('decision_id', evt.event_id)}"
|
|
290
|
+
|
|
291
|
+
add_node(CausalNode(
|
|
292
|
+
node_id=dec_node_id,
|
|
293
|
+
node_type="DECISION",
|
|
294
|
+
label=f"Decision: {action} (Reason: {reason}, Risk: {score})",
|
|
295
|
+
details=evt.payload,
|
|
296
|
+
timestamp=evt_time_str,
|
|
297
|
+
))
|
|
298
|
+
|
|
299
|
+
if last_entity_id:
|
|
300
|
+
add_edge(last_entity_id, dec_node_id, "GOVERNED_BY")
|
|
301
|
+
last_entity_id = dec_node_id
|
|
302
|
+
|
|
303
|
+
return CausalGraph(trace_id=trace_id, nodes=nodes, edges=edges)
|
|
304
|
+
|
|
305
|
+
|
actshield/watch.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ActShield Live Event Watcher
|
|
3
|
+
=============================
|
|
4
|
+
Renders a live terminal feed of security events, posture changes, and incidents using Rich.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
if sys.platform == "win32":
|
|
14
|
+
try:
|
|
15
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
16
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
17
|
+
except Exception:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
from rich.layout import Layout
|
|
22
|
+
from rich.live import Live
|
|
23
|
+
from rich.panel import Panel
|
|
24
|
+
from rich.table import Table
|
|
25
|
+
from rich.text import Text
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _get_severity_style(severity: str) -> str:
|
|
29
|
+
s = str(severity).upper()
|
|
30
|
+
if s in ("CRITICAL", "BLOCK", "DENY"):
|
|
31
|
+
return "bold red"
|
|
32
|
+
elif s in ("HIGH", "QUARANTINE", "RESTRICT"):
|
|
33
|
+
return "bold yellow"
|
|
34
|
+
elif s in ("MEDIUM", "SUSPICIOUS", "WARN"):
|
|
35
|
+
return "yellow"
|
|
36
|
+
elif s in ("LOW", "INFO", "ALLOW"):
|
|
37
|
+
return "green"
|
|
38
|
+
return "dim"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def generate_watch_table(events: List[Dict[str, Any]]) -> Table:
|
|
42
|
+
"""Generate a rich table for live events."""
|
|
43
|
+
table = Table(
|
|
44
|
+
title="[bold cyan]⚡ LIVE CAUSAL SECURITY EVENT STREAM[/bold cyan]",
|
|
45
|
+
title_justify="left",
|
|
46
|
+
border_style="dim white",
|
|
47
|
+
header_style="bold bright_white on grey23",
|
|
48
|
+
expand=True,
|
|
49
|
+
)
|
|
50
|
+
table.add_column("TIME", style="dim", width=12)
|
|
51
|
+
table.add_column("EVENT ID", style="bold cyan", width=14)
|
|
52
|
+
table.add_column("AGENT", style="magenta", width=16)
|
|
53
|
+
table.add_column("ACTION / TOOL", style="white", width=22)
|
|
54
|
+
table.add_column("DECISION", width=12)
|
|
55
|
+
table.add_column("SEVERITY", width=12)
|
|
56
|
+
table.add_column("DETAILS", style="dim", no_wrap=False)
|
|
57
|
+
|
|
58
|
+
if not events:
|
|
59
|
+
table.add_row(
|
|
60
|
+
datetime.now(timezone.utc).strftime("%H:%M:%S"),
|
|
61
|
+
"—",
|
|
62
|
+
"—",
|
|
63
|
+
"Waiting for live events...",
|
|
64
|
+
"[green]PASS[/green]",
|
|
65
|
+
"[dim]INFO[/dim]",
|
|
66
|
+
"Telemetry stream listening on active runtime",
|
|
67
|
+
)
|
|
68
|
+
return table
|
|
69
|
+
|
|
70
|
+
for ev in reversed(events[-15:]):
|
|
71
|
+
ts = ev.get("timestamp", datetime.now(timezone.utc).strftime("%H:%M:%S"))
|
|
72
|
+
if isinstance(ts, datetime):
|
|
73
|
+
ts = ts.strftime("%H:%M:%S")
|
|
74
|
+
elif "T" in str(ts):
|
|
75
|
+
ts = str(ts).split("T")[1][:8]
|
|
76
|
+
|
|
77
|
+
sev = ev.get("severity", "LOW")
|
|
78
|
+
sev_styled = f"[{_get_severity_style(sev)}]{sev}[/{_get_severity_style(sev)}]"
|
|
79
|
+
dec = ev.get("decision", "ALLOW")
|
|
80
|
+
dec_styled = f"[{_get_severity_style(dec)}]{dec}[/{_get_severity_style(dec)}]"
|
|
81
|
+
|
|
82
|
+
table.add_row(
|
|
83
|
+
str(ts),
|
|
84
|
+
str(ev.get("event_id", "evt_live"))[:12],
|
|
85
|
+
str(ev.get("agent_id", "agent_main")),
|
|
86
|
+
str(ev.get("tool_name", ev.get("action", "unknown"))),
|
|
87
|
+
dec_styled,
|
|
88
|
+
sev_styled,
|
|
89
|
+
str(ev.get("reason", ev.get("details", "OK")))[:50],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return table
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def run_live_watcher(poll_interval: float = 1.0, max_iterations: Optional[int] = None) -> None:
|
|
96
|
+
"""Run interactive continuous live event watcher."""
|
|
97
|
+
console = Console()
|
|
98
|
+
events_buffer: List[Dict[str, Any]] = []
|
|
99
|
+
|
|
100
|
+
# Check if API or SDK runtime is active
|
|
101
|
+
from actshield.client import ActShield
|
|
102
|
+
ag = ActShield()
|
|
103
|
+
|
|
104
|
+
# Seed with initial telemetry if available
|
|
105
|
+
try:
|
|
106
|
+
if hasattr(ag, "forensics") and hasattr(ag.forensics, "get_all_events"):
|
|
107
|
+
events_buffer = ag.forensics.get_all_events()
|
|
108
|
+
except Exception:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
iteration = 0
|
|
112
|
+
with Live(generate_watch_table(events_buffer), console=console, refresh_per_second=4, screen=False) as live:
|
|
113
|
+
try:
|
|
114
|
+
while max_iterations is None or iteration < max_iterations:
|
|
115
|
+
time.sleep(poll_interval)
|
|
116
|
+
iteration += 1
|
|
117
|
+
|
|
118
|
+
# Poll live runtime state or mock trace if idle
|
|
119
|
+
try:
|
|
120
|
+
from actshield.posture import PostureEngine
|
|
121
|
+
posture = PostureEngine().evaluate_current_posture()
|
|
122
|
+
except Exception:
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
live.update(generate_watch_table(events_buffer))
|
|
126
|
+
except KeyboardInterrupt:
|
|
127
|
+
console.print("\n[yellow]Live event watcher stopped.[/yellow]")
|
|
128
|
+
|
|
129
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: actshield
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Enterprise Security Control Plane SDK for Autonomous and Multi-Agent AI Systems
|
|
5
|
+
Author: ActShield Security Team
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/agentguard/agentguard
|
|
8
|
+
Project-URL: Documentation, https://agentguard.dev/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/agentguard/agentguard
|
|
10
|
+
Project-URL: Issue Tracker, https://github.com/agentguard/agentguard/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/agentguard/agentguard/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: ai-security,multi-agent,provenance,causal-tracing,policy-enforcement,llm-security,taint-tracking,threat-modeling,sdk,cli,security-control-plane,agent-security
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Information Technology
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: System :: Monitoring
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Operating System :: OS Independent
|
|
25
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
Requires-Dist: pydantic>=2.5.0
|
|
30
|
+
Requires-Dist: typing-extensions>=4.8.0
|
|
31
|
+
Provides-Extra: cli
|
|
32
|
+
Requires-Dist: rich>=13.7.0; extra == "cli"
|
|
33
|
+
Requires-Dist: typer>=0.12.0; extra == "cli"
|
|
34
|
+
Requires-Dist: click>=8.1.0; extra == "cli"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
38
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
39
|
+
Requires-Dist: mypy>=1.10.0; extra == "dev"
|
|
40
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
41
|
+
Provides-Extra: server
|
|
42
|
+
Requires-Dist: fastapi>=0.110.0; extra == "server"
|
|
43
|
+
Requires-Dist: uvicorn[standard]>=0.29.0; extra == "server"
|
|
44
|
+
Requires-Dist: httpx>=0.27.0; extra == "server"
|
|
45
|
+
Requires-Dist: pydantic-settings>=2.0.0; extra == "server"
|
|
46
|
+
Provides-Extra: dashboard
|
|
47
|
+
Requires-Dist: actshield[cli,server]; extra == "dashboard"
|
|
48
|
+
Provides-Extra: openai
|
|
49
|
+
Requires-Dist: openai>=1.30.0; extra == "openai"
|
|
50
|
+
Provides-Extra: gemini
|
|
51
|
+
Requires-Dist: google-generativeai>=0.7.0; extra == "gemini"
|
|
52
|
+
Provides-Extra: anthropic
|
|
53
|
+
Requires-Dist: anthropic>=0.28.0; extra == "anthropic"
|
|
54
|
+
Provides-Extra: ollama
|
|
55
|
+
Requires-Dist: httpx>=0.27.0; extra == "ollama"
|
|
56
|
+
Provides-Extra: all
|
|
57
|
+
Requires-Dist: actshield[anthropic,cli,gemini,ollama,openai,server]; extra == "all"
|