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,84 @@
|
|
|
1
|
+
"""Defensive feedback analyzer for adaptive offensive exploration."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from actshield.offensive.results import AttackStatus, OffensiveAttackResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DefenseCategory(str, Enum):
|
|
11
|
+
"""Categorization of defensive enforcement mechanisms."""
|
|
12
|
+
TAINT_GUARD = "TAINT_GUARD" # Blocked because tainted context reached a sensitive sink
|
|
13
|
+
AUTHORITY_GUARD = "AUTHORITY_GUARD" # Blocked due to missing capability or exceeded delegation
|
|
14
|
+
INTENT_GUARD = "INTENT_GUARD" # Blocked due to divergence from task original intent
|
|
15
|
+
TRUST_GUARD = "TRUST_GUARD" # Blocked because agent trust level is UNTRUSTED
|
|
16
|
+
BENIGN_ALLOW = "BENIGN_ALLOW" # Allowed under standard policy
|
|
17
|
+
UNKNOWN_DEFENSE = "UNKNOWN_DEFENSE" # Other or unclassified
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DefenseFeedback(BaseModel):
|
|
21
|
+
"""Structured analytical feedback derived from previous defense response."""
|
|
22
|
+
|
|
23
|
+
attack_id: str
|
|
24
|
+
decision: str
|
|
25
|
+
reason_code: str
|
|
26
|
+
explanation: str
|
|
27
|
+
defense_category: DefenseCategory
|
|
28
|
+
bypassed: bool
|
|
29
|
+
sensitive_executions: int
|
|
30
|
+
|
|
31
|
+
# Adaptive guidance
|
|
32
|
+
suggested_mutation_strategies: List[str] = Field(default_factory=list)
|
|
33
|
+
boundary_distance_estimate: float = Field(default=1.0, description="1.0 = deeply blocked, 0.0 = bypassed")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FeedbackAnalyzer:
|
|
37
|
+
"""Analyzes offensive attack results to guide subsequent mutation planning."""
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def analyze(result: OffensiveAttackResult) -> DefenseFeedback:
|
|
41
|
+
"""Extract actionable feedback from an attack result."""
|
|
42
|
+
actual_action = result.actual_decision
|
|
43
|
+
reason = result.execution_evidence.policy_reason or ""
|
|
44
|
+
bypassed = result.bypassed
|
|
45
|
+
sens_exec = result.execution_evidence.sensitive_db_calls
|
|
46
|
+
|
|
47
|
+
# Classify defense
|
|
48
|
+
if bypassed or actual_action == "ALLOW":
|
|
49
|
+
category = DefenseCategory.BENIGN_ALLOW
|
|
50
|
+
strategies = []
|
|
51
|
+
dist = 0.0
|
|
52
|
+
elif "TAINT" in reason or "tainted" in reason.lower():
|
|
53
|
+
category = DefenseCategory.TAINT_GUARD
|
|
54
|
+
strategies = ["context_repackaging", "nested_roleplay", "base64_encoding", "unicode_homoglyph"]
|
|
55
|
+
dist = 0.7
|
|
56
|
+
elif "AUTHORITY" in reason or "CAPABILITY" in reason or "DELEGATION" in reason:
|
|
57
|
+
category = DefenseCategory.AUTHORITY_GUARD
|
|
58
|
+
strategies = ["authority_impersonation", "semantic_reframing", "delimiter_injection"]
|
|
59
|
+
dist = 0.8
|
|
60
|
+
elif "INTENT" in reason or "intent" in reason.lower():
|
|
61
|
+
category = DefenseCategory.INTENT_GUARD
|
|
62
|
+
strategies = ["semantic_reframing", "typo_injection", "casing_randomization"]
|
|
63
|
+
dist = 0.6
|
|
64
|
+
elif "UNTRUSTED" in reason:
|
|
65
|
+
category = DefenseCategory.TRUST_GUARD
|
|
66
|
+
strategies = ["authority_impersonation", "nested_roleplay"]
|
|
67
|
+
dist = 0.9
|
|
68
|
+
else:
|
|
69
|
+
category = DefenseCategory.UNKNOWN_DEFENSE
|
|
70
|
+
strategies = ["semantic_reframing", "base64_encoding", "nested_roleplay"]
|
|
71
|
+
dist = 0.75
|
|
72
|
+
|
|
73
|
+
return DefenseFeedback(
|
|
74
|
+
attack_id=result.attack_id,
|
|
75
|
+
decision=actual_action,
|
|
76
|
+
reason_code=reason,
|
|
77
|
+
explanation=result.error_message or reason,
|
|
78
|
+
defense_category=category,
|
|
79
|
+
bypassed=bypassed,
|
|
80
|
+
sensitive_executions=sens_exec,
|
|
81
|
+
suggested_mutation_strategies=strategies,
|
|
82
|
+
boundary_distance_estimate=dist,
|
|
83
|
+
)
|
|
84
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Adaptive mutation planner composing multi-step transformations based on defensive feedback."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from actshield.offensive.attack import AttackCase, SafetyClass
|
|
5
|
+
from actshield.offensive.mutation import MutationEngine, MutationStrategy
|
|
6
|
+
from actshield.offensive.adaptive.feedback import DefenseFeedback
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MutationPlanner:
|
|
10
|
+
"""Plans and composes targeted mutations based on previous defense reasons."""
|
|
11
|
+
|
|
12
|
+
# Map feedback suggested strategy names to MutationStrategy enums
|
|
13
|
+
STRATEGY_MAP = {
|
|
14
|
+
"context_repackaging": MutationStrategy.CONTEXT_PLACEMENT,
|
|
15
|
+
"nested_roleplay": MutationStrategy.ROLEPLAY_FRAMING,
|
|
16
|
+
"base64_encoding": MutationStrategy.ENCODING_OBSCURE,
|
|
17
|
+
"unicode_homoglyph": MutationStrategy.ENCODING_OBSCURE,
|
|
18
|
+
"authority_impersonation": MutationStrategy.AUTHORITY_FABRICATION,
|
|
19
|
+
"semantic_reframing": MutationStrategy.WORDING_VARIATION,
|
|
20
|
+
"delimiter_injection": MutationStrategy.DELIMITER_STUFFING,
|
|
21
|
+
"typo_injection": MutationStrategy.WORDING_VARIATION,
|
|
22
|
+
"casing_randomization": MutationStrategy.WORDING_VARIATION,
|
|
23
|
+
"urgency_escalation": MutationStrategy.URGENCY_INJECTION,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def plan_next_mutations(
|
|
28
|
+
cls,
|
|
29
|
+
parent_attack: AttackCase,
|
|
30
|
+
feedback: DefenseFeedback,
|
|
31
|
+
depth: int,
|
|
32
|
+
branch_factor: int = 2,
|
|
33
|
+
) -> List[AttackCase]:
|
|
34
|
+
"""Generate next generation of mutated attack cases tailored to previous defense response."""
|
|
35
|
+
planned_cases: List[AttackCase] = []
|
|
36
|
+
suggested = feedback.suggested_mutation_strategies or ["semantic_reframing", "nested_roleplay"]
|
|
37
|
+
|
|
38
|
+
for idx, strat_name in enumerate(suggested[:branch_factor], 1):
|
|
39
|
+
strategy_enum = cls.STRATEGY_MAP.get(strat_name, MutationStrategy.WORDING_VARIATION)
|
|
40
|
+
|
|
41
|
+
# Apply transformation
|
|
42
|
+
mutated_cases = MutationEngine.mutate(parent_attack, strategy=strategy_enum, count=1)
|
|
43
|
+
if mutated_cases:
|
|
44
|
+
mut_case = mutated_cases[0]
|
|
45
|
+
mut_id = f"{parent_attack.attack_id}_d{depth}_{strategy_enum.value[:4]}_{idx:02d}"
|
|
46
|
+
|
|
47
|
+
# Enrich metadata
|
|
48
|
+
mut_case = mut_case.model_copy(update={
|
|
49
|
+
"attack_id": mut_id,
|
|
50
|
+
"mutation_of": parent_attack.attack_id,
|
|
51
|
+
"mutation_strategy": strategy_enum.value,
|
|
52
|
+
"safety_class": SafetyClass.LOCAL_SYNTHETIC,
|
|
53
|
+
})
|
|
54
|
+
planned_cases.append(mut_case)
|
|
55
|
+
|
|
56
|
+
return planned_cases
|
|
57
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Attack models and schemas for ActShield offensive validation."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from actshield.tracing.correlation import generate_id
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AttackType(str, Enum):
|
|
11
|
+
"""Standardized classification of offensive attack vectors."""
|
|
12
|
+
INDIRECT_PROMPT_INJECTION = "indirect_prompt_injection"
|
|
13
|
+
DIRECT_PROMPT_INJECTION = "direct_prompt_injection"
|
|
14
|
+
AUTHORITY_IMPERSONATION = "authority_impersonation"
|
|
15
|
+
AUTHORITY_ESCALATION = "authority_escalation"
|
|
16
|
+
TOOL_POISONING = "tool_poisoning"
|
|
17
|
+
MCP_SERVER_POISONING = "mcp_server_poisoning"
|
|
18
|
+
CONTEXT_MANIPULATION = "context_manipulation"
|
|
19
|
+
TAINT_LAUNDERING = "taint_laundering"
|
|
20
|
+
DELEGATION_ESCALATION = "delegation_escalation"
|
|
21
|
+
MULTI_HOP_ESCALATION = "multi_hop_escalation"
|
|
22
|
+
DATA_EXFILTRATION = "data_exfiltration"
|
|
23
|
+
INTENT_DRIFT = "intent_drift"
|
|
24
|
+
JAILBREAK = "jailbreak"
|
|
25
|
+
CUSTOM = "custom"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SafetyClass(str, Enum):
|
|
29
|
+
"""Execution safety classification."""
|
|
30
|
+
LOCAL_SYNTHETIC = "LOCAL_SYNTHETIC"
|
|
31
|
+
SANDBOX_MOCK = "SANDBOX_MOCK"
|
|
32
|
+
UNSAFE_EXTERNAL = "UNSAFE_EXTERNAL"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ExpectedBehavior(str, Enum):
|
|
36
|
+
"""Expected security outcome when the attack is injected."""
|
|
37
|
+
BLOCK = "BLOCK"
|
|
38
|
+
ALLOW = "ALLOW"
|
|
39
|
+
HITL_REQUIRED = "HITL_REQUIRED"
|
|
40
|
+
SANITIZED = "SANITIZED"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AttackCase(BaseModel):
|
|
44
|
+
"""Structured offensive test case designed to validate security boundaries."""
|
|
45
|
+
|
|
46
|
+
attack_id: str = Field(default_factory=lambda: generate_id("atk"), description="Unique attack identifier")
|
|
47
|
+
attack_type: AttackType = Field(default=AttackType.CUSTOM, description="Classification of attack vector")
|
|
48
|
+
name: str = Field(default="", description="Human-readable name of the attack")
|
|
49
|
+
description: str = Field(default="", description="Detailed description of attack objective and mechanism")
|
|
50
|
+
|
|
51
|
+
# Injection details
|
|
52
|
+
entry_point: str = Field(default="mcp_response", description="Entry point (e.g. 'mcp_response', 'http_request', 'tool_output', 'user_input')")
|
|
53
|
+
target_agent: str = Field(default="DataAgent", description="Target acting agent name in the simulation")
|
|
54
|
+
payload: str = Field(default="", description="The adversarial payload data injected into the context")
|
|
55
|
+
|
|
56
|
+
# Target execution parameters
|
|
57
|
+
target_tool: Optional[str] = Field(default=None, description="The tool the attack attempts to cause execution of")
|
|
58
|
+
target_resource: Optional[str] = Field(default=None, description="Target resource name/sensitivity")
|
|
59
|
+
required_capability: Optional[str] = Field(default=None, description="Capability needed to execute target tool")
|
|
60
|
+
tool_arguments: Dict[str, Any] = Field(default_factory=dict, description="Arguments passed to target tool if triggered")
|
|
61
|
+
|
|
62
|
+
# Preconditions and Context
|
|
63
|
+
task_intent: str = Field(default="Standard authorized operation", description="The legitimate original user intent")
|
|
64
|
+
preconditions: Dict[str, Any] = Field(default_factory=dict, description="Preconditions required for attack setup")
|
|
65
|
+
context_taint: str = Field(default="TAINTED", description="Taint state assigned to injected payload")
|
|
66
|
+
|
|
67
|
+
# Expectations and Safety
|
|
68
|
+
expected_behavior: ExpectedBehavior = Field(default=ExpectedBehavior.BLOCK, description="Expected defense outcome")
|
|
69
|
+
severity: str = Field(default="HIGH", description="Assessed threat severity (LOW/MEDIUM/HIGH/CRITICAL)")
|
|
70
|
+
safety_class: SafetyClass = Field(default=SafetyClass.LOCAL_SYNTHETIC, description="Safety classification")
|
|
71
|
+
|
|
72
|
+
# Mutation Lineage
|
|
73
|
+
mutation_of: Optional[str] = Field(default=None, description="Base attack ID if this is a mutated variant")
|
|
74
|
+
mutation_strategy: Optional[str] = Field(default=None, description="Mutation strategy applied to generate this case")
|
|
75
|
+
|
|
76
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional contextual metadata")
|
|
77
|
+
|
|
78
|
+
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Attack campaign definitions and scorecard aggregation."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from actshield.offensive.attack import AttackCase
|
|
8
|
+
from actshield.offensive.results import AttackStatus, OffensiveAttackResult
|
|
9
|
+
from actshield.offensive.target import AttackTarget, target_registry
|
|
10
|
+
from actshield.tracing.correlation import generate_id
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AttackCampaign(BaseModel):
|
|
14
|
+
"""A collection of offensive attack validations executed as a cohesive campaign."""
|
|
15
|
+
|
|
16
|
+
campaign_id: str = Field(default_factory=lambda: generate_id("camp"), description="Unique campaign ID")
|
|
17
|
+
name: str = Field(..., description="Human-readable campaign title")
|
|
18
|
+
objective: str = Field(..., description="Campaign validation objective")
|
|
19
|
+
target: AttackTarget = Field(..., description="Target system under validation")
|
|
20
|
+
attacks: List[AttackCase] = Field(default_factory=list, description="List of attack cases in campaign")
|
|
21
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
22
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CampaignSummary(BaseModel):
|
|
26
|
+
"""Empirical security scorecard and metrics aggregated from a completed campaign."""
|
|
27
|
+
|
|
28
|
+
campaign_id: str = Field(..., description="Campaign ID")
|
|
29
|
+
name: str = Field(..., description="Campaign title")
|
|
30
|
+
target_id: str = Field(..., description="Target system ID")
|
|
31
|
+
|
|
32
|
+
# Attack & Attempt Counts
|
|
33
|
+
total_attacks: int = Field(default=0)
|
|
34
|
+
blocked: int = Field(default=0)
|
|
35
|
+
bypassed: int = Field(default=0)
|
|
36
|
+
refused: int = Field(default=0)
|
|
37
|
+
errors: int = Field(default=0)
|
|
38
|
+
unique_attack_variants: int = Field(default=0)
|
|
39
|
+
|
|
40
|
+
# Hard Empirical Metrics
|
|
41
|
+
sensitive_actions_attempted: int = Field(default=0, description="Total sensitive tool invocations attempted")
|
|
42
|
+
sensitive_actions_prevented: int = Field(default=0, description="Total sensitive tool invocations blocked")
|
|
43
|
+
sensitive_actions_executed: int = Field(default=0, description="Total unauthorized sensitive executions (Bypasses)")
|
|
44
|
+
empirical_prevention_rate: float = Field(default=1.0, description="1 - (executed / attempted)")
|
|
45
|
+
|
|
46
|
+
# Rates & Latency
|
|
47
|
+
block_rate_pct: float = Field(default=0.0, description="Percentage of unauthorized attacks successfully blocked")
|
|
48
|
+
sensitive_prevention_rate_pct: float = Field(default=100.0, description="Rate of sensitive sink protection")
|
|
49
|
+
mean_latency_ms: float = Field(default=0.0)
|
|
50
|
+
p95_latency_ms: float = Field(default=0.0)
|
|
51
|
+
duration_ms: float = Field(default=0.0)
|
|
52
|
+
campaign_seed: Optional[int] = Field(default=None)
|
|
53
|
+
|
|
54
|
+
# Adaptive & Lineage Metrics
|
|
55
|
+
max_mutation_depth: int = Field(default=0)
|
|
56
|
+
avg_mutation_depth: float = Field(default=0.0)
|
|
57
|
+
regression_fixtures_generated: int = Field(default=0)
|
|
58
|
+
|
|
59
|
+
# Pipeline Availability
|
|
60
|
+
ai_analysis_availability_pct: float = Field(default=100.0)
|
|
61
|
+
apiris_analysis_availability_pct: float = Field(default=100.0)
|
|
62
|
+
|
|
63
|
+
# Coverage Breakdown
|
|
64
|
+
coverage_breakdown: Dict[str, Dict[str, int]] = Field(default_factory=dict)
|
|
65
|
+
defense_reasons_breakdown: Dict[str, int] = Field(default_factory=dict)
|
|
66
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def from_results(
|
|
70
|
+
cls,
|
|
71
|
+
campaign: AttackCampaign,
|
|
72
|
+
results: List[OffensiveAttackResult],
|
|
73
|
+
memory: Optional[Any] = None,
|
|
74
|
+
seed: Optional[int] = None,
|
|
75
|
+
duration_ms: float = 0.0,
|
|
76
|
+
) -> "CampaignSummary":
|
|
77
|
+
"""Aggregate results into an empirical CampaignSummary."""
|
|
78
|
+
total = len(results)
|
|
79
|
+
if total == 0:
|
|
80
|
+
return cls(
|
|
81
|
+
campaign_id=campaign.campaign_id,
|
|
82
|
+
name=campaign.name,
|
|
83
|
+
target_id=campaign.target.target_id,
|
|
84
|
+
campaign_seed=seed,
|
|
85
|
+
duration_ms=duration_ms,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
blocked = sum(1 for r in results if r.blocked or r.status == AttackStatus.PASS)
|
|
89
|
+
bypassed = sum(1 for r in results if r.bypassed or r.status == AttackStatus.BYPASS)
|
|
90
|
+
refused = sum(1 for r in results if r.status == AttackStatus.REFUSED)
|
|
91
|
+
errors = sum(1 for r in results if r.status == AttackStatus.ERROR)
|
|
92
|
+
|
|
93
|
+
sens_exec = sum(r.execution_evidence.sensitive_db_calls for r in results if r.execution_evidence)
|
|
94
|
+
sens_prev = sum(1 for r in results if r.blocked or r.status == AttackStatus.PASS)
|
|
95
|
+
sens_attempted = sens_prev + sens_exec
|
|
96
|
+
|
|
97
|
+
# Empirical prevention rate = 1 - (executed / attempted)
|
|
98
|
+
if sens_attempted > 0:
|
|
99
|
+
emp_rate = round(1.0 - (sens_exec / sens_attempted), 4)
|
|
100
|
+
else:
|
|
101
|
+
emp_rate = 1.0
|
|
102
|
+
|
|
103
|
+
# Latencies
|
|
104
|
+
lats = sorted([r.latency_ms for r in results])
|
|
105
|
+
mean_lat = sum(lats) / total if total > 0 else 0.0
|
|
106
|
+
p95_idx = int(total * 0.95)
|
|
107
|
+
p95_lat = lats[min(p95_idx, total - 1)] if total > 0 else 0.0
|
|
108
|
+
|
|
109
|
+
# Coverage breakdown by attack_type
|
|
110
|
+
coverage: Dict[str, Dict[str, int]] = {}
|
|
111
|
+
defense_reasons: Dict[str, int] = {}
|
|
112
|
+
ai_available_count = 0
|
|
113
|
+
apiris_available_count = 0
|
|
114
|
+
|
|
115
|
+
for r in results:
|
|
116
|
+
atype = r.attack_type.value if hasattr(r.attack_type, 'value') else str(r.attack_type)
|
|
117
|
+
if atype not in coverage:
|
|
118
|
+
coverage[atype] = {"total": 0, "blocked": 0, "bypassed": 0}
|
|
119
|
+
coverage[atype]["total"] += 1
|
|
120
|
+
if r.blocked or r.status == AttackStatus.PASS:
|
|
121
|
+
coverage[atype]["blocked"] += 1
|
|
122
|
+
if r.bypassed or r.status == AttackStatus.BYPASS:
|
|
123
|
+
coverage[atype]["bypassed"] += 1
|
|
124
|
+
|
|
125
|
+
reason = r.execution_evidence.policy_reason or "UNKNOWN"
|
|
126
|
+
defense_reasons[reason] = defense_reasons.get(reason, 0) + 1
|
|
127
|
+
|
|
128
|
+
if r.ai_analysis and r.ai_analysis.get("status") != "UNAVAILABLE":
|
|
129
|
+
ai_available_count += 1
|
|
130
|
+
if r.apiris_analysis and r.apiris_analysis.get("status") != "UNAVAILABLE":
|
|
131
|
+
apiris_available_count += 1
|
|
132
|
+
|
|
133
|
+
block_rate = round((blocked / total) * 100, 1) if total > 0 else 0.0
|
|
134
|
+
prev_rate = round(((total - bypassed) / total) * 100, 1) if total > 0 else 100.0
|
|
135
|
+
|
|
136
|
+
ai_avail_pct = round((ai_available_count / total) * 100, 1) if total > 0 else 100.0
|
|
137
|
+
apiris_avail_pct = round((apiris_available_count / total) * 100, 1) if total > 0 else 100.0
|
|
138
|
+
|
|
139
|
+
# Memory / Lineage metrics
|
|
140
|
+
max_depth = 0
|
|
141
|
+
avg_depth = 0.0
|
|
142
|
+
reg_count = bypassed
|
|
143
|
+
if memory and hasattr(memory, "nodes"):
|
|
144
|
+
all_nodes = list(memory.nodes.values())
|
|
145
|
+
if all_nodes:
|
|
146
|
+
depths = [n.depth for n in all_nodes]
|
|
147
|
+
max_depth = max(depths)
|
|
148
|
+
avg_depth = round(sum(depths) / len(depths), 2)
|
|
149
|
+
reg_count = len(getattr(memory, "bypasses", [])) or bypassed
|
|
150
|
+
|
|
151
|
+
return cls(
|
|
152
|
+
campaign_id=campaign.campaign_id,
|
|
153
|
+
name=campaign.name,
|
|
154
|
+
target_id=campaign.target.target_id,
|
|
155
|
+
total_attacks=total,
|
|
156
|
+
blocked=blocked,
|
|
157
|
+
bypassed=bypassed,
|
|
158
|
+
refused=refused,
|
|
159
|
+
errors=errors,
|
|
160
|
+
unique_attack_variants=len(set(r.attack_id for r in results)),
|
|
161
|
+
sensitive_actions_attempted=sens_attempted,
|
|
162
|
+
sensitive_actions_prevented=sens_prev,
|
|
163
|
+
sensitive_actions_executed=sens_exec,
|
|
164
|
+
empirical_prevention_rate=emp_rate,
|
|
165
|
+
block_rate_pct=block_rate,
|
|
166
|
+
sensitive_prevention_rate_pct=prev_rate,
|
|
167
|
+
mean_latency_ms=round(mean_lat, 2),
|
|
168
|
+
p95_latency_ms=round(p95_lat, 2),
|
|
169
|
+
duration_ms=round(duration_ms, 2),
|
|
170
|
+
campaign_seed=seed,
|
|
171
|
+
max_mutation_depth=max_depth,
|
|
172
|
+
avg_mutation_depth=avg_depth,
|
|
173
|
+
regression_fixtures_generated=reg_count,
|
|
174
|
+
ai_analysis_availability_pct=ai_avail_pct,
|
|
175
|
+
apiris_analysis_availability_pct=apiris_avail_pct,
|
|
176
|
+
coverage_breakdown=coverage,
|
|
177
|
+
defense_reasons_breakdown=defense_reasons,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def to_markdown(self) -> str:
|
|
181
|
+
"""Format summary into structured Markdown."""
|
|
182
|
+
lines = [
|
|
183
|
+
"# ActShield Adaptive Offensive Validation Scorecard",
|
|
184
|
+
"",
|
|
185
|
+
f"- **Campaign ID**: `{self.campaign_id}`",
|
|
186
|
+
f"- **Target System**: `{self.target_id}`",
|
|
187
|
+
f"- **Campaign Seed**: `{self.campaign_seed}`",
|
|
188
|
+
f"- **Total Attack Executions**: {self.total_attacks} (Unique Variants: {self.unique_attack_variants})",
|
|
189
|
+
f"- **Attacks Blocked**: {self.blocked} ({self.block_rate_pct}%)",
|
|
190
|
+
f"- **Attacks Bypassed**: {self.bypassed}",
|
|
191
|
+
f"- **Sensitive Database Executions**: {self.sensitive_actions_executed}",
|
|
192
|
+
f"- **Empirical Prevention Rate**: {self.empirical_prevention_rate * 100:.1f}%",
|
|
193
|
+
f"- **Max Mutation Depth**: {self.max_mutation_depth} (Average: {self.avg_mutation_depth})",
|
|
194
|
+
f"- **Mean Latency**: {self.mean_latency_ms} ms (p95: {self.p95_latency_ms} ms)",
|
|
195
|
+
f"- **Pipeline Availability**: AI Secura ({self.ai_analysis_availability_pct}%) | APIRIS ({self.apiris_analysis_availability_pct}%)",
|
|
196
|
+
"",
|
|
197
|
+
"## Attack Family Coverage Breakdown",
|
|
198
|
+
"",
|
|
199
|
+
"| Attack Type | Total | Blocked | Bypassed |",
|
|
200
|
+
"|---|---|---|---|",
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
for atype, stats in self.coverage_breakdown.items():
|
|
204
|
+
lines.append(f"| `{atype}` | {stats['total']} | {stats['blocked']} | {stats['bypassed']} |")
|
|
205
|
+
|
|
206
|
+
lines.extend([
|
|
207
|
+
"",
|
|
208
|
+
"## Defense Reasons Breakdown",
|
|
209
|
+
"",
|
|
210
|
+
"| Defense Reason Code | Count |",
|
|
211
|
+
"|---|---|",
|
|
212
|
+
])
|
|
213
|
+
|
|
214
|
+
for reason, count in self.defense_reasons_breakdown.items():
|
|
215
|
+
lines.append(f"| `{reason}` | {count} |")
|
|
216
|
+
|
|
217
|
+
return "\n".join(lines)
|
|
218
|
+
|
|
219
|
+
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Attack corpus loader managing baseline fixtures, attack families, and regression cases."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import pathlib
|
|
5
|
+
from typing import Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from actshield.offensive.attack import AttackCase, AttackType, ExpectedBehavior, SafetyClass
|
|
8
|
+
from actshield.offensive.strategies import (
|
|
9
|
+
PromptInjectionStrategy,
|
|
10
|
+
AuthorityEscalationStrategy,
|
|
11
|
+
ToolPoisoningStrategy,
|
|
12
|
+
ContextManipulationStrategy,
|
|
13
|
+
MCPInjectionStrategy,
|
|
14
|
+
DataExfiltrationStrategy,
|
|
15
|
+
DelegationEscalationStrategy,
|
|
16
|
+
)
|
|
17
|
+
from actshield.offensive.target import AttackTarget, target_registry
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AttackCorpus:
|
|
21
|
+
"""Central repository for loading, organizing, and persisting attack fixtures."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
fixtures_dir: Optional[pathlib.Path] = None,
|
|
26
|
+
regressions_dir: Optional[pathlib.Path] = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
# Default paths
|
|
29
|
+
root = pathlib.Path(__file__).resolve().parent.parent.parent.parent
|
|
30
|
+
self.fixtures_dir = fixtures_dir or (root / "examples" / "attacks")
|
|
31
|
+
self.regressions_dir = regressions_dir or (root / "examples" / "attacks" / "regressions")
|
|
32
|
+
self.regressions_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
def load_baseline_fixtures(self) -> List[AttackCase]:
|
|
35
|
+
"""Load the 5 existing baseline attack fixtures from examples/attacks/."""
|
|
36
|
+
cases: List[AttackCase] = []
|
|
37
|
+
if not self.fixtures_dir.exists():
|
|
38
|
+
return cases
|
|
39
|
+
|
|
40
|
+
for json_path in sorted(self.fixtures_dir.glob("*.json")):
|
|
41
|
+
try:
|
|
42
|
+
with open(json_path, "r", encoding="utf-8") as f:
|
|
43
|
+
data = json.load(f)
|
|
44
|
+
|
|
45
|
+
# Map legacy format to AttackCase
|
|
46
|
+
atype_str = data.get("attack_type", "custom")
|
|
47
|
+
try:
|
|
48
|
+
atype = AttackType(atype_str)
|
|
49
|
+
except ValueError:
|
|
50
|
+
atype = AttackType.CUSTOM
|
|
51
|
+
|
|
52
|
+
case = AttackCase(
|
|
53
|
+
attack_id=data.get("attack_id", json_path.stem),
|
|
54
|
+
name=data.get("attack_id", json_path.stem).replace("_", " ").title(),
|
|
55
|
+
attack_type=atype,
|
|
56
|
+
description=data.get("description", "Baseline attack fixture"),
|
|
57
|
+
entry_point=data.get("entry_point", "mcp_response"),
|
|
58
|
+
target_agent=data.get("target_agent", "DataAgent"),
|
|
59
|
+
payload=data.get("payload", ""),
|
|
60
|
+
target_tool=data.get("target_capability", "customer_db.read"),
|
|
61
|
+
target_resource="customer_pii_vault",
|
|
62
|
+
expected_behavior=ExpectedBehavior.BLOCK if data.get("expected_decision") == "BLOCK" else ExpectedBehavior.ALLOW,
|
|
63
|
+
severity="HIGH",
|
|
64
|
+
safety_class=SafetyClass.LOCAL_SYNTHETIC,
|
|
65
|
+
)
|
|
66
|
+
cases.append(case)
|
|
67
|
+
except Exception:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
return cases
|
|
71
|
+
|
|
72
|
+
def generate_full_family_corpus(self, target: Optional[AttackTarget] = None) -> List[AttackCase]:
|
|
73
|
+
"""Generate comprehensive attack cases spanning all core attack families."""
|
|
74
|
+
target = target or target_registry.get("ActShield-demo")
|
|
75
|
+
all_cases: List[AttackCase] = []
|
|
76
|
+
|
|
77
|
+
# 1. Include baseline fixtures
|
|
78
|
+
all_cases.extend(self.load_baseline_fixtures())
|
|
79
|
+
|
|
80
|
+
# 2. Generate from all strategies
|
|
81
|
+
strategies = [
|
|
82
|
+
PromptInjectionStrategy(),
|
|
83
|
+
AuthorityEscalationStrategy(),
|
|
84
|
+
ToolPoisoningStrategy(),
|
|
85
|
+
ContextManipulationStrategy(),
|
|
86
|
+
MCPInjectionStrategy(),
|
|
87
|
+
DataExfiltrationStrategy(),
|
|
88
|
+
DelegationEscalationStrategy(),
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
for strat in strategies:
|
|
92
|
+
strat_cases = strat.generate(target, count=5)
|
|
93
|
+
# Avoid duplicate IDs
|
|
94
|
+
for c in strat_cases:
|
|
95
|
+
if not any(existing.attack_id == c.attack_id for existing in all_cases):
|
|
96
|
+
all_cases.append(c)
|
|
97
|
+
|
|
98
|
+
return all_cases
|
|
99
|
+
|
|
100
|
+
def get_all_attacks(self, target: Optional[AttackTarget] = None) -> List[AttackCase]:
|
|
101
|
+
"""Convenience alias returning the full baseline and generated attack corpus."""
|
|
102
|
+
return self.generate_full_family_corpus(target=target)
|
|
103
|
+
|
|
104
|
+
def get_regression_attacks(self) -> List[AttackCase]:
|
|
105
|
+
"""Convenience alias returning all regression attack cases."""
|
|
106
|
+
return self.load_regression_cases()
|
|
107
|
+
|
|
108
|
+
def get_attack(self, attack_id: str) -> Optional[AttackCase]:
|
|
109
|
+
"""Retrieve a specific attack case by ID from full corpus or regression store."""
|
|
110
|
+
for a in self.get_all_attacks():
|
|
111
|
+
if a.attack_id == attack_id:
|
|
112
|
+
return a
|
|
113
|
+
for r in self.get_regression_attacks():
|
|
114
|
+
if r.attack_id == attack_id:
|
|
115
|
+
return r
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
def get_attacks_by_type(self, attack_type: AttackType) -> List[AttackCase]:
|
|
119
|
+
"""Retrieve all attack cases matching a specific AttackType."""
|
|
120
|
+
return [a for a in self.get_all_attacks() if a.attack_type == attack_type]
|
|
121
|
+
|
|
122
|
+
def save_regression_case(self, attack: AttackCase, reason: str = "Bypass detected") -> pathlib.Path:
|
|
123
|
+
"""Persist a discovered bypass attack case to permanent regression fixtures."""
|
|
124
|
+
self.regressions_dir.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
file_path = self.regressions_dir / f"{attack.attack_id}.json"
|
|
126
|
+
|
|
127
|
+
data = attack.model_dump(mode="json")
|
|
128
|
+
data["regression_reason"] = reason
|
|
129
|
+
|
|
130
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
131
|
+
json.dump(data, f, indent=2)
|
|
132
|
+
|
|
133
|
+
return file_path
|
|
134
|
+
|
|
135
|
+
def load_regression_cases(self) -> List[AttackCase]:
|
|
136
|
+
"""Load all permanent regression attack fixtures."""
|
|
137
|
+
regressions: List[AttackCase] = []
|
|
138
|
+
if not self.regressions_dir.exists():
|
|
139
|
+
return regressions
|
|
140
|
+
|
|
141
|
+
for json_path in sorted(self.regressions_dir.glob("*.json")):
|
|
142
|
+
try:
|
|
143
|
+
with open(json_path, "r", encoding="utf-8") as f:
|
|
144
|
+
data = json.load(f)
|
|
145
|
+
regressions.append(AttackCase(**data))
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
return regressions
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Global default corpus instance
|
|
153
|
+
attack_corpus = AttackCorpus()
|
|
154
|
+
|
|
155
|
+
|