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,466 @@
|
|
|
1
|
+
"""ActShield Offensive Validation Engine — Orchestrating continuous security evaluation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import pathlib
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
from actshield.client import ActShield
|
|
11
|
+
from actshield.context.provenance import ContextSource
|
|
12
|
+
from actshield.context.taint import TaintState
|
|
13
|
+
from actshield.decisions.decision import DecisionAction, SecurityDecision
|
|
14
|
+
from actshield.integrations.ai_secura import SecurityContext
|
|
15
|
+
from actshield.offensive.attack import AttackCase, AttackType, ExpectedBehavior, SafetyClass
|
|
16
|
+
from actshield.offensive.campaign import AttackCampaign, CampaignSummary
|
|
17
|
+
from actshield.offensive.corpus import AttackCorpus, attack_corpus
|
|
18
|
+
from actshield.offensive.evaluator import AttackEvaluator
|
|
19
|
+
from actshield.offensive.evidence import SecurityAnalysisEvidence
|
|
20
|
+
from actshield.offensive.results import AttackStatus, ExecutionEvidence, OffensiveAttackResult
|
|
21
|
+
from actshield.offensive.safety import SafetyValidator, safety_validator
|
|
22
|
+
from actshield.offensive.target import AttackTarget, TargetRegistry, target_registry
|
|
23
|
+
from actshield.tools.tool import Resource, SensitivityLevel, ToolDefinition, ToolRequest
|
|
24
|
+
from actshield.tracing.correlation import generate_id
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("actshield.offensive.engine")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class OffensiveEngine:
|
|
30
|
+
"""The central orchestrator for autonomous offensive security validation."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
guard: Optional[ActShield] = None,
|
|
35
|
+
validator: Optional[SafetyValidator] = None,
|
|
36
|
+
registry: Optional[TargetRegistry] = None,
|
|
37
|
+
corpus: Optional[AttackCorpus] = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
self.guard = guard or ActShield()
|
|
40
|
+
self.validator = validator or safety_validator
|
|
41
|
+
self.registry = registry or target_registry
|
|
42
|
+
self.corpus = corpus or attack_corpus
|
|
43
|
+
|
|
44
|
+
# Execution tracking counters
|
|
45
|
+
self.customer_read_calls = 0
|
|
46
|
+
self.total_tool_executions = 0
|
|
47
|
+
self._setup_tools()
|
|
48
|
+
|
|
49
|
+
def _setup_tools(self) -> None:
|
|
50
|
+
"""Register synthetic sandbox tools with hard execution monitoring."""
|
|
51
|
+
from actshield.tools.interceptor import protected_tool
|
|
52
|
+
from actshield.tools.tool import Resource, SensitivityLevel
|
|
53
|
+
|
|
54
|
+
# 1. Protected Sensitive Customer DB Sink
|
|
55
|
+
cust_res = Resource(
|
|
56
|
+
resource_id="res_cust",
|
|
57
|
+
name="customer_pii_vault",
|
|
58
|
+
sensitivity=SensitivityLevel.CRITICAL,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@protected_tool(
|
|
62
|
+
guard=self.guard,
|
|
63
|
+
name="customer_db.read",
|
|
64
|
+
description="Reads customer PII records from vault",
|
|
65
|
+
sensitivity=SensitivityLevel.CRITICAL,
|
|
66
|
+
required_capabilities=["customer_db.read"],
|
|
67
|
+
target_resources=[cust_res],
|
|
68
|
+
)
|
|
69
|
+
def customer_db_read(table: str = "customers", fields: Optional[List[str]] = None, **kwargs: Any) -> Dict[str, Any]:
|
|
70
|
+
self.customer_read_calls += 1
|
|
71
|
+
self.total_tool_executions += 1
|
|
72
|
+
return {"status": "SUCCESS", "rows": [{"id": 1, "name": "Alice", "pii": "EXTRACTED"}]}
|
|
73
|
+
|
|
74
|
+
# 2. Protected Low-Sensitivity Public Tool
|
|
75
|
+
pub_res = Resource(
|
|
76
|
+
resource_id="res_pub",
|
|
77
|
+
name="public_filing_cache",
|
|
78
|
+
sensitivity=SensitivityLevel.LOW,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@protected_tool(
|
|
82
|
+
guard=self.guard,
|
|
83
|
+
name="sec_edgar.fetch_filing",
|
|
84
|
+
description="Fetches public Form 10-K SEC filings",
|
|
85
|
+
sensitivity=SensitivityLevel.LOW,
|
|
86
|
+
required_capabilities=["public_documents", "public_search"],
|
|
87
|
+
target_resources=[pub_res],
|
|
88
|
+
)
|
|
89
|
+
def fetch_filing(ticker: str = "ACME", year: int = 2026, **kwargs: Any) -> Dict[str, Any]:
|
|
90
|
+
self.total_tool_executions += 1
|
|
91
|
+
return {"status": "SUCCESS", "filing": "ACME FY2026 10-K: Revenue $14.2B"}
|
|
92
|
+
|
|
93
|
+
# 3. HTTP Outbound Tool
|
|
94
|
+
ext_res = Resource(
|
|
95
|
+
resource_id="res_ext",
|
|
96
|
+
name="external_api",
|
|
97
|
+
sensitivity=SensitivityLevel.HIGH,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@protected_tool(
|
|
101
|
+
guard=self.guard,
|
|
102
|
+
name="http_post",
|
|
103
|
+
description="Posts outbound payload to synthetic endpoint",
|
|
104
|
+
sensitivity=SensitivityLevel.HIGH,
|
|
105
|
+
required_capabilities=["external_api"],
|
|
106
|
+
target_resources=[ext_res],
|
|
107
|
+
)
|
|
108
|
+
def http_post(url: str = "https://synthetic.internal", data: Any = None, **kwargs: Any) -> Dict[str, Any]:
|
|
109
|
+
self.total_tool_executions += 1
|
|
110
|
+
return {"status": "POSTED", "url": url}
|
|
111
|
+
|
|
112
|
+
self._tool_callables = {
|
|
113
|
+
"customer_db.read": customer_db_read,
|
|
114
|
+
"sec_edgar.fetch_filing": fetch_filing,
|
|
115
|
+
"http_post": http_post,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def execute_attack(
|
|
119
|
+
self,
|
|
120
|
+
attack: AttackCase,
|
|
121
|
+
target: Optional[AttackTarget] = None,
|
|
122
|
+
campaign_id: Optional[str] = None,
|
|
123
|
+
mutation_lineage: Optional[List[str]] = None,
|
|
124
|
+
) -> OffensiveAttackResult:
|
|
125
|
+
"""Execute a single attack case against the local synthetic environment."""
|
|
126
|
+
target = target or self.registry.get("ActShield-demo")
|
|
127
|
+
start_time = time.perf_counter()
|
|
128
|
+
|
|
129
|
+
# 1. Strict Safety Validation
|
|
130
|
+
safety_check = self.validator.validate(attack, target)
|
|
131
|
+
if not safety_check.safe:
|
|
132
|
+
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
133
|
+
return OffensiveAttackResult(
|
|
134
|
+
attack_id=attack.attack_id,
|
|
135
|
+
campaign_id=campaign_id,
|
|
136
|
+
attack_type=attack.attack_type,
|
|
137
|
+
target=target.target_id if target else "unknown",
|
|
138
|
+
entry_point=attack.entry_point,
|
|
139
|
+
expected_decision=attack.expected_behavior.value,
|
|
140
|
+
actual_decision="REFUSED",
|
|
141
|
+
status=AttackStatus.REFUSED,
|
|
142
|
+
error_message=safety_check.reason,
|
|
143
|
+
latency_ms=elapsed_ms,
|
|
144
|
+
severity=attack.severity,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# 2. Reset hard execution counters before test
|
|
148
|
+
initial_db_calls = self.customer_read_calls
|
|
149
|
+
initial_tool_execs = self.total_tool_executions
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
# 3. Initialize Multi-Agent Task Context
|
|
153
|
+
trace_id = generate_id("tr_atk")
|
|
154
|
+
|
|
155
|
+
# Setup synthetic agent with appropriate capabilities
|
|
156
|
+
if attack.attack_type in (AttackType.AUTHORITY_ESCALATION, AttackType.AUTHORITY_IMPERSONATION, AttackType.DELEGATION_ESCALATION):
|
|
157
|
+
agent_caps = [attack.required_capability] if attack.required_capability else ["public_search", "financial_extract"]
|
|
158
|
+
else:
|
|
159
|
+
agent_caps = [
|
|
160
|
+
"public_search",
|
|
161
|
+
"public_documents",
|
|
162
|
+
"financial_extract",
|
|
163
|
+
"customer_db.read",
|
|
164
|
+
"external_api",
|
|
165
|
+
]
|
|
166
|
+
if attack.required_capability and attack.required_capability not in agent_caps:
|
|
167
|
+
agent_caps.append(attack.required_capability)
|
|
168
|
+
|
|
169
|
+
agent = self.guard.agent(
|
|
170
|
+
name=attack.target_agent,
|
|
171
|
+
framework="custom",
|
|
172
|
+
capabilities=agent_caps,
|
|
173
|
+
metadata={"synthetic_target": True},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Ingest context & evaluate within task and span scope
|
|
177
|
+
with self.guard.task(intent=attack.task_intent, trace_id=trace_id) as task_obj:
|
|
178
|
+
with self.guard.span(name="attack_execution", agent_id=agent.agent_id):
|
|
179
|
+
# Ingest adversarial context
|
|
180
|
+
source_enum = ContextSource.EXTERNAL_MCP if "mcp" in attack.entry_point else ContextSource.EXTERNAL_API
|
|
181
|
+
taint_enum = TaintState.TAINTED if attack.context_taint == "TAINTED" else TaintState.CLEAN
|
|
182
|
+
|
|
183
|
+
adv_ctx = self.guard.context(
|
|
184
|
+
source=source_enum,
|
|
185
|
+
data=attack.payload,
|
|
186
|
+
taint_state=taint_enum,
|
|
187
|
+
agent_id=agent.agent_id,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
tool_name = attack.target_tool or "customer_db.read"
|
|
191
|
+
target_tool_def = self.guard.tool_registry.get(tool_name)
|
|
192
|
+
if target_tool_def is None:
|
|
193
|
+
req_cap = attack.required_capability or tool_name
|
|
194
|
+
res_name = attack.target_resource or tool_name.split(".")[0]
|
|
195
|
+
tool_res = Resource(
|
|
196
|
+
resource_id=f"res_{res_name}",
|
|
197
|
+
name=res_name,
|
|
198
|
+
sensitivity=SensitivityLevel.CRITICAL if any(k in res_name for k in ("vault", "pii", "ledger", "customer")) else SensitivityLevel.HIGH,
|
|
199
|
+
)
|
|
200
|
+
target_tool_def = ToolDefinition(
|
|
201
|
+
tool_id=generate_id("tool"),
|
|
202
|
+
name=tool_name,
|
|
203
|
+
description=f"Synthetic tool {tool_name}",
|
|
204
|
+
sensitivity=SensitivityLevel.CRITICAL if any(k in res_name for k in ("vault", "pii", "ledger", "customer")) else SensitivityLevel.HIGH,
|
|
205
|
+
required_capabilities=[req_cap],
|
|
206
|
+
target_resources=[tool_res],
|
|
207
|
+
)
|
|
208
|
+
self.guard.register_tool(target_tool_def)
|
|
209
|
+
|
|
210
|
+
tool_req = ToolRequest(
|
|
211
|
+
tool_id=target_tool_def.tool_id,
|
|
212
|
+
tool_name=tool_name,
|
|
213
|
+
agent_id=agent.agent_id,
|
|
214
|
+
arguments=attack.tool_arguments or {"table": "customers"},
|
|
215
|
+
context_ids=[adv_ctx.context_id],
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# --- Full Security Analysis Pipeline ---
|
|
219
|
+
# 1. APIRIS API/Tool Risk Intelligence
|
|
220
|
+
apiris_analysis_dict = None
|
|
221
|
+
apiris_unavail = False
|
|
222
|
+
try:
|
|
223
|
+
if hasattr(self.guard, "apiris_adapter") and self.guard.apiris_adapter:
|
|
224
|
+
apiris_raw = self.guard.apiris_adapter.analyze(tool_req)
|
|
225
|
+
if hasattr(apiris_raw, "to_dict"):
|
|
226
|
+
apiris_analysis_dict = apiris_raw.to_dict()
|
|
227
|
+
elif hasattr(apiris_raw, "model_dump"):
|
|
228
|
+
apiris_analysis_dict = apiris_raw.model_dump()
|
|
229
|
+
elif isinstance(apiris_raw, dict):
|
|
230
|
+
apiris_analysis_dict = apiris_raw
|
|
231
|
+
else:
|
|
232
|
+
apiris_analysis_dict = {
|
|
233
|
+
"tool_name": tool_name,
|
|
234
|
+
"risk_score": getattr(apiris_raw, "risk_score", 0.0),
|
|
235
|
+
"recommended_action": getattr(apiris_raw, "recommended_action", "ALLOW"),
|
|
236
|
+
}
|
|
237
|
+
except Exception as e:
|
|
238
|
+
apiris_unavail = True
|
|
239
|
+
apiris_analysis_dict = {"status": "UNAVAILABLE", "error": str(e)}
|
|
240
|
+
|
|
241
|
+
# 2. AI Secura Cybersecurity Reasoning
|
|
242
|
+
ai_analysis_dict = None
|
|
243
|
+
ai_unavail = False
|
|
244
|
+
try:
|
|
245
|
+
if hasattr(self.guard, "ai_secura_adapter") and self.guard.ai_secura_adapter:
|
|
246
|
+
sec_ctx = SecurityContext(
|
|
247
|
+
trace_id=trace_id,
|
|
248
|
+
task_intent=attack.task_intent,
|
|
249
|
+
acting_agent_id=agent.agent_id,
|
|
250
|
+
acting_agent_name=agent.name,
|
|
251
|
+
tool_name=tool_name,
|
|
252
|
+
tool_arguments=tool_req.arguments,
|
|
253
|
+
context_provenance=[{"source": adv_ctx.source.value, "taint": attack.context_taint}],
|
|
254
|
+
taint_states=[attack.context_taint],
|
|
255
|
+
)
|
|
256
|
+
ai_raw = self.guard.ai_secura_adapter.analyze(sec_ctx)
|
|
257
|
+
if hasattr(ai_raw, "to_dict"):
|
|
258
|
+
ai_analysis_dict = ai_raw.to_dict()
|
|
259
|
+
elif hasattr(ai_raw, "model_dump"):
|
|
260
|
+
ai_analysis_dict = ai_raw.model_dump()
|
|
261
|
+
elif isinstance(ai_raw, dict):
|
|
262
|
+
ai_analysis_dict = ai_raw
|
|
263
|
+
else:
|
|
264
|
+
ai_analysis_dict = {
|
|
265
|
+
"summary": getattr(ai_raw, "summary", "Security analysis generated"),
|
|
266
|
+
"risk_level": getattr(ai_raw, "risk_level", "HIGH"),
|
|
267
|
+
"risk_score": getattr(ai_raw, "risk_score", 0.8),
|
|
268
|
+
"attack_indicators": getattr(ai_raw, "attack_indicators", []),
|
|
269
|
+
}
|
|
270
|
+
except Exception as e:
|
|
271
|
+
ai_unavail = True
|
|
272
|
+
ai_analysis_dict = {"status": "UNAVAILABLE", "error": str(e)}
|
|
273
|
+
|
|
274
|
+
# 3. Deterministic Policy Enforcement
|
|
275
|
+
decision = self.guard.evaluate_tool_invocation(target_tool_def, tool_req)
|
|
276
|
+
|
|
277
|
+
# Execute tool only if policy ALLOWed
|
|
278
|
+
if decision.action == DecisionAction.ALLOW:
|
|
279
|
+
tool_func = self._tool_callables.get(tool_name)
|
|
280
|
+
if tool_func:
|
|
281
|
+
try:
|
|
282
|
+
tool_func(**tool_req.arguments)
|
|
283
|
+
except Exception:
|
|
284
|
+
pass
|
|
285
|
+
|
|
286
|
+
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
287
|
+
|
|
288
|
+
# 5. Measure hard execution evidence
|
|
289
|
+
delta_db_calls = self.customer_read_calls - initial_db_calls
|
|
290
|
+
delta_tool_execs = self.total_tool_executions - initial_tool_execs
|
|
291
|
+
|
|
292
|
+
attack_path = [
|
|
293
|
+
{"hop": 1, "source": attack.entry_point, "status": "INJECTED"},
|
|
294
|
+
{"hop": 2, "agent": attack.target_agent, "action": "INGEST_CONTEXT", "taint": attack.context_taint},
|
|
295
|
+
{"hop": 3, "target_tool": tool_name, "decision": decision.action.value},
|
|
296
|
+
]
|
|
297
|
+
|
|
298
|
+
sec_evidence = SecurityAnalysisEvidence(
|
|
299
|
+
attack_id=attack.attack_id,
|
|
300
|
+
attack_type=attack.attack_type,
|
|
301
|
+
trace_id=trace_id,
|
|
302
|
+
acting_agent=agent.name,
|
|
303
|
+
task_intent=attack.task_intent,
|
|
304
|
+
entry_point=attack.entry_point,
|
|
305
|
+
context_sources=[adv_ctx.source.value],
|
|
306
|
+
taint_state=attack.context_taint,
|
|
307
|
+
context_ids=[adv_ctx.context_id],
|
|
308
|
+
requested_tool=tool_name,
|
|
309
|
+
requested_capability=attack.required_capability,
|
|
310
|
+
required_capabilities=target_tool_def.required_capabilities,
|
|
311
|
+
delegated_capabilities=agent.capability_names(),
|
|
312
|
+
authority_contained=bool(decision and decision.authority_containment.get("contained", True)),
|
|
313
|
+
authority_reason=decision.authority_containment.get("reason", "") if decision else "",
|
|
314
|
+
intent_aligned=bool(decision and decision.intent_alignment.get("aligned", True)),
|
|
315
|
+
intent_explanation=decision.intent_alignment.get("explanation", "") if decision else "",
|
|
316
|
+
apiris_analysis=apiris_analysis_dict,
|
|
317
|
+
apiris_unavailable=apiris_unavail,
|
|
318
|
+
ai_analysis=ai_analysis_dict,
|
|
319
|
+
ai_unavailable=ai_unavail,
|
|
320
|
+
policy_decision=decision.action.value if decision else "UNKNOWN",
|
|
321
|
+
policy_reason_code=decision.reason_code if decision else "NO_DECISION",
|
|
322
|
+
policy_explanation=decision.explanation if decision else "",
|
|
323
|
+
risk_score=decision.risk_score if decision else 0.0,
|
|
324
|
+
tool_executed=delta_tool_execs > 0 or delta_db_calls > 0,
|
|
325
|
+
execution_count=delta_tool_execs,
|
|
326
|
+
sensitive_db_calls=delta_db_calls,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
result = AttackEvaluator.evaluate(
|
|
330
|
+
attack=attack,
|
|
331
|
+
decision=decision,
|
|
332
|
+
execution_count=delta_tool_execs,
|
|
333
|
+
sensitive_db_calls=delta_db_calls,
|
|
334
|
+
trace_id=trace_id,
|
|
335
|
+
attack_path=attack_path,
|
|
336
|
+
influencing_context=[adv_ctx.context_id],
|
|
337
|
+
ai_analysis=ai_analysis_dict,
|
|
338
|
+
apiris_analysis=apiris_analysis_dict,
|
|
339
|
+
security_evidence=sec_evidence,
|
|
340
|
+
mutation_lineage=mutation_lineage,
|
|
341
|
+
latency_ms=elapsed_ms,
|
|
342
|
+
campaign_id=campaign_id,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# 6. If bypass occurred, archive to regression fixtures
|
|
346
|
+
if result.bypassed:
|
|
347
|
+
self.corpus.save_regression_case(attack, reason=f"Bypass detected: {decision.explanation}")
|
|
348
|
+
|
|
349
|
+
return result
|
|
350
|
+
|
|
351
|
+
except Exception as e:
|
|
352
|
+
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
353
|
+
return AttackEvaluator.evaluate(
|
|
354
|
+
attack=attack,
|
|
355
|
+
decision=None,
|
|
356
|
+
execution_count=0,
|
|
357
|
+
sensitive_db_calls=0,
|
|
358
|
+
latency_ms=elapsed_ms,
|
|
359
|
+
campaign_id=campaign_id,
|
|
360
|
+
error_msg=str(e),
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
def execute_campaign(self, campaign: AttackCampaign) -> Tuple[List[OffensiveAttackResult], CampaignSummary]:
|
|
364
|
+
"""Execute all attack cases in an attack campaign and aggregate the scorecard."""
|
|
365
|
+
results: List[OffensiveAttackResult] = []
|
|
366
|
+
|
|
367
|
+
for attack in campaign.attacks:
|
|
368
|
+
res = self.execute_attack(attack, target=campaign.target, campaign_id=campaign.campaign_id)
|
|
369
|
+
results.append(res)
|
|
370
|
+
|
|
371
|
+
summary = CampaignSummary.from_results(campaign, results)
|
|
372
|
+
return results, summary
|
|
373
|
+
|
|
374
|
+
def replay_attack(self, attack_id: str, target: Optional[AttackTarget] = None) -> OffensiveAttackResult:
|
|
375
|
+
"""Replay a specific attack fixture by ID."""
|
|
376
|
+
target = target or self.registry.get("ActShield-demo")
|
|
377
|
+
all_cases = self.corpus.generate_full_family_corpus(target) + self.corpus.load_regression_cases()
|
|
378
|
+
|
|
379
|
+
match = next((c for c in all_cases if c.attack_id == attack_id), None)
|
|
380
|
+
if not match:
|
|
381
|
+
raise ValueError(f"Attack ID '{attack_id}' not found in baseline or regression corpus.")
|
|
382
|
+
|
|
383
|
+
return self.execute_attack(match, target=target, campaign_id="replay")
|
|
384
|
+
|
|
385
|
+
def export_reports(
|
|
386
|
+
self,
|
|
387
|
+
summary: CampaignSummary,
|
|
388
|
+
results: List[OffensiveAttackResult],
|
|
389
|
+
output_dir: Optional[pathlib.Path] = None,
|
|
390
|
+
) -> Dict[str, pathlib.Path]:
|
|
391
|
+
"""Export JSON scorecard and markdown reports to reports/phase4/."""
|
|
392
|
+
root = pathlib.Path(__file__).resolve().parent.parent.parent.parent
|
|
393
|
+
out = output_dir or (root / "reports" / "phase4")
|
|
394
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
395
|
+
|
|
396
|
+
files: Dict[str, pathlib.Path] = {}
|
|
397
|
+
|
|
398
|
+
# 1. campaign_summary.json
|
|
399
|
+
sum_path = out / "campaign_summary.json"
|
|
400
|
+
with open(sum_path, "w", encoding="utf-8") as f:
|
|
401
|
+
json.dump(summary.model_dump(mode="json"), f, indent=2)
|
|
402
|
+
files["summary_json"] = sum_path
|
|
403
|
+
|
|
404
|
+
# 2. attack_results.json
|
|
405
|
+
res_path = out / "attack_results.json"
|
|
406
|
+
with open(res_path, "w", encoding="utf-8") as f:
|
|
407
|
+
json.dump([r.model_dump(mode="json") for r in results], f, indent=2)
|
|
408
|
+
files["results_json"] = res_path
|
|
409
|
+
|
|
410
|
+
# 3. security_scorecard.json
|
|
411
|
+
score_path = out / "security_scorecard.json"
|
|
412
|
+
scorecard_data = {
|
|
413
|
+
"campaign_id": summary.campaign_id,
|
|
414
|
+
"target_id": summary.target_id,
|
|
415
|
+
"total_attacks": summary.total_attacks,
|
|
416
|
+
"attacks_blocked": summary.blocked,
|
|
417
|
+
"attacks_bypassed": summary.bypassed,
|
|
418
|
+
"block_rate_pct": summary.block_rate_pct,
|
|
419
|
+
"sensitive_actions_prevented": summary.sensitive_actions_prevented,
|
|
420
|
+
"sensitive_actions_executed": summary.sensitive_actions_executed,
|
|
421
|
+
"mean_latency_ms": summary.mean_latency_ms,
|
|
422
|
+
"p95_latency_ms": summary.p95_latency_ms,
|
|
423
|
+
}
|
|
424
|
+
with open(score_path, "w", encoding="utf-8") as f:
|
|
425
|
+
json.dump(scorecard_data, f, indent=2)
|
|
426
|
+
files["scorecard_json"] = score_path
|
|
427
|
+
|
|
428
|
+
# 4. campaign_summary.md
|
|
429
|
+
md_path = out / "campaign_summary.md"
|
|
430
|
+
md_content = (
|
|
431
|
+
f"# ActShield Offensive Validation Campaign Report\n\n"
|
|
432
|
+
f"- **Campaign ID**: `{summary.campaign_id}`\n"
|
|
433
|
+
f"- **Target System**: `{summary.target_id}`\n"
|
|
434
|
+
f"- **Total Attacks Executed**: {summary.total_attacks}\n"
|
|
435
|
+
f"- **Blocked Attacks**: {summary.blocked} ({summary.block_rate_pct}%)\n"
|
|
436
|
+
f"- **Bypassed Attacks**: {summary.bypassed}\n"
|
|
437
|
+
f"- **Sensitive Database Executions**: {summary.sensitive_actions_executed}\n"
|
|
438
|
+
f"- **Mean Latency**: {summary.mean_latency_ms:.2f} ms\n\n"
|
|
439
|
+
f"## Attack Coverage Breakdown\n\n"
|
|
440
|
+
f"| Attack Type | Total | Blocked | Bypassed |\n"
|
|
441
|
+
f"|---|---|---|---|\n"
|
|
442
|
+
)
|
|
443
|
+
for atype, counts in summary.coverage_breakdown.items():
|
|
444
|
+
md_content += f"| `{atype}` | {counts['total']} | {counts['blocked']} | {counts['bypassed']} |\n"
|
|
445
|
+
|
|
446
|
+
with open(md_path, "w", encoding="utf-8") as f:
|
|
447
|
+
f.write(md_content)
|
|
448
|
+
files["summary_md"] = md_path
|
|
449
|
+
|
|
450
|
+
return files
|
|
451
|
+
|
|
452
|
+
def run_attack_suite(
|
|
453
|
+
self,
|
|
454
|
+
attacks: List[AttackCase],
|
|
455
|
+
target: Optional[AttackTarget] = None,
|
|
456
|
+
) -> List[OffensiveAttackResult]:
|
|
457
|
+
"""Execute a batch list of attacks against the specified target."""
|
|
458
|
+
results: List[OffensiveAttackResult] = []
|
|
459
|
+
for atk in attacks:
|
|
460
|
+
res = self.execute_attack(atk, target=target)
|
|
461
|
+
results.append(res)
|
|
462
|
+
return results
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Offensive attack evaluator enforcing empirical defense validation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
from actshield.decisions.decision import DecisionAction, SecurityDecision
|
|
5
|
+
from actshield.offensive.attack import AttackCase, ExpectedBehavior
|
|
6
|
+
from actshield.offensive.evidence import SecurityAnalysisEvidence
|
|
7
|
+
from actshield.offensive.results import AttackStatus, ExecutionEvidence, OffensiveAttackResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AttackEvaluator:
|
|
11
|
+
"""Evaluates whether an attack was effectively prevented using both policy decision and empirical execution counters."""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def evaluate(
|
|
15
|
+
attack: AttackCase,
|
|
16
|
+
decision: Optional[SecurityDecision],
|
|
17
|
+
execution_count: int,
|
|
18
|
+
sensitive_db_calls: int,
|
|
19
|
+
trace_id: Optional[str] = None,
|
|
20
|
+
attack_path: Optional[List[Dict[str, Any]]] = None,
|
|
21
|
+
influencing_context: Optional[List[str]] = None,
|
|
22
|
+
ai_analysis: Optional[Dict[str, Any]] = None,
|
|
23
|
+
apiris_analysis: Optional[Dict[str, Any]] = None,
|
|
24
|
+
security_evidence: Optional[SecurityAnalysisEvidence] = None,
|
|
25
|
+
mutation_lineage: Optional[List[str]] = None,
|
|
26
|
+
latency_ms: float = 0.0,
|
|
27
|
+
campaign_id: Optional[str] = None,
|
|
28
|
+
error_msg: Optional[str] = None,
|
|
29
|
+
) -> OffensiveAttackResult:
|
|
30
|
+
"""Render the definitive OffensiveAttackResult."""
|
|
31
|
+
|
|
32
|
+
if error_msg:
|
|
33
|
+
return OffensiveAttackResult(
|
|
34
|
+
attack_id=attack.attack_id,
|
|
35
|
+
campaign_id=campaign_id,
|
|
36
|
+
attack_type=attack.attack_type,
|
|
37
|
+
target=attack.target_agent,
|
|
38
|
+
entry_point=attack.entry_point,
|
|
39
|
+
expected_decision=attack.expected_behavior.value,
|
|
40
|
+
actual_decision="ERROR",
|
|
41
|
+
status=AttackStatus.ERROR,
|
|
42
|
+
error_message=error_msg,
|
|
43
|
+
latency_ms=latency_ms,
|
|
44
|
+
severity=attack.severity,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
actual_action = decision.action.value if decision else "UNKNOWN"
|
|
48
|
+
policy_reason = decision.reason_code if decision else "NO_DECISION"
|
|
49
|
+
|
|
50
|
+
# Empirical execution validation
|
|
51
|
+
tool_actually_executed = execution_count > 0 or sensitive_db_calls > 0
|
|
52
|
+
expected_to_execute = attack.expected_behavior == ExpectedBehavior.ALLOW
|
|
53
|
+
|
|
54
|
+
# Hard Bypass Detection:
|
|
55
|
+
# If attack was expected to be BLOCKED, but was ALLOWED or executed sensitive sinks
|
|
56
|
+
bypassed = False
|
|
57
|
+
blocked = (actual_action in ("BLOCK", "QUARANTINE", "REVOKE"))
|
|
58
|
+
|
|
59
|
+
if attack.expected_behavior == ExpectedBehavior.BLOCK:
|
|
60
|
+
if actual_action == "ALLOW" or sensitive_db_calls > 0:
|
|
61
|
+
bypassed = True
|
|
62
|
+
status = AttackStatus.BYPASS
|
|
63
|
+
elif blocked and sensitive_db_calls == 0:
|
|
64
|
+
bypassed = False
|
|
65
|
+
status = AttackStatus.PASS
|
|
66
|
+
else:
|
|
67
|
+
bypassed = tool_actually_executed
|
|
68
|
+
status = AttackStatus.BYPASS if bypassed else AttackStatus.PASS
|
|
69
|
+
elif attack.expected_behavior == ExpectedBehavior.ALLOW:
|
|
70
|
+
if actual_action == "ALLOW" and tool_actually_executed:
|
|
71
|
+
status = AttackStatus.PASS
|
|
72
|
+
else:
|
|
73
|
+
# Expected allow but blocked -> false positive in benign control
|
|
74
|
+
status = AttackStatus.ERROR
|
|
75
|
+
|
|
76
|
+
# Causal trace check
|
|
77
|
+
trace_preserved = bool(trace_id and attack_path is not None)
|
|
78
|
+
|
|
79
|
+
evidence = ExecutionEvidence(
|
|
80
|
+
tool_executed=tool_actually_executed,
|
|
81
|
+
execution_count=execution_count,
|
|
82
|
+
sensitive_db_calls=sensitive_db_calls,
|
|
83
|
+
actual_action=actual_action,
|
|
84
|
+
policy_reason=policy_reason,
|
|
85
|
+
taint_state=attack.context_taint,
|
|
86
|
+
causal_trace_preserved=trace_preserved,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return OffensiveAttackResult(
|
|
90
|
+
attack_id=attack.attack_id,
|
|
91
|
+
campaign_id=campaign_id,
|
|
92
|
+
attack_type=attack.attack_type,
|
|
93
|
+
target=attack.target_agent,
|
|
94
|
+
entry_point=attack.entry_point,
|
|
95
|
+
expected_decision=attack.expected_behavior.value,
|
|
96
|
+
actual_decision=actual_action,
|
|
97
|
+
expected_execution=expected_to_execute,
|
|
98
|
+
actual_execution=tool_actually_executed,
|
|
99
|
+
blocked=blocked,
|
|
100
|
+
bypassed=bypassed,
|
|
101
|
+
trace_id=trace_id,
|
|
102
|
+
attack_path=attack_path or [],
|
|
103
|
+
influencing_context=influencing_context or [],
|
|
104
|
+
taint_state=attack.context_taint,
|
|
105
|
+
authority_violation=bool(decision and not decision.authority_containment.get("contained", True)),
|
|
106
|
+
intent_violation=bool(decision and not decision.intent_alignment.get("aligned", True)),
|
|
107
|
+
ai_analysis=ai_analysis,
|
|
108
|
+
apiris_analysis=apiris_analysis,
|
|
109
|
+
security_evidence=security_evidence,
|
|
110
|
+
mutation_lineage=mutation_lineage or [],
|
|
111
|
+
execution_evidence=evidence,
|
|
112
|
+
severity=attack.severity,
|
|
113
|
+
latency_ms=latency_ms,
|
|
114
|
+
status=status,
|
|
115
|
+
)
|
|
116
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Unified Security Analysis Evidence — Complete audit trail for attack analysis."""
|
|
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 AttackType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SecurityAnalysisEvidence(BaseModel):
|
|
11
|
+
"""Unified security evidence object capturing the end-to-end pipeline:
|
|
12
|
+
OBSERVE -> CORRELATE -> ANALYZE -> ENFORCE -> EXECUTE.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
attack_id: str
|
|
16
|
+
attack_type: AttackType
|
|
17
|
+
trace_id: Optional[str] = None
|
|
18
|
+
acting_agent: str
|
|
19
|
+
task_intent: str
|
|
20
|
+
entry_point: str
|
|
21
|
+
|
|
22
|
+
# Context & Taint Provenance
|
|
23
|
+
context_sources: List[str] = Field(default_factory=list)
|
|
24
|
+
taint_state: str = "CLEAN"
|
|
25
|
+
context_ids: List[str] = Field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
# Authority & Capability
|
|
28
|
+
requested_tool: str
|
|
29
|
+
requested_capability: Optional[str] = None
|
|
30
|
+
required_capabilities: List[str] = Field(default_factory=list)
|
|
31
|
+
delegated_capabilities: List[str] = Field(default_factory=list)
|
|
32
|
+
authority_contained: bool = True
|
|
33
|
+
authority_reason: str = ""
|
|
34
|
+
|
|
35
|
+
# Intent Alignment
|
|
36
|
+
intent_aligned: bool = True
|
|
37
|
+
intent_explanation: str = ""
|
|
38
|
+
|
|
39
|
+
# APIRIS Tool Intelligence
|
|
40
|
+
apiris_analysis: Optional[Dict[str, Any]] = None
|
|
41
|
+
apiris_unavailable: bool = False
|
|
42
|
+
|
|
43
|
+
# AI Secura Contextual Reasoning
|
|
44
|
+
ai_analysis: Optional[Dict[str, Any]] = None
|
|
45
|
+
ai_unavailable: bool = False
|
|
46
|
+
|
|
47
|
+
# Deterministic Policy Decision
|
|
48
|
+
policy_decision: str = "UNKNOWN"
|
|
49
|
+
policy_reason_code: str = ""
|
|
50
|
+
policy_explanation: str = ""
|
|
51
|
+
risk_score: float = 0.0
|
|
52
|
+
|
|
53
|
+
# Runtime Execution Evidence
|
|
54
|
+
tool_executed: bool = False
|
|
55
|
+
execution_count: int = 0
|
|
56
|
+
sensitive_db_calls: int = 0
|
|
57
|
+
|
|
58
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
59
|
+
|
|
60
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
61
|
+
"""Convert evidence to structured dictionary."""
|
|
62
|
+
return self.model_dump(mode="json")
|
|
63
|
+
|