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,367 @@
|
|
|
1
|
+
"""Forensic query engine — answers structured forensic questions.
|
|
2
|
+
|
|
3
|
+
Implements all named queries from the spec:
|
|
4
|
+
get_agent_access, get_resource_access, get_agent_delegation_chain,
|
|
5
|
+
get_resource_authorized_agents, get_agent_attempts, get_agent_actual_access,
|
|
6
|
+
get_resource_access_history, get_reachable_resources, get_reachable_agents,
|
|
7
|
+
get_access_snapshot, compare_access_snapshots, get_influencing_context,
|
|
8
|
+
get_causal_path, get_attack_forensics, get_decision_explanation,
|
|
9
|
+
get_regression_forensics
|
|
10
|
+
|
|
11
|
+
All queries are read-only. None can modify security authority.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
from actshield.forensics.models import (
|
|
19
|
+
AccessDiff,
|
|
20
|
+
AccessMatrix,
|
|
21
|
+
AccessMatrixEntry,
|
|
22
|
+
AccessSnapshot,
|
|
23
|
+
AgentAccessProfile,
|
|
24
|
+
AttackForensicReport,
|
|
25
|
+
ForensicExplanation,
|
|
26
|
+
ForensicIncident,
|
|
27
|
+
ResourceAccessProfile,
|
|
28
|
+
)
|
|
29
|
+
from actshield.forensics.snapshots import compare_snapshots
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from actshield.forensics.service import ForensicService
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ForensicQueryEngine:
|
|
36
|
+
"""Central query interface for the forensic layer.
|
|
37
|
+
|
|
38
|
+
Every method is read-only with respect to the security runtime.
|
|
39
|
+
Queries never grant capabilities, modify taint, or bypass policy.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, service: "ForensicService") -> None:
|
|
43
|
+
self._svc = service
|
|
44
|
+
|
|
45
|
+
# ------------------------------------------------------------------
|
|
46
|
+
# Agent queries
|
|
47
|
+
# ------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
def get_agent_access(self, agent_id: str) -> Optional[AgentAccessProfile]:
|
|
50
|
+
"""Return full forensic access profile for an agent."""
|
|
51
|
+
agent = self._svc._guard.agent_registry.get(agent_id)
|
|
52
|
+
if agent is None:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
calc = self._svc._calculator
|
|
56
|
+
declared = calc.get_declared_capabilities(agent_id) if calc else []
|
|
57
|
+
delegated = calc.get_delegated_capabilities(agent_id) if calc else []
|
|
58
|
+
effective = calc.get_effective_capabilities(agent_id) if calc else []
|
|
59
|
+
reachable_tools = calc.get_reachable_tools(agent_id) if calc else []
|
|
60
|
+
reachable_resources = calc.get_reachable_resources(agent_id) if calc else []
|
|
61
|
+
|
|
62
|
+
# Authority graph
|
|
63
|
+
auth_graph = self._svc._auth_graph
|
|
64
|
+
delegation_chain = auth_graph.get_delegation_chain(agent_id) if auth_graph else []
|
|
65
|
+
|
|
66
|
+
# Access graph
|
|
67
|
+
acc_graph = self._svc.access_graph
|
|
68
|
+
attempts = acc_graph.get_agent_attempts(agent_id)
|
|
69
|
+
actual = acc_graph.get_agent_actual_access(agent_id)
|
|
70
|
+
|
|
71
|
+
# Never count blocked attempts as actual access
|
|
72
|
+
total_blocked = sum(1 for a in attempts if a.was_blocked)
|
|
73
|
+
total_allowed = sum(1 for a in attempts if a.was_allowed)
|
|
74
|
+
total_executions = sum(a.execution_count for a in actual)
|
|
75
|
+
|
|
76
|
+
return AgentAccessProfile(
|
|
77
|
+
agent_id=agent_id,
|
|
78
|
+
agent_name=agent.name,
|
|
79
|
+
trust_level=agent.trust_level.value,
|
|
80
|
+
declared_capabilities=declared,
|
|
81
|
+
delegated_capabilities=delegated,
|
|
82
|
+
effective_capabilities=effective,
|
|
83
|
+
restricted_capabilities=[],
|
|
84
|
+
reachable_tools=reachable_tools,
|
|
85
|
+
reachable_resources=reachable_resources,
|
|
86
|
+
recent_attempts=attempts[-20:],
|
|
87
|
+
recent_actual_access=actual[-20:],
|
|
88
|
+
delegation_chain=delegation_chain,
|
|
89
|
+
total_attempts=len(attempts),
|
|
90
|
+
total_blocked=total_blocked,
|
|
91
|
+
total_allowed=total_allowed,
|
|
92
|
+
total_executions=total_executions,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def get_all_agents(self) -> List[str]:
|
|
96
|
+
return sorted(self._svc._guard.agent_registry.keys())
|
|
97
|
+
|
|
98
|
+
def get_agent_delegation_chain(self, agent_id: str) -> List[Dict[str, Any]]:
|
|
99
|
+
"""Return the full delegation chain leading to agent_id."""
|
|
100
|
+
if not self._svc._auth_graph:
|
|
101
|
+
return []
|
|
102
|
+
chain = self._svc._auth_graph.get_delegation_chain(agent_id)
|
|
103
|
+
return [c.model_dump(mode="json") for c in chain]
|
|
104
|
+
|
|
105
|
+
def get_agent_attempts(self, agent_id: str) -> List[Dict[str, Any]]:
|
|
106
|
+
return [
|
|
107
|
+
a.model_dump(mode="json")
|
|
108
|
+
for a in self._svc.access_graph.get_agent_attempts(agent_id)
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
def get_agent_actual_access(self, agent_id: str) -> List[Dict[str, Any]]:
|
|
112
|
+
return [
|
|
113
|
+
a.model_dump(mode="json")
|
|
114
|
+
for a in self._svc.access_graph.get_agent_actual_access(agent_id)
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
def get_reachable_resources(self, agent_id: str) -> List[str]:
|
|
118
|
+
"""Deterministic reachable-resource analysis using authority state."""
|
|
119
|
+
if not self._svc._calculator:
|
|
120
|
+
return []
|
|
121
|
+
return self._svc._calculator.get_reachable_resources(agent_id)
|
|
122
|
+
|
|
123
|
+
def get_reachable_tools(self, agent_id: str) -> List[str]:
|
|
124
|
+
if not self._svc._calculator:
|
|
125
|
+
return []
|
|
126
|
+
return self._svc._calculator.get_reachable_tools(agent_id)
|
|
127
|
+
|
|
128
|
+
# ------------------------------------------------------------------
|
|
129
|
+
# Resource queries
|
|
130
|
+
# ------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
def get_resource_access(
|
|
133
|
+
self, resource_name: str
|
|
134
|
+
) -> Optional[ResourceAccessProfile]:
|
|
135
|
+
return self._svc.access_graph.get_resource_profile(resource_name)
|
|
136
|
+
|
|
137
|
+
def get_all_resources(self) -> List[str]:
|
|
138
|
+
return self._svc.access_graph.get_all_resources()
|
|
139
|
+
|
|
140
|
+
def get_resource_authorized_agents(
|
|
141
|
+
self, resource_name: str
|
|
142
|
+
) -> List[str]:
|
|
143
|
+
return self._svc.access_graph.get_reachable_agents(resource_name)
|
|
144
|
+
|
|
145
|
+
def get_resource_access_history(
|
|
146
|
+
self, resource_name: str
|
|
147
|
+
) -> List[Dict[str, Any]]:
|
|
148
|
+
return [
|
|
149
|
+
a.model_dump(mode="json")
|
|
150
|
+
for a in self._svc.access_graph.get_resource_history(resource_name)
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
def get_reachable_agents(self, resource_name: str) -> List[str]:
|
|
154
|
+
return self._svc.access_graph.get_reachable_agents(resource_name)
|
|
155
|
+
|
|
156
|
+
# ------------------------------------------------------------------
|
|
157
|
+
# Access matrix
|
|
158
|
+
# ------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
def get_access_matrix(self) -> AccessMatrix:
|
|
161
|
+
"""Build the full agent × resource access matrix."""
|
|
162
|
+
agent_ids = self.get_all_agents()
|
|
163
|
+
resource_names = self.get_all_resources()
|
|
164
|
+
|
|
165
|
+
# Also include resources from tool registry
|
|
166
|
+
for tool_def in self._svc._guard.tool_registry.values():
|
|
167
|
+
for resource in tool_def.target_resources:
|
|
168
|
+
if resource.name not in resource_names:
|
|
169
|
+
resource_names.append(resource.name)
|
|
170
|
+
resource_names = sorted(set(resource_names))
|
|
171
|
+
|
|
172
|
+
matrix = AccessMatrix(agents=agent_ids, resources=resource_names)
|
|
173
|
+
calc = self._svc._calculator
|
|
174
|
+
|
|
175
|
+
for agent_id in agent_ids:
|
|
176
|
+
matrix.entries[agent_id] = {}
|
|
177
|
+
eff_perm = set(calc.get_reachable_resources(agent_id)) if calc else set()
|
|
178
|
+
attempts = self._svc.access_graph.get_agent_attempts(agent_id)
|
|
179
|
+
actual = self._svc.access_graph.get_agent_actual_access(agent_id)
|
|
180
|
+
|
|
181
|
+
for resource_name in resource_names:
|
|
182
|
+
attempted = any(
|
|
183
|
+
a.resource_name == resource_name or a.tool_name == resource_name
|
|
184
|
+
for a in attempts
|
|
185
|
+
)
|
|
186
|
+
blocked = any(
|
|
187
|
+
(a.resource_name == resource_name or a.tool_name == resource_name)
|
|
188
|
+
and a.was_blocked
|
|
189
|
+
for a in attempts
|
|
190
|
+
)
|
|
191
|
+
historically_accessed = any(
|
|
192
|
+
a.resource_name == resource_name or a.tool_name == resource_name
|
|
193
|
+
for a in actual
|
|
194
|
+
)
|
|
195
|
+
matrix.entries[agent_id][resource_name] = AccessMatrixEntry(
|
|
196
|
+
agent_id=agent_id,
|
|
197
|
+
resource_name=resource_name,
|
|
198
|
+
effective_permission=resource_name in eff_perm,
|
|
199
|
+
historically_accessed=historically_accessed,
|
|
200
|
+
attempted=attempted,
|
|
201
|
+
blocked=blocked,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return matrix
|
|
205
|
+
|
|
206
|
+
# ------------------------------------------------------------------
|
|
207
|
+
# Snapshot queries
|
|
208
|
+
# ------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
def get_access_snapshot(
|
|
211
|
+
self, snapshot_id: str
|
|
212
|
+
) -> Optional[AccessSnapshot]:
|
|
213
|
+
return self._svc._snapshots.get(snapshot_id)
|
|
214
|
+
|
|
215
|
+
def list_snapshots(self) -> List[Dict[str, Any]]:
|
|
216
|
+
return [
|
|
217
|
+
{"snapshot_id": s.snapshot_id, "label": s.label, "timestamp": s.timestamp.isoformat()}
|
|
218
|
+
for s in self._svc._snapshots.list_all()
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
def compare_access_snapshots(
|
|
222
|
+
self, before_id: str, after_id: str
|
|
223
|
+
) -> Optional[AccessDiff]:
|
|
224
|
+
before = self._svc._snapshots.get(before_id)
|
|
225
|
+
after = self._svc._snapshots.get(after_id)
|
|
226
|
+
if not before or not after:
|
|
227
|
+
return None
|
|
228
|
+
return compare_snapshots(before, after)
|
|
229
|
+
|
|
230
|
+
# ------------------------------------------------------------------
|
|
231
|
+
# Trace / causal queries
|
|
232
|
+
# ------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
def get_causal_path(self, trace_id: str) -> Optional[str]:
|
|
235
|
+
"""Return the causal graph JSON for a trace (from storage)."""
|
|
236
|
+
if self._svc._guard.storage:
|
|
237
|
+
return self._svc._guard.storage.get_trace_graph(trace_id)
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
def get_influencing_context(
|
|
241
|
+
self, trace_id: str
|
|
242
|
+
) -> List[Dict[str, Any]]:
|
|
243
|
+
"""Return context records influencing a trace."""
|
|
244
|
+
events = self._svc._guard.storage.get_events(trace_id=trace_id)
|
|
245
|
+
contexts = []
|
|
246
|
+
for event in events:
|
|
247
|
+
if event.event_type.value in (
|
|
248
|
+
"context.received", "context.propagated", "context.generated"
|
|
249
|
+
):
|
|
250
|
+
contexts.append({
|
|
251
|
+
"event_id": event.event_id,
|
|
252
|
+
"event_type": event.event_type.value,
|
|
253
|
+
"context_id": event.context_id,
|
|
254
|
+
"agent_id": event.agent_id,
|
|
255
|
+
"timestamp": event.timestamp.isoformat(),
|
|
256
|
+
"payload": event.payload,
|
|
257
|
+
})
|
|
258
|
+
return contexts
|
|
259
|
+
|
|
260
|
+
# ------------------------------------------------------------------
|
|
261
|
+
# Attack / campaign queries
|
|
262
|
+
# ------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
def get_attack_forensics(
|
|
265
|
+
self, attack_id: str
|
|
266
|
+
) -> Optional[AttackForensicReport]:
|
|
267
|
+
"""Return the full forensic report for an attack."""
|
|
268
|
+
return self._svc._explainer.explain_attack(attack_id)
|
|
269
|
+
|
|
270
|
+
def get_regression_forensics(
|
|
271
|
+
self, regression_id: str
|
|
272
|
+
) -> Optional[AttackForensicReport]:
|
|
273
|
+
"""Return forensic report for a regression attack (same as attack)."""
|
|
274
|
+
return self.get_attack_forensics(regression_id)
|
|
275
|
+
|
|
276
|
+
def get_all_attack_ids(self) -> List[str]:
|
|
277
|
+
return list(self._svc._attack_results.keys())
|
|
278
|
+
|
|
279
|
+
def get_all_bypass_attacks(self) -> List[str]:
|
|
280
|
+
return [
|
|
281
|
+
aid for aid, r in self._svc._attack_results.items()
|
|
282
|
+
if r.bypassed
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
# ------------------------------------------------------------------
|
|
286
|
+
# Decision explanation
|
|
287
|
+
# ------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
def get_decision_explanation(
|
|
290
|
+
self,
|
|
291
|
+
event_id: Optional[str] = None,
|
|
292
|
+
trace_id: Optional[str] = None,
|
|
293
|
+
agent_id: Optional[str] = None,
|
|
294
|
+
tool_name: Optional[str] = None,
|
|
295
|
+
) -> ForensicExplanation:
|
|
296
|
+
"""Return a deterministic explanation for a security decision."""
|
|
297
|
+
return self._svc._explainer.explain_decision(
|
|
298
|
+
event_id=event_id,
|
|
299
|
+
trace_id=trace_id,
|
|
300
|
+
agent_id=agent_id,
|
|
301
|
+
tool_name=tool_name,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# ------------------------------------------------------------------
|
|
305
|
+
# Incidents
|
|
306
|
+
# ------------------------------------------------------------------
|
|
307
|
+
|
|
308
|
+
def get_all_incidents(self) -> List[ForensicIncident]:
|
|
309
|
+
return list(self._svc._incidents.values())
|
|
310
|
+
|
|
311
|
+
def get_incident(self, incident_id: str) -> Optional[ForensicIncident]:
|
|
312
|
+
return self._svc._incidents.get(incident_id)
|
|
313
|
+
|
|
314
|
+
# ------------------------------------------------------------------
|
|
315
|
+
# Overview metrics
|
|
316
|
+
# ------------------------------------------------------------------
|
|
317
|
+
|
|
318
|
+
def get_overview(self) -> Dict[str, Any]:
|
|
319
|
+
"""Return aggregate dashboard-ready metrics."""
|
|
320
|
+
guard = self._svc._guard
|
|
321
|
+
attempts = self._svc.access_graph.get_all_attempts()
|
|
322
|
+
actual = self._svc.access_graph.get_all_actual_accesses()
|
|
323
|
+
bypass_count = len(self.get_all_bypass_attacks())
|
|
324
|
+
incidents = list(self._svc._incidents.values())
|
|
325
|
+
|
|
326
|
+
# Count today's attacks (all attacks from attack_results)
|
|
327
|
+
attack_count = len(self._svc._attack_results)
|
|
328
|
+
blocked_count = sum(1 for a in attempts if a.was_blocked)
|
|
329
|
+
hitl_count = sum(
|
|
330
|
+
1 for a in attempts if a.decision.value == "HITL"
|
|
331
|
+
)
|
|
332
|
+
sensitive_attempts = sum(
|
|
333
|
+
1 for a in attempts
|
|
334
|
+
if a.resource_sensitivity in ("HIGH", "CRITICAL")
|
|
335
|
+
)
|
|
336
|
+
sensitive_prevented = sum(
|
|
337
|
+
1 for a in attempts
|
|
338
|
+
if a.resource_sensitivity in ("HIGH", "CRITICAL") and a.was_blocked
|
|
339
|
+
)
|
|
340
|
+
sensitive_executed = sum(
|
|
341
|
+
a.sensitive_db_calls for a in actual
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
"agents": len(guard.agent_registry),
|
|
346
|
+
"active_tasks": None, # TaskManager not directly accessible
|
|
347
|
+
"protected_tools": len(guard.tool_registry),
|
|
348
|
+
"mcp_servers": None,
|
|
349
|
+
"attacks_today": attack_count,
|
|
350
|
+
"blocked": blocked_count,
|
|
351
|
+
"hitl": hitl_count,
|
|
352
|
+
"bypasses": bypass_count,
|
|
353
|
+
"sensitive_attempts": sensitive_attempts,
|
|
354
|
+
"sensitive_prevented": sensitive_prevented,
|
|
355
|
+
"sensitive_executed": sensitive_executed,
|
|
356
|
+
"campaigns": len({
|
|
357
|
+
r.campaign_id for r in self._svc._attack_results.values()
|
|
358
|
+
if r.campaign_id
|
|
359
|
+
}),
|
|
360
|
+
"regressions": bypass_count,
|
|
361
|
+
"incidents": len(incidents),
|
|
362
|
+
"availability": {
|
|
363
|
+
"active_tasks": False,
|
|
364
|
+
"mcp_servers": False,
|
|
365
|
+
},
|
|
366
|
+
}
|
|
367
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""JSON serializers for forensic exports.
|
|
2
|
+
|
|
3
|
+
All forensic data exported to reports/forensics/ uses stable schemas.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _default_serializer(obj: Any) -> Any:
|
|
15
|
+
if isinstance(obj, datetime):
|
|
16
|
+
return obj.isoformat()
|
|
17
|
+
if hasattr(obj, "model_dump"):
|
|
18
|
+
return obj.model_dump(mode="json")
|
|
19
|
+
if hasattr(obj, "value"):
|
|
20
|
+
return obj.value
|
|
21
|
+
return str(obj)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def to_json(data: Any, indent: int = 2) -> str:
|
|
25
|
+
"""Serialize forensic data to JSON string."""
|
|
26
|
+
if hasattr(data, "model_dump"):
|
|
27
|
+
data = data.model_dump(mode="json")
|
|
28
|
+
return json.dumps(data, indent=indent, default=_default_serializer)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def export_json(
|
|
32
|
+
data: Any,
|
|
33
|
+
filepath: str,
|
|
34
|
+
indent: int = 2,
|
|
35
|
+
) -> str:
|
|
36
|
+
"""Export forensic data to a JSON file. Creates directory if needed."""
|
|
37
|
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
38
|
+
content = to_json(data, indent=indent)
|
|
39
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
40
|
+
f.write(content)
|
|
41
|
+
return filepath
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def sanitize_for_export(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
45
|
+
"""Remove sensitive fields before export.
|
|
46
|
+
|
|
47
|
+
Strips: credentials, tokens, raw passwords, internal paths.
|
|
48
|
+
"""
|
|
49
|
+
SENSITIVE_KEYS = {
|
|
50
|
+
"password", "secret", "token", "api_key", "credential",
|
|
51
|
+
"authorization", "bearer", "private_key",
|
|
52
|
+
}
|
|
53
|
+
if not isinstance(data, dict):
|
|
54
|
+
return data
|
|
55
|
+
result = {}
|
|
56
|
+
for key, value in data.items():
|
|
57
|
+
if any(s in key.lower() for s in SENSITIVE_KEYS):
|
|
58
|
+
result[key] = "[REDACTED]"
|
|
59
|
+
elif isinstance(value, dict):
|
|
60
|
+
result[key] = sanitize_for_export(value)
|
|
61
|
+
elif isinstance(value, list):
|
|
62
|
+
result[key] = [
|
|
63
|
+
sanitize_for_export(item) if isinstance(item, dict) else item
|
|
64
|
+
for item in value
|
|
65
|
+
]
|
|
66
|
+
else:
|
|
67
|
+
result[key] = value
|
|
68
|
+
return result
|