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
actshield/llm_config.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""LLM provider configuration for ActShield AI Secura reasoning engine.
|
|
2
|
+
|
|
3
|
+
Reads from environment variables. No hardcoded values.
|
|
4
|
+
|
|
5
|
+
Variables:
|
|
6
|
+
AGENTGUARD_LLM_PROVIDER : 'ollama' or 'local' (default: 'local')
|
|
7
|
+
AGENTGUARD_LLM_MODEL : model name (default: 'qwen2.5:7b')
|
|
8
|
+
AGENTGUARD_OLLAMA_BASE_URL: Ollama server URL (default: 'http://localhost:11434')
|
|
9
|
+
AGENTGUARD_LLM_TIMEOUT : request timeout seconds (default: 30)
|
|
10
|
+
AGENTGUARD_LLM_MAX_TOKENS : max response tokens (default: 2048)
|
|
11
|
+
AGENTGUARD_LLM_TEMPERATURE: sampling temperature (default: 0.1)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from typing import Literal
|
|
16
|
+
from pydantic import BaseModel, Field
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LLMConfig(BaseModel):
|
|
20
|
+
"""Runtime configuration for the AI Secura LLM reasoning engine."""
|
|
21
|
+
|
|
22
|
+
provider: Literal["ollama", "local"] = Field(
|
|
23
|
+
default="local",
|
|
24
|
+
description="LLM provider: 'ollama' for real AI reasoning, 'local' for deterministic heuristic adapter",
|
|
25
|
+
)
|
|
26
|
+
model: str = Field(
|
|
27
|
+
default="qwen2.5:7b",
|
|
28
|
+
description="Model identifier (e.g. 'qwen2.5:7b', 'ai-secura')",
|
|
29
|
+
)
|
|
30
|
+
ollama_base_url: str = Field(
|
|
31
|
+
default="http://localhost:11434",
|
|
32
|
+
description="Base URL for the Ollama server",
|
|
33
|
+
)
|
|
34
|
+
timeout_seconds: int = Field(
|
|
35
|
+
default=60,
|
|
36
|
+
ge=5,
|
|
37
|
+
le=300,
|
|
38
|
+
description="HTTP request timeout in seconds for Ollama calls",
|
|
39
|
+
)
|
|
40
|
+
max_tokens: int = Field(
|
|
41
|
+
default=512,
|
|
42
|
+
ge=256,
|
|
43
|
+
le=8192,
|
|
44
|
+
description="Maximum tokens the model may generate per analysis",
|
|
45
|
+
)
|
|
46
|
+
temperature: float = Field(
|
|
47
|
+
default=0.1,
|
|
48
|
+
ge=0.0,
|
|
49
|
+
le=1.0,
|
|
50
|
+
description="Sampling temperature. Low values produce deterministic, consistent security analysis.",
|
|
51
|
+
)
|
|
52
|
+
num_ctx: int = Field(
|
|
53
|
+
default=8192,
|
|
54
|
+
ge=2048,
|
|
55
|
+
description="Ollama context window size in tokens",
|
|
56
|
+
)
|
|
57
|
+
repair_on_invalid_json: bool = Field(
|
|
58
|
+
default=True,
|
|
59
|
+
description="Attempt exactly one JSON repair request if the initial response is malformed",
|
|
60
|
+
)
|
|
61
|
+
fallback_to_local_on_error: bool = Field(
|
|
62
|
+
default=True,
|
|
63
|
+
description="Return an ai_unavailable=True analysis when Ollama is unreachable",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_env(cls) -> "LLMConfig":
|
|
68
|
+
return cls(
|
|
69
|
+
provider=os.environ.get("AGENTGUARD_LLM_PROVIDER", "local"), # type: ignore[arg-type]
|
|
70
|
+
model=os.environ.get("AGENTGUARD_LLM_MODEL", "qwen2.5:7b"),
|
|
71
|
+
ollama_base_url=os.environ.get("AGENTGUARD_OLLAMA_BASE_URL", "http://localhost:11434"),
|
|
72
|
+
timeout_seconds=int(os.environ.get("AGENTGUARD_LLM_TIMEOUT", "30")),
|
|
73
|
+
max_tokens=int(os.environ.get("AGENTGUARD_LLM_MAX_TOKENS", "2048")),
|
|
74
|
+
temperature=float(os.environ.get("AGENTGUARD_LLM_TEMPERATURE", "0.1")),
|
|
75
|
+
num_ctx=int(os.environ.get("AGENTGUARD_LLM_NUM_CTX", "8192")),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def ollama_generate_url(self) -> str:
|
|
80
|
+
return f"{self.ollama_base_url.rstrip('/')}/api/generate"
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def ollama_tags_url(self) -> str:
|
|
84
|
+
return f"{self.ollama_base_url.rstrip('/')}/api/tags"
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def is_ollama(self) -> bool:
|
|
88
|
+
return self.provider == "ollama"
|
|
89
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""ActShield Offensive Security Validation Engine."""
|
|
2
|
+
|
|
3
|
+
from actshield.offensive.attack import (
|
|
4
|
+
AttackCase,
|
|
5
|
+
AttackType,
|
|
6
|
+
ExpectedBehavior,
|
|
7
|
+
SafetyClass,
|
|
8
|
+
)
|
|
9
|
+
from actshield.offensive.target import (
|
|
10
|
+
AttackTarget,
|
|
11
|
+
TargetEnvironment,
|
|
12
|
+
TargetRegistry,
|
|
13
|
+
target_registry,
|
|
14
|
+
)
|
|
15
|
+
from actshield.offensive.safety import (
|
|
16
|
+
SafetyValidationResult,
|
|
17
|
+
SafetyValidator,
|
|
18
|
+
safety_validator,
|
|
19
|
+
)
|
|
20
|
+
from actshield.offensive.evidence import SecurityAnalysisEvidence
|
|
21
|
+
from actshield.offensive.results import (
|
|
22
|
+
AttackStatus,
|
|
23
|
+
ExecutionEvidence,
|
|
24
|
+
OffensiveAttackResult,
|
|
25
|
+
)
|
|
26
|
+
from actshield.offensive.evaluator import AttackEvaluator
|
|
27
|
+
from actshield.offensive.mutation import (
|
|
28
|
+
MutationEngine,
|
|
29
|
+
MutationStrategy,
|
|
30
|
+
)
|
|
31
|
+
from actshield.offensive.campaign import (
|
|
32
|
+
AttackCampaign,
|
|
33
|
+
CampaignSummary,
|
|
34
|
+
)
|
|
35
|
+
from actshield.offensive.corpus import (
|
|
36
|
+
AttackCorpus,
|
|
37
|
+
attack_corpus,
|
|
38
|
+
)
|
|
39
|
+
from actshield.offensive.generator import (
|
|
40
|
+
AIAttackGenerator,
|
|
41
|
+
OFFENSIVE_SYSTEM_PROMPT,
|
|
42
|
+
)
|
|
43
|
+
from actshield.offensive.vulnerable_target import VulnerableDemoTarget
|
|
44
|
+
from actshield.offensive.engine import OffensiveEngine
|
|
45
|
+
from actshield.offensive.adaptive import (
|
|
46
|
+
AdaptiveAttackSelector,
|
|
47
|
+
AdaptiveEngine,
|
|
48
|
+
BoundarySearchEngine,
|
|
49
|
+
CampaignMemory,
|
|
50
|
+
DefenseCategory,
|
|
51
|
+
DefenseFeedback,
|
|
52
|
+
FeedbackAnalyzer,
|
|
53
|
+
MutationNode,
|
|
54
|
+
MutationPlanner,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"AttackCase",
|
|
59
|
+
"AttackType",
|
|
60
|
+
"ExpectedBehavior",
|
|
61
|
+
"SafetyClass",
|
|
62
|
+
"AttackTarget",
|
|
63
|
+
"TargetEnvironment",
|
|
64
|
+
"TargetRegistry",
|
|
65
|
+
"target_registry",
|
|
66
|
+
"SafetyValidationResult",
|
|
67
|
+
"SafetyValidator",
|
|
68
|
+
"safety_validator",
|
|
69
|
+
"SecurityAnalysisEvidence",
|
|
70
|
+
"AttackStatus",
|
|
71
|
+
"ExecutionEvidence",
|
|
72
|
+
"OffensiveAttackResult",
|
|
73
|
+
"AttackEvaluator",
|
|
74
|
+
"MutationEngine",
|
|
75
|
+
"MutationStrategy",
|
|
76
|
+
"AttackCampaign",
|
|
77
|
+
"CampaignSummary",
|
|
78
|
+
"AttackCorpus",
|
|
79
|
+
"attack_corpus",
|
|
80
|
+
"AIAttackGenerator",
|
|
81
|
+
"OFFENSIVE_SYSTEM_PROMPT",
|
|
82
|
+
"VulnerableDemoTarget",
|
|
83
|
+
"OffensiveEngine",
|
|
84
|
+
"AdaptiveAttackSelector",
|
|
85
|
+
"AdaptiveEngine",
|
|
86
|
+
"BoundarySearchEngine",
|
|
87
|
+
"CampaignMemory",
|
|
88
|
+
"DefenseCategory",
|
|
89
|
+
"DefenseFeedback",
|
|
90
|
+
"FeedbackAnalyzer",
|
|
91
|
+
"MutationNode",
|
|
92
|
+
"MutationPlanner",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""ActShield Adaptive Offensive Validation Subsystem."""
|
|
2
|
+
|
|
3
|
+
from actshield.offensive.adaptive.campaign_memory import CampaignMemory, MutationNode
|
|
4
|
+
from actshield.offensive.adaptive.feedback import DefenseCategory, DefenseFeedback, FeedbackAnalyzer
|
|
5
|
+
from actshield.offensive.adaptive.mutation_planner import MutationPlanner
|
|
6
|
+
from actshield.offensive.adaptive.attack_selector import AdaptiveAttackSelector
|
|
7
|
+
from actshield.offensive.adaptive.boundary_search import BoundarySearchEngine
|
|
8
|
+
from actshield.offensive.adaptive.adaptive_engine import AdaptiveEngine
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"CampaignMemory",
|
|
12
|
+
"MutationNode",
|
|
13
|
+
"DefenseCategory",
|
|
14
|
+
"DefenseFeedback",
|
|
15
|
+
"FeedbackAnalyzer",
|
|
16
|
+
"MutationPlanner",
|
|
17
|
+
"AdaptiveAttackSelector",
|
|
18
|
+
"BoundarySearchEngine",
|
|
19
|
+
"AdaptiveEngine",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Adaptive Offensive Engine — Master orchestrator for adaptive validation campaigns."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import pathlib
|
|
7
|
+
import time
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
from actshield.offensive.attack import AttackCase, AttackType
|
|
12
|
+
from actshield.offensive.campaign import AttackCampaign, CampaignSummary
|
|
13
|
+
from actshield.offensive.corpus import AttackCorpus, attack_corpus
|
|
14
|
+
from actshield.offensive.engine import OffensiveEngine
|
|
15
|
+
from actshield.offensive.results import AttackStatus, OffensiveAttackResult
|
|
16
|
+
from actshield.offensive.safety import SafetyValidator, safety_validator
|
|
17
|
+
from actshield.offensive.target import AttackTarget, TargetRegistry, target_registry
|
|
18
|
+
from actshield.offensive.adaptive.attack_selector import AdaptiveAttackSelector
|
|
19
|
+
from actshield.offensive.adaptive.boundary_search import BoundarySearchEngine
|
|
20
|
+
from actshield.offensive.adaptive.campaign_memory import CampaignMemory, MutationNode
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("actshield.offensive.adaptive.engine")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AdaptiveEngine:
|
|
26
|
+
"""Orchestrates adaptive multi-depth offensive campaigns against ActShield defenses."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
engine: Optional[OffensiveEngine] = None,
|
|
31
|
+
corpus: Optional[AttackCorpus] = None,
|
|
32
|
+
registry: Optional[TargetRegistry] = None,
|
|
33
|
+
validator: Optional[SafetyValidator] = None,
|
|
34
|
+
max_depth: int = 3,
|
|
35
|
+
branch_factor: int = 2,
|
|
36
|
+
seed: Optional[int] = 20260919,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.engine = engine or OffensiveEngine(corpus=corpus, registry=registry, validator=validator)
|
|
39
|
+
self.corpus = corpus or attack_corpus
|
|
40
|
+
self.registry = registry or target_registry
|
|
41
|
+
self.validator = validator or safety_validator
|
|
42
|
+
self.max_depth = max_depth
|
|
43
|
+
self.branch_factor = branch_factor
|
|
44
|
+
self.seed = seed
|
|
45
|
+
self.selector = AdaptiveAttackSelector(corpus=self.corpus, seed=seed)
|
|
46
|
+
|
|
47
|
+
def run_adaptive_campaign(
|
|
48
|
+
self,
|
|
49
|
+
family: Optional[str] = None,
|
|
50
|
+
attack_id: Optional[str] = None,
|
|
51
|
+
limit_seeds: Optional[int] = None,
|
|
52
|
+
target: Optional[AttackTarget] = None,
|
|
53
|
+
campaign_name: str = "Adaptive Offensive Campaign",
|
|
54
|
+
) -> Tuple[List[OffensiveAttackResult], CampaignSummary, CampaignMemory]:
|
|
55
|
+
"""Execute a full adaptive validation campaign."""
|
|
56
|
+
start_time = time.perf_counter()
|
|
57
|
+
target = target or self.registry.get("ActShield-demo")
|
|
58
|
+
campaign_id = f"camp_adapt_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}"
|
|
59
|
+
memory = CampaignMemory(campaign_id=campaign_id)
|
|
60
|
+
|
|
61
|
+
search_engine = BoundarySearchEngine(
|
|
62
|
+
engine=self.engine,
|
|
63
|
+
memory=memory,
|
|
64
|
+
max_depth=self.max_depth,
|
|
65
|
+
branch_factor=self.branch_factor,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
seed_attacks = self.selector.select_seeds(family=family, attack_id=attack_id, limit=limit_seeds)
|
|
69
|
+
all_results: List[OffensiveAttackResult] = []
|
|
70
|
+
|
|
71
|
+
campaign = AttackCampaign(
|
|
72
|
+
campaign_id=campaign_id,
|
|
73
|
+
name=campaign_name,
|
|
74
|
+
objective=f"Adaptive exploration of defense boundaries (Max Depth: {self.max_depth})",
|
|
75
|
+
target=target,
|
|
76
|
+
attacks=seed_attacks,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
for seed_attack in seed_attacks:
|
|
80
|
+
branch_results = search_engine.explore_boundary(
|
|
81
|
+
root_attack=seed_attack,
|
|
82
|
+
target=target,
|
|
83
|
+
campaign_id=campaign_id,
|
|
84
|
+
)
|
|
85
|
+
all_results.extend(branch_results)
|
|
86
|
+
|
|
87
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
88
|
+
|
|
89
|
+
# Calculate enhanced scorecard
|
|
90
|
+
summary = CampaignSummary.from_results(
|
|
91
|
+
campaign=campaign,
|
|
92
|
+
results=all_results,
|
|
93
|
+
memory=memory,
|
|
94
|
+
seed=self.seed,
|
|
95
|
+
duration_ms=duration_ms,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return all_results, summary, memory
|
|
99
|
+
|
|
100
|
+
def export_dashboard_json(
|
|
101
|
+
self,
|
|
102
|
+
summary: CampaignSummary,
|
|
103
|
+
results: List[OffensiveAttackResult],
|
|
104
|
+
memory: CampaignMemory,
|
|
105
|
+
output_dir: Optional[str] = None,
|
|
106
|
+
) -> Dict[str, str]:
|
|
107
|
+
"""Export comprehensive dashboard-ready JSON artifacts."""
|
|
108
|
+
if output_dir:
|
|
109
|
+
out_path = pathlib.Path(output_dir)
|
|
110
|
+
else:
|
|
111
|
+
root = pathlib.Path(__file__).resolve().parent.parent.parent.parent.parent
|
|
112
|
+
out_path = root / "reports" / "phase5"
|
|
113
|
+
|
|
114
|
+
out_path.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
|
|
116
|
+
# 1. Dashboard Master Package
|
|
117
|
+
dashboard_payload = {
|
|
118
|
+
"campaign": {
|
|
119
|
+
"campaign_id": summary.campaign_id,
|
|
120
|
+
"name": summary.name,
|
|
121
|
+
"target_id": summary.target_id,
|
|
122
|
+
"timestamp": summary.timestamp.isoformat(),
|
|
123
|
+
"seed": summary.campaign_seed,
|
|
124
|
+
"duration_ms": summary.duration_ms,
|
|
125
|
+
},
|
|
126
|
+
"coverage": {
|
|
127
|
+
"total_attacks": summary.total_attacks,
|
|
128
|
+
"attack_family_breakdown": summary.coverage_breakdown,
|
|
129
|
+
"unique_attack_variants": summary.unique_attack_variants,
|
|
130
|
+
"max_mutation_depth": summary.max_mutation_depth,
|
|
131
|
+
"avg_mutation_depth": summary.avg_mutation_depth,
|
|
132
|
+
},
|
|
133
|
+
"security": {
|
|
134
|
+
"blocked": summary.blocked,
|
|
135
|
+
"bypassed": summary.bypassed,
|
|
136
|
+
"block_rate_pct": summary.block_rate_pct,
|
|
137
|
+
"sensitive_actions_attempted": summary.sensitive_actions_attempted,
|
|
138
|
+
"sensitive_actions_executed": summary.sensitive_actions_executed,
|
|
139
|
+
"empirical_prevention_rate": summary.empirical_prevention_rate,
|
|
140
|
+
"ai_analysis_availability_pct": summary.ai_analysis_availability_pct,
|
|
141
|
+
"apiris_analysis_availability_pct": summary.apiris_analysis_availability_pct,
|
|
142
|
+
"defense_reasons_breakdown": summary.defense_reasons_breakdown,
|
|
143
|
+
},
|
|
144
|
+
"performance": {
|
|
145
|
+
"mean_latency_ms": summary.mean_latency_ms,
|
|
146
|
+
"p95_latency_ms": summary.p95_latency_ms,
|
|
147
|
+
},
|
|
148
|
+
"mutations": [n.to_dict() for n in memory.get_all_nodes()],
|
|
149
|
+
"bypasses": [n.to_dict() for n in memory.get_bypasses()],
|
|
150
|
+
"regressions": summary.regression_fixtures_generated,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
dash_file = out_path / "adaptive_dashboard.json"
|
|
154
|
+
dash_file.write_text(json.dumps(dashboard_payload, indent=2), encoding="utf-8")
|
|
155
|
+
|
|
156
|
+
# 2. Markdown Summary
|
|
157
|
+
md_file = out_path / "adaptive_campaign_summary.md"
|
|
158
|
+
md_file.write_text(summary.to_markdown(), encoding="utf-8")
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
"dashboard_json": str(dash_file),
|
|
162
|
+
"summary_md": str(md_file),
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Adaptive attack selector providing deterministic seed selection across attack families."""
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
from actshield.offensive.attack import AttackCase, AttackType
|
|
6
|
+
from actshield.offensive.corpus import AttackCorpus, attack_corpus
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AdaptiveAttackSelector:
|
|
10
|
+
"""Selects base seed attacks from the corpus for adaptive exploration."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, corpus: Optional[AttackCorpus] = None, seed: Optional[int] = None) -> None:
|
|
13
|
+
self.corpus = corpus or attack_corpus
|
|
14
|
+
self.seed = seed
|
|
15
|
+
if seed is not None:
|
|
16
|
+
random.seed(seed)
|
|
17
|
+
|
|
18
|
+
def select_seeds(
|
|
19
|
+
self,
|
|
20
|
+
family: Optional[str] = None,
|
|
21
|
+
attack_id: Optional[str] = None,
|
|
22
|
+
limit: Optional[int] = None,
|
|
23
|
+
) -> List[AttackCase]:
|
|
24
|
+
"""Select seed attacks matching criteria."""
|
|
25
|
+
all_attacks = self.corpus.get_all_attacks()
|
|
26
|
+
|
|
27
|
+
if attack_id:
|
|
28
|
+
found = self.corpus.get_attack(attack_id)
|
|
29
|
+
return [found] if found else []
|
|
30
|
+
|
|
31
|
+
if family:
|
|
32
|
+
fam_clean = family.lower().replace("-", "_")
|
|
33
|
+
filtered = [
|
|
34
|
+
a for a in all_attacks
|
|
35
|
+
if fam_clean in a.attack_type.value.lower() or fam_clean in a.attack_id.lower()
|
|
36
|
+
]
|
|
37
|
+
else:
|
|
38
|
+
filtered = all_attacks
|
|
39
|
+
|
|
40
|
+
if self.seed is not None:
|
|
41
|
+
# Deterministic shuffle
|
|
42
|
+
rng = random.Random(self.seed)
|
|
43
|
+
shuffled = list(filtered)
|
|
44
|
+
rng.shuffle(shuffled)
|
|
45
|
+
filtered = shuffled
|
|
46
|
+
|
|
47
|
+
if limit and limit > 0:
|
|
48
|
+
return filtered[:limit]
|
|
49
|
+
|
|
50
|
+
return filtered
|
|
51
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Boundary search engine executing bounded adaptive exploration of defense limits."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional, Tuple
|
|
4
|
+
from actshield.offensive.attack import AttackCase
|
|
5
|
+
from actshield.offensive.engine import OffensiveEngine
|
|
6
|
+
from actshield.offensive.results import AttackStatus, OffensiveAttackResult
|
|
7
|
+
from actshield.offensive.target import AttackTarget
|
|
8
|
+
from actshield.offensive.adaptive.campaign_memory import CampaignMemory
|
|
9
|
+
from actshield.offensive.adaptive.feedback import FeedbackAnalyzer
|
|
10
|
+
from actshield.offensive.adaptive.mutation_planner import MutationPlanner
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BoundarySearchEngine:
|
|
14
|
+
"""Explores the decision boundary around an attack vector until bypass or max depth."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
engine: OffensiveEngine,
|
|
19
|
+
memory: CampaignMemory,
|
|
20
|
+
max_depth: int = 3,
|
|
21
|
+
branch_factor: int = 2,
|
|
22
|
+
max_attempts_per_attack: int = 10,
|
|
23
|
+
) -> None:
|
|
24
|
+
self.engine = engine
|
|
25
|
+
self.memory = memory
|
|
26
|
+
self.max_depth = max_depth
|
|
27
|
+
self.branch_factor = branch_factor
|
|
28
|
+
self.max_attempts_per_attack = max_attempts_per_attack
|
|
29
|
+
|
|
30
|
+
def explore_boundary(
|
|
31
|
+
self,
|
|
32
|
+
root_attack: AttackCase,
|
|
33
|
+
target: Optional[AttackTarget] = None,
|
|
34
|
+
campaign_id: Optional[str] = None,
|
|
35
|
+
) -> List[OffensiveAttackResult]:
|
|
36
|
+
"""Execute bounded adaptive exploration starting from root_attack."""
|
|
37
|
+
results: List[OffensiveAttackResult] = []
|
|
38
|
+
attempts = 0
|
|
39
|
+
|
|
40
|
+
# Queue items: (AttackCase, depth, parent_id, strategy_name)
|
|
41
|
+
queue: List[Tuple[AttackCase, int, Optional[str], str]] = [(root_attack, 0, None, "baseline")]
|
|
42
|
+
|
|
43
|
+
while queue and attempts < self.max_attempts_per_attack:
|
|
44
|
+
current_attack, depth, parent_id, strategy = queue.pop(0)
|
|
45
|
+
attempts += 1
|
|
46
|
+
|
|
47
|
+
# Get lineage so far
|
|
48
|
+
lineage_ids = [n.mutation_id for n in self.memory.get_lineage(parent_id)] if parent_id else []
|
|
49
|
+
lineage_ids.append(current_attack.attack_id)
|
|
50
|
+
|
|
51
|
+
# Execute attack through full security pipeline
|
|
52
|
+
res = self.engine.execute_attack(
|
|
53
|
+
attack=current_attack,
|
|
54
|
+
target=target,
|
|
55
|
+
campaign_id=campaign_id,
|
|
56
|
+
mutation_lineage=lineage_ids,
|
|
57
|
+
)
|
|
58
|
+
results.append(res)
|
|
59
|
+
|
|
60
|
+
# Record in campaign memory
|
|
61
|
+
self.memory.record_node(
|
|
62
|
+
mutation_id=current_attack.attack_id,
|
|
63
|
+
root_attack_id=root_attack.attack_id,
|
|
64
|
+
parent_id=parent_id,
|
|
65
|
+
strategy=strategy,
|
|
66
|
+
depth=depth,
|
|
67
|
+
input_payload=root_attack.payload,
|
|
68
|
+
output_payload=current_attack.payload,
|
|
69
|
+
defense_reason=res.execution_evidence.policy_reason,
|
|
70
|
+
decision=res.actual_decision,
|
|
71
|
+
status=res.status.value,
|
|
72
|
+
sensitive_executions=res.execution_evidence.sensitive_db_calls,
|
|
73
|
+
latency_ms=res.latency_ms,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# INVARIANT: If bypass is discovered, STOP exploring this branch immediately!
|
|
77
|
+
if res.bypassed:
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
# If not bypassed and depth < max_depth, generate targeted mutations
|
|
81
|
+
if depth < self.max_depth:
|
|
82
|
+
feedback = FeedbackAnalyzer.analyze(res)
|
|
83
|
+
next_mutations = MutationPlanner.plan_next_mutations(
|
|
84
|
+
parent_attack=current_attack,
|
|
85
|
+
feedback=feedback,
|
|
86
|
+
depth=depth + 1,
|
|
87
|
+
branch_factor=self.branch_factor,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
for mut_case in next_mutations:
|
|
91
|
+
queue.append((
|
|
92
|
+
mut_case,
|
|
93
|
+
depth + 1,
|
|
94
|
+
current_attack.attack_id,
|
|
95
|
+
mut_case.mutation_strategy or "mutation",
|
|
96
|
+
))
|
|
97
|
+
|
|
98
|
+
return results
|
|
99
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Campaign Memory and Mutation Lineage Graph for Adaptive Offensive Validation."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MutationNode(BaseModel):
|
|
10
|
+
"""Represents a single node in an adaptive attack mutation lineage tree."""
|
|
11
|
+
|
|
12
|
+
mutation_id: str = Field(..., description="Unique ID for this mutation attempt")
|
|
13
|
+
parent_id: Optional[str] = Field(default=None, description="Parent attack/mutation ID")
|
|
14
|
+
root_attack_id: str = Field(..., description="The original baseline attack ID")
|
|
15
|
+
strategy: str = Field(default="baseline", description="Mutation strategy applied")
|
|
16
|
+
depth: int = Field(default=0, description="Mutation depth from baseline root (0 = root)")
|
|
17
|
+
|
|
18
|
+
input_payload: str = Field(default="", description="Input payload before mutation")
|
|
19
|
+
output_payload: str = Field(default="", description="Output payload after mutation")
|
|
20
|
+
input_hash: str = Field(default="", description="SHA-256 hash of input payload")
|
|
21
|
+
output_hash: str = Field(default="", description="SHA-256 hash of output payload")
|
|
22
|
+
|
|
23
|
+
defense_reason: str = Field(default="", description="Defensive policy reason code rendered")
|
|
24
|
+
decision: str = Field(default="UNKNOWN", description="Policy decision (BLOCK/ALLOW/etc.)")
|
|
25
|
+
status: str = Field(default="PASS", description="Attack status (PASS/BYPASS/ERROR/REFUSED)")
|
|
26
|
+
sensitive_executions: int = Field(default=0, description="Count of unauthorized sensitive sink calls")
|
|
27
|
+
latency_ms: float = Field(default=0.0, description="Execution latency in milliseconds")
|
|
28
|
+
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def compute_hash(text: str) -> str:
|
|
32
|
+
"""Compute 8-char truncated SHA-256 hash of text."""
|
|
33
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()[:12]
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
36
|
+
"""Convert node to dictionary."""
|
|
37
|
+
return self.model_dump(mode="json")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CampaignMemory:
|
|
41
|
+
"""Stores the full adaptive exploration graph, lineage trees, and defense responses."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, campaign_id: Optional[str] = None) -> None:
|
|
44
|
+
self.campaign_id = campaign_id or f"camp_mem_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}"
|
|
45
|
+
self.nodes: Dict[str, MutationNode] = {}
|
|
46
|
+
self.children_map: Dict[str, List[str]] = {}
|
|
47
|
+
self.bypasses: List[str] = []
|
|
48
|
+
|
|
49
|
+
def record_node(
|
|
50
|
+
self,
|
|
51
|
+
mutation_id: str,
|
|
52
|
+
root_attack_id: str,
|
|
53
|
+
parent_id: Optional[str] = None,
|
|
54
|
+
strategy: str = "baseline",
|
|
55
|
+
depth: int = 0,
|
|
56
|
+
input_payload: str = "",
|
|
57
|
+
output_payload: str = "",
|
|
58
|
+
defense_reason: str = "",
|
|
59
|
+
decision: str = "UNKNOWN",
|
|
60
|
+
status: str = "PASS",
|
|
61
|
+
sensitive_executions: int = 0,
|
|
62
|
+
latency_ms: float = 0.0,
|
|
63
|
+
) -> MutationNode:
|
|
64
|
+
"""Record an attack attempt into campaign memory."""
|
|
65
|
+
node = MutationNode(
|
|
66
|
+
mutation_id=mutation_id,
|
|
67
|
+
parent_id=parent_id,
|
|
68
|
+
root_attack_id=root_attack_id,
|
|
69
|
+
strategy=strategy,
|
|
70
|
+
depth=depth,
|
|
71
|
+
input_payload=input_payload,
|
|
72
|
+
output_payload=output_payload,
|
|
73
|
+
input_hash=MutationNode.compute_hash(input_payload),
|
|
74
|
+
output_hash=MutationNode.compute_hash(output_payload),
|
|
75
|
+
defense_reason=defense_reason,
|
|
76
|
+
decision=decision,
|
|
77
|
+
status=status,
|
|
78
|
+
sensitive_executions=sensitive_executions,
|
|
79
|
+
latency_ms=latency_ms,
|
|
80
|
+
)
|
|
81
|
+
self.nodes[mutation_id] = node
|
|
82
|
+
|
|
83
|
+
if parent_id:
|
|
84
|
+
if parent_id not in self.children_map:
|
|
85
|
+
self.children_map[parent_id] = []
|
|
86
|
+
self.children_map[parent_id].append(mutation_id)
|
|
87
|
+
|
|
88
|
+
if status == "BYPASS" or sensitive_executions > 0:
|
|
89
|
+
if mutation_id not in self.bypasses:
|
|
90
|
+
self.bypasses.append(mutation_id)
|
|
91
|
+
|
|
92
|
+
return node
|
|
93
|
+
|
|
94
|
+
def get_lineage(self, attack_or_mutation_id: str) -> List[MutationNode]:
|
|
95
|
+
"""Return root-to-leaf sequence of MutationNodes for the given attack/mutation ID."""
|
|
96
|
+
lineage: List[MutationNode] = []
|
|
97
|
+
curr_id: Optional[str] = attack_or_mutation_id
|
|
98
|
+
|
|
99
|
+
while curr_id and curr_id in self.nodes:
|
|
100
|
+
node = self.nodes[curr_id]
|
|
101
|
+
lineage.append(node)
|
|
102
|
+
curr_id = node.parent_id
|
|
103
|
+
|
|
104
|
+
lineage.reverse()
|
|
105
|
+
return lineage
|
|
106
|
+
|
|
107
|
+
def get_all_nodes(self) -> List[MutationNode]:
|
|
108
|
+
"""Return all recorded mutation nodes."""
|
|
109
|
+
return list(self.nodes.values())
|
|
110
|
+
|
|
111
|
+
def get_bypasses(self) -> List[MutationNode]:
|
|
112
|
+
"""Return all nodes where a bypass was discovered."""
|
|
113
|
+
return [self.nodes[bid] for bid in self.bypasses if bid in self.nodes]
|
|
114
|
+
|
|
115
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
116
|
+
"""Export campaign memory to dictionary."""
|
|
117
|
+
return {
|
|
118
|
+
"campaign_id": self.campaign_id,
|
|
119
|
+
"total_nodes": len(self.nodes),
|
|
120
|
+
"total_bypasses": len(self.bypasses),
|
|
121
|
+
"nodes": [n.to_dict() for n in self.nodes.values()],
|
|
122
|
+
"bypasses": self.bypasses,
|
|
123
|
+
}
|