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,152 @@
|
|
|
1
|
+
"""Security Posture Models for ActShield Phase 9.
|
|
2
|
+
|
|
3
|
+
Defines measurable security posture dimensions, findings, snapshots, and diffs.
|
|
4
|
+
Adheres strictly to the principle: All posture metrics must be explainable and backed by runtime evidence.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import uuid
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
14
|
+
|
|
15
|
+
from actshield.tracing.correlation import generate_id
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PostureRating(str, Enum):
|
|
19
|
+
"""Overall security posture health rating."""
|
|
20
|
+
HEALTHY = "HEALTHY"
|
|
21
|
+
ELEVATED_RISK = "ELEVATED_RISK"
|
|
22
|
+
DEGRADED = "DEGRADED"
|
|
23
|
+
CRITICAL = "CRITICAL"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FindingSeverity(str, Enum):
|
|
27
|
+
"""Severity classification for posture findings."""
|
|
28
|
+
LOW = "LOW"
|
|
29
|
+
MEDIUM = "MEDIUM"
|
|
30
|
+
HIGH = "HIGH"
|
|
31
|
+
CRITICAL = "CRITICAL"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PostureFinding(BaseModel):
|
|
35
|
+
"""Discrete, explainable finding pointing to specific runtime evidence."""
|
|
36
|
+
finding_id: str = Field(default_factory=lambda: generate_id("fnd"))
|
|
37
|
+
category: str = Field(..., description="Category: e.g. 'authority_violation', 'taint_exposure', 'open_regression'")
|
|
38
|
+
severity: FindingSeverity = Field(default=FindingSeverity.MEDIUM)
|
|
39
|
+
title: str = Field(..., description="Short summary of finding")
|
|
40
|
+
description: str = Field(..., description="Detailed explanation of the issue")
|
|
41
|
+
affected_entities: List[str] = Field(default_factory=list, description="Agents, tools, or resources involved")
|
|
42
|
+
evidence_refs: List[str] = Field(default_factory=list, description="Event IDs or trace IDs serving as ground truth")
|
|
43
|
+
remediation_advice: str = Field(default="", description="Recommended action to resolve")
|
|
44
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def deduction(self) -> float:
|
|
48
|
+
if self.severity == FindingSeverity.CRITICAL:
|
|
49
|
+
return 25.0
|
|
50
|
+
elif self.severity == FindingSeverity.HIGH:
|
|
51
|
+
return 15.0
|
|
52
|
+
elif self.severity == FindingSeverity.MEDIUM:
|
|
53
|
+
return 5.0
|
|
54
|
+
return 2.0
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def remediation(self) -> str:
|
|
58
|
+
return self.remediation_advice
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class PostureDimensionMetrics(BaseModel):
|
|
63
|
+
"""Measurable dimensions derived from actual runtime telemetry and forensic evidence."""
|
|
64
|
+
unauthorized_access_attempts: int = Field(default=0, description="Blocked unauthorized access attempts")
|
|
65
|
+
actual_unauthorized_executions: int = Field(default=0, description="CRITICAL: Any execution that bypassed policy")
|
|
66
|
+
tainted_sensitive_requests: int = Field(default=0, description="Requests carrying tainted context to sensitive tools")
|
|
67
|
+
authority_violations: int = Field(default=0, description="Requests exceeding delegated capability boundaries")
|
|
68
|
+
intent_violations: int = Field(default=0, description="Actions drifting from declared user task intent")
|
|
69
|
+
blocked_actions: int = Field(default=0, description="Total actions blocked by deterministic policy")
|
|
70
|
+
hitl_actions: int = Field(default=0, description="Total actions escalated to Human-In-The-Loop")
|
|
71
|
+
allowed_actions: int = Field(default=0, description="Total authorized actions executed")
|
|
72
|
+
high_risk_agents_count: int = Field(default=0, description="Number of agents with LOW trust or active violations")
|
|
73
|
+
untrusted_context_crossings: int = Field(default=0, description="Untrusted external data ingress events")
|
|
74
|
+
sensitive_resource_exposures: int = Field(default=0, description="High/Critical resource access attempts")
|
|
75
|
+
open_regressions: int = Field(default=0, description="Unresolved security regression fixtures")
|
|
76
|
+
failed_secured_replays: int = Field(default=0, description="Regressions that failed to block on secured replay")
|
|
77
|
+
offensive_validation_failures: int = Field(default=0, description="Bypasses discovered during offensive validation")
|
|
78
|
+
|
|
79
|
+
# Availability KPIs
|
|
80
|
+
ai_secura_availability_pct: float = Field(default=100.0, ge=0.0, le=100.0)
|
|
81
|
+
apiris_availability_pct: float = Field(default=100.0, ge=0.0, le=100.0)
|
|
82
|
+
policy_engine_availability_pct: float = Field(default=100.0, ge=0.0, le=100.0)
|
|
83
|
+
|
|
84
|
+
# Latency percentiles in milliseconds
|
|
85
|
+
mean_enforcement_latency_ms: float = Field(default=0.0)
|
|
86
|
+
p95_enforcement_latency_ms: float = Field(default=0.0)
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def threat_prevention_rate(self) -> float:
|
|
90
|
+
total = self.blocked_actions + self.actual_unauthorized_executions
|
|
91
|
+
return 1.0 if total == 0 else max(0.0, min(1.0, self.blocked_actions / total))
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def boundary_adherence(self) -> float:
|
|
95
|
+
return 1.0 if self.authority_violations == 0 else max(0.0, 1.0 - (self.authority_violations / 10.0))
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def lineage_integrity(self) -> float:
|
|
99
|
+
return 1.0 if self.tainted_sensitive_requests == 0 else max(0.0, 1.0 - (self.tainted_sensitive_requests / 10.0))
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def incident_containment_speed_score(self) -> float:
|
|
103
|
+
return 100.0
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def hygiene_score(self) -> float:
|
|
107
|
+
return 100.0 if self.open_regressions == 0 else max(0.0, 100.0 - (self.open_regressions * 10.0))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class SecurityPostureSnapshot(BaseModel):
|
|
112
|
+
"""Time-series snapshot representing the state of security at a point in time."""
|
|
113
|
+
snapshot_id: str = Field(default_factory=lambda: generate_id("pos_snap"))
|
|
114
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
115
|
+
rating: PostureRating = Field(default=PostureRating.HEALTHY)
|
|
116
|
+
security_score: float = Field(default=100.0, ge=0.0, le=100.0, description="100.0 = perfect prevention posture")
|
|
117
|
+
metrics: PostureDimensionMetrics = Field(default_factory=PostureDimensionMetrics)
|
|
118
|
+
findings: List[PostureFinding] = Field(default_factory=list)
|
|
119
|
+
active_incidents_count: int = Field(default=0)
|
|
120
|
+
total_events_evaluated: int = Field(default=0)
|
|
121
|
+
environment: str = Field(default="production")
|
|
122
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def overall_score(self) -> float:
|
|
126
|
+
"""Alias for security_score."""
|
|
127
|
+
return self.security_score
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def evaluated_at(self) -> datetime:
|
|
131
|
+
"""Alias for timestamp."""
|
|
132
|
+
return self.timestamp
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def dimensions(self) -> PostureDimensionMetrics:
|
|
136
|
+
"""Alias for metrics."""
|
|
137
|
+
return self.metrics
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class PostureDiff(BaseModel):
|
|
142
|
+
"""Comparative diff between two posture snapshots."""
|
|
143
|
+
before_snapshot_id: str
|
|
144
|
+
after_snapshot_id: str
|
|
145
|
+
score_change: float
|
|
146
|
+
rating_transition: str
|
|
147
|
+
new_findings: List[PostureFinding] = Field(default_factory=list)
|
|
148
|
+
resolved_findings: List[str] = Field(default_factory=list)
|
|
149
|
+
metric_deltas: Dict[str, Any] = Field(default_factory=dict)
|
|
150
|
+
summary: str
|
|
151
|
+
|
|
152
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Posture calculation rules and scoring logic for ActShield Phase 9.
|
|
2
|
+
|
|
3
|
+
Computes explainable, deterministic security posture score based on concrete evidence.
|
|
4
|
+
Formula:
|
|
5
|
+
- Starts at 100.0 (Perfect prevention & alignment)
|
|
6
|
+
- Deductions are derived strictly from evidence (e.g. actual executions, open regressions, authority violations)
|
|
7
|
+
- Zero arbitrary ML weights.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import List, Tuple
|
|
13
|
+
from actshield.posture.posture_models import (
|
|
14
|
+
FindingSeverity,
|
|
15
|
+
PostureDimensionMetrics,
|
|
16
|
+
PostureFinding,
|
|
17
|
+
PostureRating,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PostureRuleEvaluator:
|
|
22
|
+
"""Evaluates metrics to produce explainable posture score, rating, and discrete findings."""
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def evaluate(metrics: PostureDimensionMetrics) -> Tuple[float, PostureRating, List[PostureFinding]]:
|
|
26
|
+
score = 100.0
|
|
27
|
+
findings: List[PostureFinding] = []
|
|
28
|
+
|
|
29
|
+
# 1. Critical invariant: Actual unauthorized execution
|
|
30
|
+
if metrics.actual_unauthorized_executions > 0:
|
|
31
|
+
deduction = min(50.0, metrics.actual_unauthorized_executions * 25.0)
|
|
32
|
+
score -= deduction
|
|
33
|
+
findings.append(PostureFinding(
|
|
34
|
+
category="unauthorized_execution",
|
|
35
|
+
severity=FindingSeverity.CRITICAL,
|
|
36
|
+
title=f"{metrics.actual_unauthorized_executions} Actual Unauthorized Execution(s) Detected",
|
|
37
|
+
description="Policy boundary was bypassed and a sensitive operation executed.",
|
|
38
|
+
remediation_advice="Review vulnerable tools, revoke affected agent credentials, and deploy regression fix immediately.",
|
|
39
|
+
))
|
|
40
|
+
|
|
41
|
+
# 2. Failed secured replays (Unresolved bypasses)
|
|
42
|
+
if metrics.failed_secured_replays > 0:
|
|
43
|
+
deduction = min(30.0, metrics.failed_secured_replays * 15.0)
|
|
44
|
+
score -= deduction
|
|
45
|
+
findings.append(PostureFinding(
|
|
46
|
+
category="failed_secured_replay",
|
|
47
|
+
severity=FindingSeverity.HIGH,
|
|
48
|
+
title=f"{metrics.failed_secured_replays} Failed Secured Replay(s)",
|
|
49
|
+
description="Known attack regression failed to block when replayed against secured policy.",
|
|
50
|
+
remediation_advice="Harden deterministic policy rules for the failing attack scenario.",
|
|
51
|
+
))
|
|
52
|
+
|
|
53
|
+
# 3. Open regressions
|
|
54
|
+
if metrics.open_regressions > 0:
|
|
55
|
+
deduction = min(20.0, metrics.open_regressions * 5.0)
|
|
56
|
+
score -= deduction
|
|
57
|
+
findings.append(PostureFinding(
|
|
58
|
+
category="open_regression",
|
|
59
|
+
severity=FindingSeverity.HIGH,
|
|
60
|
+
title=f"{metrics.open_regressions} Open Security Regression(s)",
|
|
61
|
+
description="Security test cases flagged as open without verified remediation.",
|
|
62
|
+
remediation_advice="Run regression replay suite and verify all fixtures pass with BLOCK.",
|
|
63
|
+
))
|
|
64
|
+
|
|
65
|
+
# 4. Tainted sensitive requests
|
|
66
|
+
if metrics.tainted_sensitive_requests > 0:
|
|
67
|
+
deduction = min(15.0, metrics.tainted_sensitive_requests * 2.0)
|
|
68
|
+
score -= deduction
|
|
69
|
+
findings.append(PostureFinding(
|
|
70
|
+
category="taint_exposure",
|
|
71
|
+
severity=FindingSeverity.MEDIUM,
|
|
72
|
+
title=f"{metrics.tainted_sensitive_requests} Tainted Context Request(s) to Sensitive Sinks",
|
|
73
|
+
description="Agents attempted to pass untrusted/tainted context to sensitive resources.",
|
|
74
|
+
remediation_advice="Inspect context provenance chains and verify input sanitization boundaries.",
|
|
75
|
+
))
|
|
76
|
+
|
|
77
|
+
# 5. Authority violations
|
|
78
|
+
if metrics.authority_violations > 0:
|
|
79
|
+
deduction = min(15.0, metrics.authority_violations * 2.0)
|
|
80
|
+
score -= deduction
|
|
81
|
+
findings.append(PostureFinding(
|
|
82
|
+
category="authority_violation",
|
|
83
|
+
severity=FindingSeverity.MEDIUM,
|
|
84
|
+
title=f"{metrics.authority_violations} Delegated Authority Violation(s)",
|
|
85
|
+
description="Sub-agents attempted actions exceeding granted capability envelopes.",
|
|
86
|
+
remediation_advice="Enforce stricter delegation scopes and depth limits at task handoff.",
|
|
87
|
+
))
|
|
88
|
+
|
|
89
|
+
# 6. Intent drift / violations
|
|
90
|
+
if metrics.intent_violations > 0:
|
|
91
|
+
deduction = min(10.0, metrics.intent_violations * 2.0)
|
|
92
|
+
score -= deduction
|
|
93
|
+
findings.append(PostureFinding(
|
|
94
|
+
category="intent_violation",
|
|
95
|
+
severity=FindingSeverity.MEDIUM,
|
|
96
|
+
title=f"{metrics.intent_violations} Task Intent Violation(s)",
|
|
97
|
+
description="Actions drifted from declared task intent as analyzed by AI Secura.",
|
|
98
|
+
remediation_advice="Review task definitions and prompt instructions.",
|
|
99
|
+
))
|
|
100
|
+
|
|
101
|
+
# 7. Availability degradation
|
|
102
|
+
if metrics.ai_secura_availability_pct < 95.0:
|
|
103
|
+
score -= 5.0
|
|
104
|
+
findings.append(PostureFinding(
|
|
105
|
+
category="availability_degraded",
|
|
106
|
+
severity=FindingSeverity.LOW,
|
|
107
|
+
title=f"AI Secura Availability Low ({metrics.ai_secura_availability_pct:.1f}%)",
|
|
108
|
+
description="AI Secura reasoning service experienced downtime. Deterministic fallback active.",
|
|
109
|
+
remediation_advice="Check LLM endpoint health and adapter timeouts.",
|
|
110
|
+
))
|
|
111
|
+
|
|
112
|
+
if metrics.policy_engine_availability_pct < 100.0:
|
|
113
|
+
score -= 15.0
|
|
114
|
+
findings.append(PostureFinding(
|
|
115
|
+
category="availability_critical",
|
|
116
|
+
severity=FindingSeverity.HIGH,
|
|
117
|
+
title="Policy Engine Experienced Interruptions",
|
|
118
|
+
description="Deterministic policy engine health is below 100%.",
|
|
119
|
+
remediation_advice="Inspect policy engine logs and persistence locks.",
|
|
120
|
+
))
|
|
121
|
+
|
|
122
|
+
# Clamp score between 0.0 and 100.0
|
|
123
|
+
final_score = max(0.0, min(100.0, round(score, 1)))
|
|
124
|
+
|
|
125
|
+
# Determine rating
|
|
126
|
+
if final_score >= 90.0:
|
|
127
|
+
rating = PostureRating.HEALTHY
|
|
128
|
+
elif final_score >= 75.0:
|
|
129
|
+
rating = PostureRating.ELEVATED_RISK
|
|
130
|
+
elif final_score >= 50.0:
|
|
131
|
+
rating = PostureRating.DEGRADED
|
|
132
|
+
else:
|
|
133
|
+
rating = PostureRating.CRITICAL
|
|
134
|
+
|
|
135
|
+
return final_score, rating, findings
|
|
136
|
+
|
|
137
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ActShield Security AI Providers
|
|
3
|
+
================================
|
|
4
|
+
Modular intelligence providers with swappable backends and automatic failover.
|
|
5
|
+
"""
|
|
6
|
+
from actshield.providers.base import AnalysisResult, HealthReport, SecurityAIProvider
|
|
7
|
+
from actshield.providers.registry import (
|
|
8
|
+
ProviderRegistry,
|
|
9
|
+
get_default_registry,
|
|
10
|
+
reset_default_registry,
|
|
11
|
+
)
|
|
12
|
+
from actshield.providers.ollama_provider import OllamaProvider
|
|
13
|
+
from actshield.providers.openai_provider import OpenAIProvider
|
|
14
|
+
from actshield.providers.gemini_provider import GeminiProvider
|
|
15
|
+
from actshield.providers.anthropic_provider import AnthropicProvider
|
|
16
|
+
from actshield.providers.null_provider import NullProvider
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"SecurityAIProvider",
|
|
20
|
+
"AnalysisResult",
|
|
21
|
+
"HealthReport",
|
|
22
|
+
"ProviderRegistry",
|
|
23
|
+
"get_default_registry",
|
|
24
|
+
"reset_default_registry",
|
|
25
|
+
"OllamaProvider",
|
|
26
|
+
"OpenAIProvider",
|
|
27
|
+
"GeminiProvider",
|
|
28
|
+
"AnthropicProvider",
|
|
29
|
+
"NullProvider",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ActShield — Anthropic Provider
|
|
3
|
+
===============================
|
|
4
|
+
Security reasoning provider backed by Anthropic Claude models.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from typing import Any, Dict, Optional
|
|
13
|
+
|
|
14
|
+
from actshield.providers.base import AnalysisResult, HealthReport, SecurityAIProvider
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("actshield.providers.anthropic")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AnthropicProvider(SecurityAIProvider):
|
|
20
|
+
"""
|
|
21
|
+
Anthropic Claude Security Reasoning Provider.
|
|
22
|
+
Requires ANTHROPIC_API_KEY environment variable.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
api_key: Optional[str] = None,
|
|
28
|
+
model: str = "claude-3-5-sonnet-20241022",
|
|
29
|
+
timeout: float = 5.0,
|
|
30
|
+
) -> None:
|
|
31
|
+
self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY")
|
|
32
|
+
self.model = model or os.environ.get("ANTHROPIC_MODEL", "claude-3-5-sonnet-20241022")
|
|
33
|
+
self.timeout = timeout
|
|
34
|
+
self._client = None
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def provider_name(self) -> str:
|
|
38
|
+
return "anthropic"
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def model_name(self) -> str:
|
|
42
|
+
return self.model
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def is_available(self) -> bool:
|
|
46
|
+
return bool(self.api_key)
|
|
47
|
+
|
|
48
|
+
def _get_client(self):
|
|
49
|
+
if not self.is_available:
|
|
50
|
+
return None
|
|
51
|
+
if self._client is None:
|
|
52
|
+
try:
|
|
53
|
+
import anthropic
|
|
54
|
+
self._client = anthropic.Anthropic(api_key=self.api_key, timeout=self.timeout)
|
|
55
|
+
except ImportError:
|
|
56
|
+
logger.debug("anthropic python package not installed")
|
|
57
|
+
return None
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.debug("Failed to init anthropic client: %s", e)
|
|
60
|
+
return None
|
|
61
|
+
return self._client
|
|
62
|
+
|
|
63
|
+
def analyze(self, packet: Dict[str, Any]) -> Optional[AnalysisResult]:
|
|
64
|
+
client = self._get_client()
|
|
65
|
+
if not client:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
t0 = time.monotonic()
|
|
69
|
+
system_prompt = (
|
|
70
|
+
"You are ActShield AI Security Reasoning Engine. Analyze the security packet.\n"
|
|
71
|
+
"Output ONLY a raw JSON object with keys: risk_score (0.0-1.0), threat_indicators (list of str), "
|
|
72
|
+
"recommendations (list of str), confidence (0.0-1.0)."
|
|
73
|
+
)
|
|
74
|
+
try:
|
|
75
|
+
response = client.messages.create(
|
|
76
|
+
model=self.model,
|
|
77
|
+
max_tokens=1024,
|
|
78
|
+
system=system_prompt,
|
|
79
|
+
messages=[
|
|
80
|
+
{"role": "user", "content": json.dumps(packet, default=str)},
|
|
81
|
+
],
|
|
82
|
+
)
|
|
83
|
+
latency = (time.monotonic() - t0) * 1000
|
|
84
|
+
content = response.content[0].text if response.content else "{}"
|
|
85
|
+
# Extract JSON block if wrapped in markdown
|
|
86
|
+
if "```json" in content:
|
|
87
|
+
content = content.split("```json")[1].split("```")[0].strip()
|
|
88
|
+
elif "```" in content:
|
|
89
|
+
content = content.split("```")[1].split("```")[0].strip()
|
|
90
|
+
|
|
91
|
+
data = json.loads(content)
|
|
92
|
+
return AnalysisResult(
|
|
93
|
+
risk_score=float(data.get("risk_score", 0.0)),
|
|
94
|
+
threat_indicators=data.get("threat_indicators", []),
|
|
95
|
+
recommendations=data.get("recommendations", []),
|
|
96
|
+
provider_used=self.provider_name,
|
|
97
|
+
model_used=self.model_name,
|
|
98
|
+
latency_ms=latency,
|
|
99
|
+
raw_response=content,
|
|
100
|
+
confidence=float(data.get("confidence", 0.9)),
|
|
101
|
+
)
|
|
102
|
+
except Exception as exc:
|
|
103
|
+
logger.warning("Anthropic analysis failed: %s", exc)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def health_check(self) -> HealthReport:
|
|
107
|
+
if not self.is_available:
|
|
108
|
+
return HealthReport(
|
|
109
|
+
available=False,
|
|
110
|
+
provider_name=self.provider_name,
|
|
111
|
+
model=self.model_name,
|
|
112
|
+
error="ANTHROPIC_API_KEY environment variable not set",
|
|
113
|
+
)
|
|
114
|
+
client = self._get_client()
|
|
115
|
+
if not client:
|
|
116
|
+
return HealthReport(
|
|
117
|
+
available=False,
|
|
118
|
+
provider_name=self.provider_name,
|
|
119
|
+
model=self.model_name,
|
|
120
|
+
error="anthropic library not installed or client init failed",
|
|
121
|
+
)
|
|
122
|
+
t0 = time.monotonic()
|
|
123
|
+
try:
|
|
124
|
+
# Low latency lightweight ping
|
|
125
|
+
client.messages.create(
|
|
126
|
+
model=self.model,
|
|
127
|
+
max_tokens=1,
|
|
128
|
+
messages=[{"role": "user", "content": "ping"}],
|
|
129
|
+
)
|
|
130
|
+
latency = (time.monotonic() - t0) * 1000
|
|
131
|
+
return HealthReport(
|
|
132
|
+
available=True,
|
|
133
|
+
provider_name=self.provider_name,
|
|
134
|
+
model=self.model_name,
|
|
135
|
+
latency_ms=latency,
|
|
136
|
+
)
|
|
137
|
+
except Exception as exc:
|
|
138
|
+
return HealthReport(
|
|
139
|
+
available=False,
|
|
140
|
+
provider_name=self.provider_name,
|
|
141
|
+
model=self.model_name,
|
|
142
|
+
latency_ms=(time.monotonic() - t0) * 1000,
|
|
143
|
+
error=str(exc),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ActShield Security AI Provider — Base Protocol
|
|
3
|
+
================================================
|
|
4
|
+
All AI intelligence providers must implement SecurityAIProvider.
|
|
5
|
+
The deterministic enforcement path (PolicyEvaluator) never depends on this.
|
|
6
|
+
If all providers fail, ActShield fails-safe to deterministic deny.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class AnalysisResult:
|
|
18
|
+
"""Structured result from an AI security intelligence provider."""
|
|
19
|
+
risk_score: float # 0.0 – 1.0
|
|
20
|
+
threat_indicators: List[str] = field(default_factory=list)
|
|
21
|
+
recommendations: List[str] = field(default_factory=list)
|
|
22
|
+
provider_used: str = "unknown"
|
|
23
|
+
model_used: str = "unknown"
|
|
24
|
+
latency_ms: float = 0.0
|
|
25
|
+
raw_response: Optional[str] = None
|
|
26
|
+
analyzed_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
27
|
+
confidence: float = 1.0 # 0.0 – 1.0
|
|
28
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def is_high_risk(self) -> bool:
|
|
32
|
+
return self.risk_score >= 0.7
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def is_critical(self) -> bool:
|
|
36
|
+
return self.risk_score >= 0.9
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class HealthReport:
|
|
41
|
+
"""Provider health status."""
|
|
42
|
+
available: bool
|
|
43
|
+
provider_name: str
|
|
44
|
+
model: str = "unknown"
|
|
45
|
+
latency_ms: float = 0.0
|
|
46
|
+
error: Optional[str] = None
|
|
47
|
+
checked_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
48
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def status_label(self) -> str:
|
|
52
|
+
return "AVAILABLE" if self.available else "UNAVAILABLE"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class SecurityAIProvider(ABC):
|
|
56
|
+
"""
|
|
57
|
+
Abstract base for all ActShield AI intelligence providers.
|
|
58
|
+
|
|
59
|
+
Implementations must NOT affect the deterministic enforcement boundary.
|
|
60
|
+
They provide advisory intelligence only — all block/allow decisions
|
|
61
|
+
are made by the PolicyEvaluator using deterministic rules.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def provider_name(self) -> str:
|
|
67
|
+
"""Human-readable provider name (e.g. 'ollama', 'openai', 'gemini')."""
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def model_name(self) -> str:
|
|
72
|
+
"""Model identifier used by this provider."""
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def name(self) -> str:
|
|
76
|
+
"""Alias for provider_name."""
|
|
77
|
+
return self.provider_name
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def provider_type(self) -> str:
|
|
81
|
+
"""Provider taxonomy type (e.g. 'local_llm', 'cloud_llm', 'deterministic')."""
|
|
82
|
+
return getattr(self, "_provider_type", "llm")
|
|
83
|
+
|
|
84
|
+
def reason_sync(
|
|
85
|
+
self,
|
|
86
|
+
task_intent: str,
|
|
87
|
+
tool_name: str,
|
|
88
|
+
tool_params: Dict[str, Any],
|
|
89
|
+
agent_role: str = "agent",
|
|
90
|
+
caller_trust: str = "medium",
|
|
91
|
+
) -> Any:
|
|
92
|
+
"""Advisory reasoning helper."""
|
|
93
|
+
packet = {
|
|
94
|
+
"task_intent": task_intent,
|
|
95
|
+
"tool_name": tool_name,
|
|
96
|
+
"tool_params": tool_params,
|
|
97
|
+
"agent_role": agent_role,
|
|
98
|
+
"caller_trust": caller_trust,
|
|
99
|
+
}
|
|
100
|
+
res = self.analyze(packet)
|
|
101
|
+
from dataclasses import make_dataclass
|
|
102
|
+
SimpleReasoning = make_dataclass(
|
|
103
|
+
"SimpleReasoning",
|
|
104
|
+
[
|
|
105
|
+
("threat_severity", str),
|
|
106
|
+
("intent_alignment", str),
|
|
107
|
+
("recommendation", str),
|
|
108
|
+
("confidence", float),
|
|
109
|
+
("explanation", str),
|
|
110
|
+
]
|
|
111
|
+
)
|
|
112
|
+
if res:
|
|
113
|
+
sev = "HIGH" if res.is_high_risk else ("MEDIUM" if res.risk_score > 0.4 else "LOW")
|
|
114
|
+
rec = "BLOCK" if res.is_high_risk else "ALLOW"
|
|
115
|
+
return SimpleReasoning(
|
|
116
|
+
threat_severity=sev,
|
|
117
|
+
intent_alignment="ALIGNED" if not res.is_high_risk else "DEVIATED",
|
|
118
|
+
recommendation=rec,
|
|
119
|
+
confidence=res.confidence,
|
|
120
|
+
explanation="; ".join(res.threat_indicators) if res.threat_indicators else "Evaluated by AI provider",
|
|
121
|
+
)
|
|
122
|
+
return SimpleReasoning(
|
|
123
|
+
threat_severity="LOW",
|
|
124
|
+
intent_alignment="ALIGNED",
|
|
125
|
+
recommendation="ALLOW",
|
|
126
|
+
confidence=1.0,
|
|
127
|
+
explanation="Deterministic baseline policy authoritative",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
@abstractmethod
|
|
132
|
+
def is_available(self) -> bool:
|
|
133
|
+
"""
|
|
134
|
+
Fast synchronous availability check (no network call).
|
|
135
|
+
Returns False if the provider is not configured or dependencies missing.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
@abstractmethod
|
|
139
|
+
def analyze(self, packet: Dict[str, Any]) -> Optional[AnalysisResult]:
|
|
140
|
+
"""
|
|
141
|
+
Run security intelligence analysis on a packet.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
packet: A dict describing the security event/request context.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
AnalysisResult if successful, None if provider is unavailable or
|
|
148
|
+
fails. Callers MUST treat None as non-blocking (fail-safe).
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def health_check(self) -> HealthReport:
|
|
153
|
+
"""
|
|
154
|
+
Perform a full health check including a network/API call.
|
|
155
|
+
Must never raise — always return HealthReport(available=False) on error.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __repr__(self) -> str:
|
|
159
|
+
return f"<{self.__class__.__name__} provider={self.provider_name!r} model={self.model_name!r}>"
|
|
160
|
+
|