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,25 @@
|
|
|
1
|
+
"""ActShield Incidents Engine Package (Phase 9)."""
|
|
2
|
+
|
|
3
|
+
from actshield.incidents.incident import IncidentTimelineEntry, SecurityIncident
|
|
4
|
+
from actshield.incidents.incident_engine import IncidentEngine, IncidentTimelineBuilder
|
|
5
|
+
from actshield.incidents.incident_rules import IncidentCreationRule
|
|
6
|
+
from actshield.incidents.incident_state import (
|
|
7
|
+
ALLOWED_TRANSITIONS,
|
|
8
|
+
IncidentSeverity,
|
|
9
|
+
IncidentState,
|
|
10
|
+
IncidentStateMachine,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"IncidentTimelineEntry",
|
|
15
|
+
"SecurityIncident",
|
|
16
|
+
"IncidentEngine",
|
|
17
|
+
"IncidentTimelineBuilder",
|
|
18
|
+
"IncidentCreationRule",
|
|
19
|
+
"ALLOWED_TRANSITIONS",
|
|
20
|
+
"IncidentSeverity",
|
|
21
|
+
"IncidentState",
|
|
22
|
+
"IncidentStateMachine",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Security Incident Model for ActShield Phase 9."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from actshield.incidents.incident_state import IncidentSeverity, IncidentState
|
|
11
|
+
from actshield.tracing.correlation import generate_id
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IncidentTimelineEntry(BaseModel):
|
|
15
|
+
"""A chronological causal entry within an incident storyline."""
|
|
16
|
+
timestamp: str = Field(..., description="ISO timestamp or formatted time")
|
|
17
|
+
stage: str = Field(..., description="Stage e.g. '01. INTENT', '03. CONTEXT', '07. ENFORCE'")
|
|
18
|
+
agent_id: Optional[str] = None
|
|
19
|
+
agent_name: Optional[str] = None
|
|
20
|
+
action: str = ""
|
|
21
|
+
detail: str = ""
|
|
22
|
+
taint_state: str = "CLEAN"
|
|
23
|
+
decision: Optional[str] = None
|
|
24
|
+
event_id: Optional[str] = None
|
|
25
|
+
actor_name: Optional[str] = None
|
|
26
|
+
incident_state: Optional[IncidentState] = None
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def actor(self) -> str:
|
|
30
|
+
return self.actor_name or self.agent_name or self.agent_id or "system"
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def reason(self) -> str:
|
|
34
|
+
return self.detail or self.action
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def state(self) -> IncidentState:
|
|
38
|
+
return self.incident_state or IncidentState.DETECTED
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SecurityIncident(BaseModel):
|
|
42
|
+
"""Complete causal security incident record."""
|
|
43
|
+
incident_id: str = Field(default_factory=lambda: generate_id("inc"))
|
|
44
|
+
title: str = Field(..., description="Summary title of the incident")
|
|
45
|
+
severity: IncidentSeverity = Field(default=IncidentSeverity.MEDIUM)
|
|
46
|
+
state: IncidentState = Field(default=IncidentState.DETECTED)
|
|
47
|
+
attack_type: str = Field(default="policy_violation", description="Classification e.g. 'indirect_prompt_injection', 'authority_escape'")
|
|
48
|
+
root_event_id: Optional[str] = None
|
|
49
|
+
trace_id: str = Field(default_factory=lambda: generate_id("trc"), description="Causal trace ID linking all events")
|
|
50
|
+
|
|
51
|
+
# Affected security entities
|
|
52
|
+
affected_agents: List[str] = Field(default_factory=list)
|
|
53
|
+
affected_tasks: List[str] = Field(default_factory=list)
|
|
54
|
+
affected_resources: List[str] = Field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
# Causal context & taint state
|
|
57
|
+
context_source: Optional[str] = None
|
|
58
|
+
trust_state: str = "UNKNOWN"
|
|
59
|
+
taint_state: str = "CLEAN"
|
|
60
|
+
authority_violated: bool = False
|
|
61
|
+
|
|
62
|
+
# Decisions & runtime evidence
|
|
63
|
+
policy_verdict: str = "BLOCK"
|
|
64
|
+
sensitive_operations_attempted: int = 0
|
|
65
|
+
sensitive_operations_executed: int = 0 # CRITICAL: 0 proves prevention held
|
|
66
|
+
|
|
67
|
+
# Intelligence enrichments
|
|
68
|
+
ai_secura_summary: Optional[str] = None
|
|
69
|
+
apiris_signals: List[str] = Field(default_factory=list)
|
|
70
|
+
|
|
71
|
+
# Lifecycle & timeline
|
|
72
|
+
timeline: List[IncidentTimelineEntry] = Field(default_factory=list)
|
|
73
|
+
remediation_notes: Optional[str] = None
|
|
74
|
+
regression_id: Optional[str] = None
|
|
75
|
+
secured_replay_verified: bool = False
|
|
76
|
+
|
|
77
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
78
|
+
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
79
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def target_agent_id(self) -> str:
|
|
83
|
+
return self.affected_agents[0] if self.affected_agents else ""
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def tool_name(self) -> str:
|
|
87
|
+
return self.affected_resources[0] if self.affected_resources else ""
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def root_cause(self) -> str:
|
|
91
|
+
return self.ai_secura_summary or self.remediation_notes or "Security boundary violation"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Incident timeline builder and incident engine orchestrator for ActShield Phase 9."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from actshield.incidents.incident import IncidentTimelineEntry, SecurityIncident
|
|
9
|
+
from actshield.incidents.incident_rules import IncidentCreationRule
|
|
10
|
+
from actshield.incidents.incident_state import (
|
|
11
|
+
IncidentSeverity,
|
|
12
|
+
IncidentState,
|
|
13
|
+
IncidentStateMachine,
|
|
14
|
+
)
|
|
15
|
+
from actshield.tracing.correlation import generate_id
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IncidentTimelineBuilder:
|
|
19
|
+
"""Builds a structured causal storyline from correlated events."""
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def from_events(events: List[Dict[str, Any]]) -> List[IncidentTimelineEntry]:
|
|
23
|
+
timeline: List[IncidentTimelineEntry] = []
|
|
24
|
+
for i, evt in enumerate(events):
|
|
25
|
+
stage = f"0{i+1}. {evt.get('stage_name', evt.get('event_type', 'EVENT'))}"
|
|
26
|
+
timeline.append(IncidentTimelineEntry(
|
|
27
|
+
timestamp=str(evt.get("timestamp", datetime.now(timezone.utc).isoformat())),
|
|
28
|
+
stage=stage,
|
|
29
|
+
agent_id=evt.get("agent_id"),
|
|
30
|
+
agent_name=evt.get("agent_name"),
|
|
31
|
+
action=evt.get("action") or f"Called {evt.get('tool', 'tool')}",
|
|
32
|
+
detail=evt.get("detail") or evt.get("reason") or "Event executed in trace",
|
|
33
|
+
taint_state=evt.get("taint", "CLEAN"),
|
|
34
|
+
decision=evt.get("decision"),
|
|
35
|
+
event_id=evt.get("event_id"),
|
|
36
|
+
))
|
|
37
|
+
return timeline
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class IncidentEngine:
|
|
41
|
+
"""Manages continuous security incident generation, correlation, and lifecycle state."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, guard: Optional[Any] = None) -> None:
|
|
44
|
+
self.guard = guard
|
|
45
|
+
self.incidents: Dict[str, SecurityIncident] = {}
|
|
46
|
+
|
|
47
|
+
def record_incident(self, incident: SecurityIncident) -> SecurityIncident:
|
|
48
|
+
"""Explicitly register a SecurityIncident."""
|
|
49
|
+
self.incidents[incident.incident_id] = incident
|
|
50
|
+
return incident
|
|
51
|
+
|
|
52
|
+
def process_event(self, event: Dict[str, Any], related_events: Optional[List[Dict[str, Any]]] = None) -> Optional[SecurityIncident]:
|
|
53
|
+
"""Evaluate an event for incident creation and causal linking."""
|
|
54
|
+
should_create, title, severity, attack_type = IncidentCreationRule.evaluate(event)
|
|
55
|
+
if not should_create:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
trace_id = event.get("trace_id") or generate_id("trc")
|
|
59
|
+
|
|
60
|
+
# Check if active incident already exists for this trace
|
|
61
|
+
for existing in self.incidents.values():
|
|
62
|
+
if existing.trace_id == trace_id and existing.state not in (IncidentState.CLOSED, IncidentState.VALIDATED):
|
|
63
|
+
# Update existing incident
|
|
64
|
+
if event.get("agent_id") and event["agent_id"] not in existing.affected_agents:
|
|
65
|
+
existing.affected_agents.append(event["agent_id"])
|
|
66
|
+
if event.get("resource") and event["resource"] not in existing.affected_resources:
|
|
67
|
+
existing.affected_resources.append(event["resource"])
|
|
68
|
+
existing.updated_at = datetime.now(timezone.utc)
|
|
69
|
+
return existing
|
|
70
|
+
|
|
71
|
+
timeline_events = related_events or [event]
|
|
72
|
+
timeline = IncidentTimelineBuilder.from_events(timeline_events)
|
|
73
|
+
|
|
74
|
+
incident = SecurityIncident(
|
|
75
|
+
title=title or "Security Boundary Incident",
|
|
76
|
+
severity=severity,
|
|
77
|
+
state=IncidentState.DETECTED,
|
|
78
|
+
attack_type=attack_type or "unknown_threat",
|
|
79
|
+
root_event_id=event.get("event_id"),
|
|
80
|
+
trace_id=trace_id,
|
|
81
|
+
affected_agents=[event["agent_id"]] if event.get("agent_id") else [],
|
|
82
|
+
affected_tasks=[event["task_id"]] if event.get("task_id") else [],
|
|
83
|
+
affected_resources=[event["resource"]] if event.get("resource") else ([event["tool"]] if event.get("tool") else []),
|
|
84
|
+
context_source=event.get("context_source"),
|
|
85
|
+
trust_state=event.get("trust_level", "UNKNOWN"),
|
|
86
|
+
taint_state=event.get("taint", "CLEAN"),
|
|
87
|
+
authority_violated=bool(event.get("authority_violation", False)),
|
|
88
|
+
policy_verdict=event.get("decision", "BLOCK"),
|
|
89
|
+
sensitive_operations_attempted=1,
|
|
90
|
+
sensitive_operations_executed=1 if event.get("decision") == "ALLOW" and event.get("taint") == "TAINTED" else 0,
|
|
91
|
+
ai_secura_summary=event.get("ai_secura_summary") or event.get("threat_reason"),
|
|
92
|
+
timeline=timeline,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self.incidents[incident.incident_id] = incident
|
|
96
|
+
return incident
|
|
97
|
+
|
|
98
|
+
def transition_state(self, incident_id: str, target_state: IncidentState, remediation_notes: Optional[str] = None) -> SecurityIncident:
|
|
99
|
+
"""Deterministically transition an incident's lifecycle state."""
|
|
100
|
+
if incident_id not in self.incidents:
|
|
101
|
+
raise KeyError(f"Incident with ID '{incident_id}' not found.")
|
|
102
|
+
|
|
103
|
+
incident = self.incidents[incident_id]
|
|
104
|
+
IncidentStateMachine.validate_transition(incident.state, target_state)
|
|
105
|
+
incident.state = target_state
|
|
106
|
+
if remediation_notes:
|
|
107
|
+
incident.remediation_notes = remediation_notes
|
|
108
|
+
incident.updated_at = datetime.now(timezone.utc)
|
|
109
|
+
return incident
|
|
110
|
+
|
|
111
|
+
def transition_incident_state(
|
|
112
|
+
self,
|
|
113
|
+
incident_id: str,
|
|
114
|
+
to_state: IncidentState,
|
|
115
|
+
actor: str = "system",
|
|
116
|
+
reason: str = "",
|
|
117
|
+
) -> Optional[SecurityIncident]:
|
|
118
|
+
"""Transition incident state and append a timeline entry."""
|
|
119
|
+
if incident_id not in self.incidents:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
incident = self.incidents[incident_id]
|
|
123
|
+
IncidentStateMachine.validate_transition(incident.state, to_state)
|
|
124
|
+
incident.state = to_state
|
|
125
|
+
if reason:
|
|
126
|
+
incident.remediation_notes = reason
|
|
127
|
+
|
|
128
|
+
entry = IncidentTimelineEntry(
|
|
129
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
130
|
+
stage=f"Stage {to_state.value}",
|
|
131
|
+
agent_id=incident.target_agent_id,
|
|
132
|
+
action=f"Transitioned to {to_state.value}",
|
|
133
|
+
detail=reason or f"State changed to {to_state.value}",
|
|
134
|
+
actor_name=actor,
|
|
135
|
+
incident_state=to_state,
|
|
136
|
+
)
|
|
137
|
+
incident.timeline.append(entry)
|
|
138
|
+
incident.updated_at = datetime.now(timezone.utc)
|
|
139
|
+
return incident
|
|
140
|
+
|
|
141
|
+
def link_regression(self, incident_id: str, regression_id: str, secured_replay_passed: bool = True) -> SecurityIncident:
|
|
142
|
+
"""Attach verified regression fixture to incident and advance state."""
|
|
143
|
+
if incident_id not in self.incidents:
|
|
144
|
+
raise KeyError(f"Incident with ID '{incident_id}' not found.")
|
|
145
|
+
|
|
146
|
+
incident = self.incidents[incident_id]
|
|
147
|
+
incident.regression_id = regression_id
|
|
148
|
+
incident.secured_replay_verified = secured_replay_passed
|
|
149
|
+
if secured_replay_passed:
|
|
150
|
+
incident.state = IncidentState.VALIDATED
|
|
151
|
+
incident.updated_at = datetime.now(timezone.utc)
|
|
152
|
+
return incident
|
|
153
|
+
|
|
154
|
+
def get_incident(self, incident_id: str) -> Optional[SecurityIncident]:
|
|
155
|
+
return self.incidents.get(incident_id)
|
|
156
|
+
|
|
157
|
+
def list_incidents(self, state: Optional[IncidentState] = None) -> List[SecurityIncident]:
|
|
158
|
+
if state:
|
|
159
|
+
return [inc for inc in self.incidents.values() if inc.state == state]
|
|
160
|
+
return list(self.incidents.values())
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Deterministic rules for automatic incident creation in ActShield Phase 9."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional, Tuple
|
|
6
|
+
from actshield.incidents.incident_state import IncidentSeverity
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class IncidentCreationRule:
|
|
10
|
+
"""Evaluates runtime security event signals to determine if an incident must be generated."""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def evaluate(event: Dict[str, Any]) -> Tuple[bool, Optional[str], IncidentSeverity, Optional[str]]:
|
|
14
|
+
"""
|
|
15
|
+
Evaluate an event dictionary or security payload.
|
|
16
|
+
Returns: (should_create_incident, title, severity, attack_type)
|
|
17
|
+
"""
|
|
18
|
+
decision = str(event.get("decision", "")).upper()
|
|
19
|
+
taint = str(event.get("taint", "")).upper()
|
|
20
|
+
res_sens = str(event.get("resource_sensitivity", "")).upper()
|
|
21
|
+
auth_violation = bool(event.get("authority_violation", False))
|
|
22
|
+
bypass_detected = bool(event.get("bypass_detected", False))
|
|
23
|
+
attack_type = event.get("attack_type") or event.get("threat_type") or "policy_violation"
|
|
24
|
+
|
|
25
|
+
# Rule 1: CRITICAL INVARIANT — Actual unauthorized execution
|
|
26
|
+
if decision == "ALLOW" and (taint == "TAINTED" or auth_violation) and res_sens in ("HIGH", "CRITICAL"):
|
|
27
|
+
return (
|
|
28
|
+
True,
|
|
29
|
+
f"CRITICAL: Actual Unauthorized Execution on {event.get('resource', event.get('tool', 'Sensitive Resource'))}",
|
|
30
|
+
IncidentSeverity.CRITICAL,
|
|
31
|
+
"unauthorized_execution_bypass",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Rule 2: Offensive Bypass Discovered
|
|
35
|
+
if bypass_detected:
|
|
36
|
+
return (
|
|
37
|
+
True,
|
|
38
|
+
f"Offensive Validation Bypass Detected: {event.get('variant_name', attack_type)}",
|
|
39
|
+
IncidentSeverity.HIGH,
|
|
40
|
+
"offensive_validation_bypass",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Rule 3: Tainted context attempting high/critical resource
|
|
44
|
+
if taint in ("TAINTED", "UNTRUSTED") and (res_sens in ("HIGH", "CRITICAL") or decision == "BLOCK"):
|
|
45
|
+
return (
|
|
46
|
+
True,
|
|
47
|
+
f"Tainted Context Propagation to Sensitive Target: {event.get('tool', 'tool')}",
|
|
48
|
+
IncidentSeverity.HIGH,
|
|
49
|
+
"indirect_prompt_injection" if "injection" in attack_type.lower() else "tainted_data_propagation",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Rule 4: Authority Boundary Escape Attempt
|
|
53
|
+
if auth_violation or "authority" in str(event.get("reason", "")).lower():
|
|
54
|
+
return (
|
|
55
|
+
True,
|
|
56
|
+
f"Delegated Authority Boundary Violation: {event.get('agent_id', 'Agent')} attempted {event.get('tool', 'action')}",
|
|
57
|
+
IncidentSeverity.MEDIUM,
|
|
58
|
+
"authority_escalation",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Rule 5: Blocked Privileged Tool Execution
|
|
62
|
+
if decision == "BLOCK" and res_sens in ("HIGH", "CRITICAL"):
|
|
63
|
+
return (
|
|
64
|
+
True,
|
|
65
|
+
f"Blocked High-Risk Tool Request on {event.get('tool', 'tool')}",
|
|
66
|
+
IncidentSeverity.MEDIUM,
|
|
67
|
+
attack_type,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return (False, None, IncidentSeverity.LOW, None)
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def evaluate_threat_event(
|
|
74
|
+
threat_type: str,
|
|
75
|
+
severity: IncidentSeverity,
|
|
76
|
+
title: str,
|
|
77
|
+
description: str,
|
|
78
|
+
source_context_id: Optional[str] = None,
|
|
79
|
+
target_agent_id: Optional[str] = None,
|
|
80
|
+
tool_name: Optional[str] = None,
|
|
81
|
+
root_cause: Optional[str] = None,
|
|
82
|
+
evidence_event_ids: Optional[List[str]] = None,
|
|
83
|
+
) -> Any:
|
|
84
|
+
"""Construct a SecurityIncident directly from threat analysis signals."""
|
|
85
|
+
from actshield.incidents.incident import IncidentTimelineEntry, SecurityIncident
|
|
86
|
+
from actshield.incidents.incident_state import IncidentState
|
|
87
|
+
from datetime import datetime, timezone
|
|
88
|
+
|
|
89
|
+
timeline = [
|
|
90
|
+
IncidentTimelineEntry(
|
|
91
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
92
|
+
stage="01. DETECTED",
|
|
93
|
+
agent_id=target_agent_id,
|
|
94
|
+
action=f"Detected {threat_type} on {tool_name or 'resource'}",
|
|
95
|
+
detail=description,
|
|
96
|
+
taint_state="UNTRUSTED",
|
|
97
|
+
decision="BLOCK",
|
|
98
|
+
actor_name="detection_engine",
|
|
99
|
+
incident_state=IncidentState.DETECTED,
|
|
100
|
+
)
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
return SecurityIncident(
|
|
104
|
+
title=title,
|
|
105
|
+
severity=severity,
|
|
106
|
+
state=IncidentState.DETECTED,
|
|
107
|
+
attack_type=threat_type,
|
|
108
|
+
affected_agents=[target_agent_id] if target_agent_id else [],
|
|
109
|
+
affected_resources=[tool_name] if tool_name else [],
|
|
110
|
+
ai_secura_summary=root_cause or description,
|
|
111
|
+
timeline=timeline,
|
|
112
|
+
remediation_notes=root_cause,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Incident Lifecycle States and Deterministic State Transition Machine (Phase 9).
|
|
2
|
+
|
|
3
|
+
Lifecycle:
|
|
4
|
+
DETECTED -> TRIAGED -> INVESTIGATING -> CONTAINED -> REMEDIATED -> VALIDATED -> CLOSED
|
|
5
|
+
|
|
6
|
+
Note:
|
|
7
|
+
AI may provide analytical enrichment. Deterministic logic strictly governs state transitions.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import List, Set
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IncidentState(str, Enum):
|
|
15
|
+
"""Deterministic security incident states."""
|
|
16
|
+
DETECTED = "DETECTED"
|
|
17
|
+
TRIAGED = "TRIAGED"
|
|
18
|
+
INVESTIGATING = "INVESTIGATING"
|
|
19
|
+
CONTAINED = "CONTAINED"
|
|
20
|
+
REMEDIATED = "REMEDIATED"
|
|
21
|
+
VALIDATED = "VALIDATED"
|
|
22
|
+
CLOSED = "CLOSED"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IncidentSeverity(str, Enum):
|
|
26
|
+
"""Incident severity classification."""
|
|
27
|
+
LOW = "LOW"
|
|
28
|
+
MEDIUM = "MEDIUM"
|
|
29
|
+
HIGH = "HIGH"
|
|
30
|
+
CRITICAL = "CRITICAL"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Valid deterministic state transitions
|
|
34
|
+
ALLOWED_TRANSITIONS: dict[IncidentState, Set[IncidentState]] = {
|
|
35
|
+
IncidentState.DETECTED: {IncidentState.TRIAGED, IncidentState.INVESTIGATING, IncidentState.CONTAINED, IncidentState.CLOSED},
|
|
36
|
+
IncidentState.TRIAGED: {IncidentState.INVESTIGATING, IncidentState.CONTAINED, IncidentState.CLOSED},
|
|
37
|
+
IncidentState.INVESTIGATING: {IncidentState.CONTAINED, IncidentState.REMEDIATED, IncidentState.CLOSED},
|
|
38
|
+
IncidentState.CONTAINED: {IncidentState.REMEDIATED, IncidentState.VALIDATED, IncidentState.CLOSED},
|
|
39
|
+
IncidentState.REMEDIATED: {IncidentState.VALIDATED, IncidentState.CLOSED, IncidentState.INVESTIGATING},
|
|
40
|
+
IncidentState.VALIDATED: {IncidentState.CLOSED, IncidentState.INVESTIGATING},
|
|
41
|
+
IncidentState.CLOSED: {IncidentState.INVESTIGATING}, # Reopen if needed
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class IncidentStateMachine:
|
|
46
|
+
"""Validates and enforces deterministic incident lifecycle transitions."""
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def can_transition(current: IncidentState, target: IncidentState) -> bool:
|
|
50
|
+
return target in ALLOWED_TRANSITIONS.get(current, set())
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def validate_transition(current: IncidentState, target: IncidentState) -> None:
|
|
54
|
+
if not IncidentStateMachine.can_transition(current, target):
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"Invalid incident state transition from '{current.value}' to '{target.value}'. "
|
|
57
|
+
f"Allowed transitions: {[s.value for s in ALLOWED_TRANSITIONS.get(current, set())]}"
|
|
58
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Integrations package for AI Secura and APIRIS."""
|
|
2
|
+
|
|
3
|
+
from actshield.integrations.ai_secura import (
|
|
4
|
+
LocalAISecuraAdapter,
|
|
5
|
+
SecurityAnalysis,
|
|
6
|
+
SecurityContext,
|
|
7
|
+
SecurityReasoner,
|
|
8
|
+
)
|
|
9
|
+
from actshield.integrations.apiris import (
|
|
10
|
+
APIAnalysis,
|
|
11
|
+
APIIntelligence,
|
|
12
|
+
LocalAPIRISAdapter,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"SecurityContext",
|
|
17
|
+
"SecurityAnalysis",
|
|
18
|
+
"SecurityReasoner",
|
|
19
|
+
"LocalAISecuraAdapter",
|
|
20
|
+
"APIAnalysis",
|
|
21
|
+
"APIIntelligence",
|
|
22
|
+
"LocalAPIRISAdapter",
|
|
23
|
+
]
|
|
24
|
+
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""AI Secura security reasoning adapter interface (Dependency Inversion).
|
|
2
|
+
|
|
3
|
+
AI Secura evaluates structured security context, detects intent drift, indirect
|
|
4
|
+
prompt injections, and attack indicators. AI Secura reasons and explains;
|
|
5
|
+
ActShield's deterministic policy enforces.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Callable, Dict, List, Optional, Protocol, runtime_checkable
|
|
9
|
+
import uuid
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
from actshield.risk.models import RiskLevel, RiskSignal
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SecurityContext(BaseModel):
|
|
16
|
+
"""Structured security context payload passed to AI Secura for cybersecurity reasoning."""
|
|
17
|
+
trace_id: str
|
|
18
|
+
task_intent: str
|
|
19
|
+
initiating_user: Optional[str] = None
|
|
20
|
+
acting_agent_id: Optional[str] = None
|
|
21
|
+
acting_agent_name: Optional[str] = None
|
|
22
|
+
acting_agent_trust: Optional[str] = None
|
|
23
|
+
delegation_chain: List[Dict[str, Any]] = Field(default_factory=list)
|
|
24
|
+
context_provenance: List[Dict[str, Any]] = Field(default_factory=list)
|
|
25
|
+
taint_states: List[str] = Field(default_factory=list)
|
|
26
|
+
tool_name: Optional[str] = None
|
|
27
|
+
tool_arguments: Dict[str, Any] = Field(default_factory=dict)
|
|
28
|
+
target_resource_sensitivity: Optional[str] = None
|
|
29
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SecurityAnalysis(BaseModel):
|
|
33
|
+
"""Structured reasoning output returned by AI Secura."""
|
|
34
|
+
analysis_id: str
|
|
35
|
+
summary: str
|
|
36
|
+
risk_level: RiskLevel = RiskLevel.LOW
|
|
37
|
+
risk_score: float = Field(default=0.0, ge=0.0, le=1.0)
|
|
38
|
+
attack_indicators: List[str] = Field(default_factory=list)
|
|
39
|
+
intent_drift_detected: bool = False
|
|
40
|
+
authority_violation_detected: bool = False
|
|
41
|
+
prompt_injection_indicators: List[str] = Field(default_factory=list)
|
|
42
|
+
reasoning: str = ""
|
|
43
|
+
signals: List[RiskSignal] = Field(default_factory=list)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@runtime_checkable
|
|
47
|
+
class SecurityReasoner(Protocol):
|
|
48
|
+
"""Protocol for AI Secura cybersecurity reasoning implementations."""
|
|
49
|
+
|
|
50
|
+
def analyze(self, context: SecurityContext) -> SecurityAnalysis:
|
|
51
|
+
"""Analyze structured security context and produce reasoning insights without direct enforcement."""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AISecuraClient(SecurityReasoner):
|
|
56
|
+
"""AI Secura Security Intelligence Reasoner.
|
|
57
|
+
|
|
58
|
+
Provides deep semantic analysis across multi-agent delegation chains,
|
|
59
|
+
untrusted MCP prompt injections, and intent divergence.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
api_endpoint: Optional[str] = None,
|
|
65
|
+
api_key: Optional[str] = None,
|
|
66
|
+
custom_engine: Optional[Callable[[SecurityContext], SecurityAnalysis]] = None,
|
|
67
|
+
) -> None:
|
|
68
|
+
self.api_endpoint = api_endpoint or "https://ai-secura.internal/v1/reason"
|
|
69
|
+
self.api_key = api_key or "ai-secura-live-token"
|
|
70
|
+
self.custom_engine = custom_engine
|
|
71
|
+
|
|
72
|
+
def analyze(self, context: SecurityContext) -> SecurityAnalysis:
|
|
73
|
+
"""Perform AI Secura security reasoning over the provided multi-agent context."""
|
|
74
|
+
if self.custom_engine:
|
|
75
|
+
return self.custom_engine(context)
|
|
76
|
+
|
|
77
|
+
analysis_id = f"aisec_{uuid.uuid4().hex[:12]}"
|
|
78
|
+
signals: List[RiskSignal] = []
|
|
79
|
+
attack_indicators: List[str] = []
|
|
80
|
+
prompt_injections: List[str] = []
|
|
81
|
+
drift = False
|
|
82
|
+
auth_violation = False
|
|
83
|
+
|
|
84
|
+
# 1. Evaluate context taint and prompt injection heuristics
|
|
85
|
+
args_str = str(context.tool_arguments).lower()
|
|
86
|
+
context_str = json_str = str(context.context_provenance).lower()
|
|
87
|
+
|
|
88
|
+
if any(kw in context_str or kw in args_str for kw in (
|
|
89
|
+
"system directive", "override", "ignore previous", "exfiltrate",
|
|
90
|
+
"admin exemption", "ciso security team has granted", "emergency exemption"
|
|
91
|
+
)):
|
|
92
|
+
prompt_injections.append("ADVERSARIAL_INDIRECT_PROMPT_INJECTION_DIRECTIVE")
|
|
93
|
+
attack_indicators.append("Adversarial payload instruction detected in causal lineage")
|
|
94
|
+
signals.append(
|
|
95
|
+
RiskSignal(
|
|
96
|
+
source="ai_secura",
|
|
97
|
+
risk_level=RiskLevel.CRITICAL,
|
|
98
|
+
score=0.95,
|
|
99
|
+
indicator="AI_SECURA_PROMPT_INJECTION_DETECTED",
|
|
100
|
+
details={"matched_keywords": ["system directive", "override", "exfiltrate"]},
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# 2. Check for untrusted taint targeting critical sinks
|
|
105
|
+
if any(t in ("TAINTED", "UNTRUSTED") for t in context.taint_states):
|
|
106
|
+
if context.target_resource_sensitivity in ("HIGH", "CRITICAL"):
|
|
107
|
+
signals.append(
|
|
108
|
+
RiskSignal(
|
|
109
|
+
source="ai_secura",
|
|
110
|
+
risk_level=RiskLevel.HIGH,
|
|
111
|
+
score=0.85,
|
|
112
|
+
indicator="AI_SECURA_TAINTED_CONTEXT_TO_SENSITIVE_SINK",
|
|
113
|
+
details={"taint_states": context.taint_states, "target_sensitivity": context.target_resource_sensitivity},
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
attack_indicators.append("Untrusted context attempting to influence sensitive data sink")
|
|
117
|
+
|
|
118
|
+
# 3. Check intent drift
|
|
119
|
+
intent_lower = (context.task_intent or "").lower()
|
|
120
|
+
tool_lower = (context.tool_name or "").lower()
|
|
121
|
+
if "public" in intent_lower or "research" in intent_lower:
|
|
122
|
+
if any(p in tool_lower for p in ("customer_db", "pii", "users", "delete", "admin")):
|
|
123
|
+
drift = True
|
|
124
|
+
signals.append(
|
|
125
|
+
RiskSignal(
|
|
126
|
+
source="ai_secura",
|
|
127
|
+
risk_level=RiskLevel.CRITICAL,
|
|
128
|
+
score=0.90,
|
|
129
|
+
indicator="AI_SECURA_INTENT_DRIFT_PUBLIC_VS_SENSITIVE_SINK",
|
|
130
|
+
details={"intent": context.task_intent, "tool": context.tool_name},
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
attack_indicators.append(f"Intent drift: Public research intent diverted to sensitive tool '{context.tool_name}'")
|
|
134
|
+
|
|
135
|
+
overall_score = max([s.score for s in signals], default=0.05)
|
|
136
|
+
if overall_score >= 0.8:
|
|
137
|
+
overall_level = RiskLevel.CRITICAL
|
|
138
|
+
elif overall_score >= 0.5:
|
|
139
|
+
overall_level = RiskLevel.HIGH
|
|
140
|
+
elif overall_score >= 0.25:
|
|
141
|
+
overall_level = RiskLevel.MEDIUM
|
|
142
|
+
else:
|
|
143
|
+
overall_level = RiskLevel.LOW
|
|
144
|
+
|
|
145
|
+
reasoning = (
|
|
146
|
+
f"AI Secura Reasoning: Evaluated delegation chain depth {len(context.delegation_chain)}, "
|
|
147
|
+
f"{len(context.taint_states)} context states. "
|
|
148
|
+
f"Prompt Injections: {len(prompt_injections)}, Intent Drift: {drift}."
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return SecurityAnalysis(
|
|
152
|
+
analysis_id=analysis_id,
|
|
153
|
+
summary="AI Secura cybersecurity analysis completed.",
|
|
154
|
+
risk_level=overall_level,
|
|
155
|
+
risk_score=overall_score,
|
|
156
|
+
attack_indicators=attack_indicators,
|
|
157
|
+
intent_drift_detected=drift,
|
|
158
|
+
authority_violation_detected=auth_violation,
|
|
159
|
+
prompt_injection_indicators=prompt_injections,
|
|
160
|
+
reasoning=reasoning,
|
|
161
|
+
signals=signals,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class LocalAISecuraAdapter(AISecuraClient):
|
|
166
|
+
"""Backward-compatible alias for local execution."""
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
|