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/client.py
ADDED
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
"""ActShield central client and security runtime orchestrator."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
import functools
|
|
8
|
+
import inspect
|
|
9
|
+
|
|
10
|
+
from actshield.agents.agent import Agent
|
|
11
|
+
from actshield.agents.identity import AgentCapability, AgentIdentity, AgentStatus, AgentTrustLevel
|
|
12
|
+
from actshield.config import ActShieldConfig
|
|
13
|
+
from actshield.context.context import Context
|
|
14
|
+
from actshield.context.provenance import ContextSource, Provenance
|
|
15
|
+
from actshield.context.taint import TaintState
|
|
16
|
+
from actshield.decisions.decision import SecurityDecision
|
|
17
|
+
from actshield.delegation.authority import AuthorityGrant
|
|
18
|
+
from actshield.delegation.delegation import Delegation, DelegationScope
|
|
19
|
+
from actshield.integrations.ai_secura import (
|
|
20
|
+
LocalAISecuraAdapter,
|
|
21
|
+
SecurityReasoner,
|
|
22
|
+
)
|
|
23
|
+
from actshield.integrations.apiris import (
|
|
24
|
+
APIIntelligence,
|
|
25
|
+
LocalAPIRISAdapter,
|
|
26
|
+
)
|
|
27
|
+
from actshield.approval.approval import ApprovalManager
|
|
28
|
+
from actshield.gateway.http import HTTPGateway
|
|
29
|
+
from actshield.gateway.mcp import MCPGateway
|
|
30
|
+
from actshield.persistence.storage import SQLiteStorage, StorageBackend
|
|
31
|
+
from actshield.policy.evaluator import PolicyEvaluator
|
|
32
|
+
from actshield.policy.intent import IntentAnalyzer
|
|
33
|
+
from actshield.tasks.task import Task, TaskContext
|
|
34
|
+
from actshield.tools.interceptor import protected_tool as _protected_tool_decorator
|
|
35
|
+
from actshield.tools.tool import Resource, SensitivityLevel, ToolDefinition, ToolRequest
|
|
36
|
+
from actshield.tracing.correlation import (
|
|
37
|
+
CorrelationContext,
|
|
38
|
+
SpanScope,
|
|
39
|
+
generate_id,
|
|
40
|
+
get_current_agent_id,
|
|
41
|
+
get_current_context,
|
|
42
|
+
get_current_delegation_id,
|
|
43
|
+
get_current_span_id,
|
|
44
|
+
get_current_task_id,
|
|
45
|
+
get_current_trace_id,
|
|
46
|
+
)
|
|
47
|
+
from actshield.tracing.events import EventType, SecurityEvent
|
|
48
|
+
from actshield.tracing.tracer import CausalGraph, TraceManager
|
|
49
|
+
from actshield.posture.posture_models import SecurityPostureSnapshot
|
|
50
|
+
from actshield.posture.posture_engine import PostureEngine
|
|
51
|
+
from actshield.incidents.incident_engine import IncidentEngine
|
|
52
|
+
from actshield.response.response_engine import ResponseEngine
|
|
53
|
+
from actshield.drift.drift_engine import BehavioralBaselineTracker
|
|
54
|
+
from actshield.gates.security_gate import SecurityGateEvaluator
|
|
55
|
+
from actshield.providers.base import SecurityAIProvider
|
|
56
|
+
from actshield.providers.registry import get_provider_registry
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ActShield:
|
|
60
|
+
"""The central ActShield SDK client.
|
|
61
|
+
|
|
62
|
+
Provides causal security tracking, authority delegation governance, context taint propagation,
|
|
63
|
+
tool interception, and deterministic policy enforcement for multi-agent AI systems.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
config: Optional[ActShieldConfig] = None,
|
|
69
|
+
mode: str = "strict",
|
|
70
|
+
enforcement_mode: Optional[str] = None,
|
|
71
|
+
ai_provider: Optional[Union[SecurityAIProvider, str]] = None,
|
|
72
|
+
ai_secura: Optional[SecurityReasoner] = None,
|
|
73
|
+
apiris: Optional[APIIntelligence] = None,
|
|
74
|
+
intent_analyzer: Optional[IntentAnalyzer] = None,
|
|
75
|
+
storage: Optional[StorageBackend] = None,
|
|
76
|
+
) -> None:
|
|
77
|
+
self.config = config or ActShieldConfig()
|
|
78
|
+
if enforcement_mode is not None:
|
|
79
|
+
self.config.enforcement_mode = enforcement_mode
|
|
80
|
+
self.mode = enforcement_mode or mode
|
|
81
|
+
self.tracer = TraceManager()
|
|
82
|
+
self.storage: StorageBackend = storage or SQLiteStorage(":memory:")
|
|
83
|
+
self.approvals = ApprovalManager(self)
|
|
84
|
+
self.policy_evaluator = PolicyEvaluator(self)
|
|
85
|
+
if intent_analyzer:
|
|
86
|
+
self.policy_evaluator.intent_analyzer = intent_analyzer
|
|
87
|
+
|
|
88
|
+
# Pluggable AI Provider Registry
|
|
89
|
+
self.provider_registry = get_provider_registry()
|
|
90
|
+
if isinstance(ai_provider, str):
|
|
91
|
+
self.provider_registry.set_active_provider(ai_provider)
|
|
92
|
+
elif isinstance(ai_provider, SecurityAIProvider):
|
|
93
|
+
self.provider_registry.register(ai_provider, is_default=True)
|
|
94
|
+
|
|
95
|
+
# Adapters (Dependency Inversion)
|
|
96
|
+
self.ai_secura_adapter: SecurityReasoner = ai_secura or LocalAISecuraAdapter()
|
|
97
|
+
self.apiris_adapter: APIIntelligence = apiris or LocalAPIRISAdapter()
|
|
98
|
+
|
|
99
|
+
# In-memory registries
|
|
100
|
+
self.agent_registry: Dict[str, Agent] = {}
|
|
101
|
+
self.tool_registry: Dict[str, ToolDefinition] = {}
|
|
102
|
+
self.delegation_registry: Dict[str, Delegation] = {}
|
|
103
|
+
self.context_registry: Dict[str, Context] = {}
|
|
104
|
+
|
|
105
|
+
# Phase 9: Continuous Security Control Plane Engines
|
|
106
|
+
self.posture = PostureEngine(guard=self)
|
|
107
|
+
self.incidents = IncidentEngine(guard=self)
|
|
108
|
+
self.response = ResponseEngine(guard=self)
|
|
109
|
+
self.drift = BehavioralBaselineTracker(guard=self)
|
|
110
|
+
self.gates = SecurityGateEvaluator()
|
|
111
|
+
self._is_running: bool = False
|
|
112
|
+
|
|
113
|
+
def start(self) -> "ActShield":
|
|
114
|
+
"""Initialize and mark the security runtime active."""
|
|
115
|
+
self._is_running = True
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def is_running(self) -> bool:
|
|
120
|
+
return self._is_running
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def policy(self) -> PolicyEvaluator:
|
|
124
|
+
"""Alias for policy_evaluator."""
|
|
125
|
+
return self.policy_evaluator
|
|
126
|
+
|
|
127
|
+
def get_security_posture(
|
|
128
|
+
self,
|
|
129
|
+
events: Optional[List[Dict[str, Any]]] = None,
|
|
130
|
+
regressions: Optional[List[Dict[str, Any]]] = None,
|
|
131
|
+
incidents: Optional[List[Dict[str, Any]]] = None,
|
|
132
|
+
agents: Optional[List[Dict[str, Any]]] = None,
|
|
133
|
+
environment: str = "production",
|
|
134
|
+
) -> SecurityPostureSnapshot:
|
|
135
|
+
"""Derive and return the current Security Posture Snapshot."""
|
|
136
|
+
return self.posture.evaluate_current_posture(
|
|
137
|
+
events=events,
|
|
138
|
+
regressions=regressions,
|
|
139
|
+
incidents=incidents,
|
|
140
|
+
agents=agents,
|
|
141
|
+
environment=environment,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def mcp_gateway(
|
|
145
|
+
self,
|
|
146
|
+
server_name: str = "upstream-mcp-server",
|
|
147
|
+
server_uri: str = "mcp://gateway.internal/v1",
|
|
148
|
+
) -> MCPGateway:
|
|
149
|
+
"""Create a Model Context Protocol (MCP) live security gateway."""
|
|
150
|
+
return MCPGateway(guard=self, server_name=server_name, server_uri=server_uri)
|
|
151
|
+
|
|
152
|
+
def http_gateway(
|
|
153
|
+
self,
|
|
154
|
+
mock_transport: Optional[Callable[[str, str, Dict[str, Any], Any], Dict[str, Any]]] = None,
|
|
155
|
+
) -> HTTPGateway:
|
|
156
|
+
"""Create an HTTP API live security gateway."""
|
|
157
|
+
return HTTPGateway(guard=self, mock_transport=mock_transport)
|
|
158
|
+
|
|
159
|
+
def register_agent(
|
|
160
|
+
self,
|
|
161
|
+
name: Union[str, AgentIdentity, Agent],
|
|
162
|
+
framework: str = "custom",
|
|
163
|
+
version: str = "1.0.0",
|
|
164
|
+
capabilities: Optional[List[Union[str, AgentCapability, Dict[str, Any]]]] = None,
|
|
165
|
+
trust_level: Union[str, AgentTrustLevel] = AgentTrustLevel.MEDIUM,
|
|
166
|
+
parent_agent_id: Optional[str] = None,
|
|
167
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
168
|
+
) -> Agent:
|
|
169
|
+
"""Register and return an Agent instance with full identity and capability specification."""
|
|
170
|
+
if isinstance(name, Agent):
|
|
171
|
+
agent_instance = name
|
|
172
|
+
agent_instance.guard = self
|
|
173
|
+
self.agent_registry[agent_instance.agent_id] = agent_instance
|
|
174
|
+
self.agent_registry[f"name:{agent_instance.name}"] = agent_instance
|
|
175
|
+
return agent_instance
|
|
176
|
+
|
|
177
|
+
if isinstance(name, AgentIdentity):
|
|
178
|
+
identity = name
|
|
179
|
+
agent_instance = Agent(identity=identity, guard=self)
|
|
180
|
+
self.agent_registry[agent_instance.agent_id] = agent_instance
|
|
181
|
+
self.agent_registry[f"name:{agent_instance.name}"] = agent_instance
|
|
182
|
+
return agent_instance
|
|
183
|
+
|
|
184
|
+
if isinstance(trust_level, str):
|
|
185
|
+
trust_level = AgentTrustLevel(trust_level.lower())
|
|
186
|
+
|
|
187
|
+
current_ctx = get_current_context()
|
|
188
|
+
effective_parent = parent_agent_id or (current_ctx.agent_id if current_ctx else None)
|
|
189
|
+
|
|
190
|
+
identity = AgentIdentity(
|
|
191
|
+
agent_id=generate_id("agt"),
|
|
192
|
+
name=name,
|
|
193
|
+
framework=framework,
|
|
194
|
+
version=version,
|
|
195
|
+
capabilities=capabilities or [], # type: ignore
|
|
196
|
+
trust_level=trust_level,
|
|
197
|
+
parent_agent_id=effective_parent,
|
|
198
|
+
metadata=metadata or {},
|
|
199
|
+
)
|
|
200
|
+
agent_instance = Agent(identity=identity, guard=self)
|
|
201
|
+
self.agent_registry[agent_instance.agent_id] = agent_instance
|
|
202
|
+
# Map by name as well for quick lookup if unique
|
|
203
|
+
self.agent_registry[f"name:{name}"] = agent_instance
|
|
204
|
+
|
|
205
|
+
# Emit agent.created event
|
|
206
|
+
self.emit_event(
|
|
207
|
+
event_type=EventType.AGENT_CREATED,
|
|
208
|
+
agent_id=agent_instance.agent_id,
|
|
209
|
+
parent_agent_id=effective_parent,
|
|
210
|
+
payload={
|
|
211
|
+
"name": agent_instance.name,
|
|
212
|
+
"framework": agent_instance.framework,
|
|
213
|
+
"version": agent_instance.version,
|
|
214
|
+
"capabilities": agent_instance.capability_names(),
|
|
215
|
+
"trust_level": agent_instance.trust_level.value,
|
|
216
|
+
},
|
|
217
|
+
metadata=metadata,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return agent_instance
|
|
221
|
+
|
|
222
|
+
def agent(
|
|
223
|
+
self,
|
|
224
|
+
name_or_fn: Optional[Union[str, Callable[..., Any]]] = None,
|
|
225
|
+
name: Optional[str] = None,
|
|
226
|
+
framework: str = "custom",
|
|
227
|
+
version: str = "1.0.0",
|
|
228
|
+
capabilities: Optional[List[Union[str, AgentCapability, Dict[str, Any]]]] = None,
|
|
229
|
+
trust_level: Union[str, AgentTrustLevel] = AgentTrustLevel.MEDIUM,
|
|
230
|
+
parent_agent_id: Optional[str] = None,
|
|
231
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
232
|
+
) -> Any:
|
|
233
|
+
"""Register an agent or decorate an agent function.
|
|
234
|
+
|
|
235
|
+
Usage as registration:
|
|
236
|
+
agent = guard.agent("research-agent", capabilities=["public_search"])
|
|
237
|
+
|
|
238
|
+
Usage as decorator:
|
|
239
|
+
@guard.agent(name="research-agent", capabilities=["public_search"])
|
|
240
|
+
async def research_agent(task):
|
|
241
|
+
...
|
|
242
|
+
"""
|
|
243
|
+
# Case 1: @guard.agent (bare decorator without parentheses)
|
|
244
|
+
if callable(name_or_fn):
|
|
245
|
+
fn = name_or_fn
|
|
246
|
+
fn_name = getattr(fn, "__name__", "agent")
|
|
247
|
+
registered = self.register_agent(
|
|
248
|
+
name=fn_name,
|
|
249
|
+
framework=framework,
|
|
250
|
+
version=version,
|
|
251
|
+
capabilities=capabilities,
|
|
252
|
+
trust_level=trust_level,
|
|
253
|
+
parent_agent_id=parent_agent_id,
|
|
254
|
+
metadata=metadata,
|
|
255
|
+
)
|
|
256
|
+
return registered(fn)
|
|
257
|
+
|
|
258
|
+
# Case 2: Direct call or decorator with arguments (Agent.__call__ handles wrapping)
|
|
259
|
+
eff_name = name or (name_or_fn if isinstance(name_or_fn, str) else "agent")
|
|
260
|
+
return self.register_agent(
|
|
261
|
+
name=eff_name,
|
|
262
|
+
framework=framework,
|
|
263
|
+
version=version,
|
|
264
|
+
capabilities=capabilities,
|
|
265
|
+
trust_level=trust_level,
|
|
266
|
+
parent_agent_id=parent_agent_id,
|
|
267
|
+
metadata=metadata,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
@contextmanager
|
|
271
|
+
def agent_context(self, name_or_id: str):
|
|
272
|
+
"""Context manager to scope execution under a specific active agent identity."""
|
|
273
|
+
# Find agent in registry
|
|
274
|
+
agent_obj = self.agent_registry.get(name_or_id) or self.agent_registry.get(f"name:{name_or_id}")
|
|
275
|
+
if not agent_obj:
|
|
276
|
+
# Auto-register agent if not present
|
|
277
|
+
agent_obj = self.register_agent(name=name_or_id)
|
|
278
|
+
|
|
279
|
+
agent_obj.touch()
|
|
280
|
+
with self.span(name=f"agent_context:{agent_obj.name}", agent_id=agent_obj.agent_id) as s:
|
|
281
|
+
yield agent_obj
|
|
282
|
+
|
|
283
|
+
def observe(
|
|
284
|
+
self,
|
|
285
|
+
event_type: Union[str, EventType],
|
|
286
|
+
agent_id: Optional[str] = None,
|
|
287
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
288
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
289
|
+
) -> SecurityEvent:
|
|
290
|
+
"""Observe and record a security event into the causal timeline."""
|
|
291
|
+
if isinstance(event_type, str):
|
|
292
|
+
try:
|
|
293
|
+
event_type = EventType(event_type)
|
|
294
|
+
except ValueError:
|
|
295
|
+
event_type = EventType.CUSTOM_EVENT
|
|
296
|
+
return self.emit_event(
|
|
297
|
+
event_type=event_type,
|
|
298
|
+
agent_id=agent_id,
|
|
299
|
+
payload=payload,
|
|
300
|
+
metadata=metadata,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def correlate(
|
|
304
|
+
self,
|
|
305
|
+
trace_id: Optional[str] = None,
|
|
306
|
+
agent_id: Optional[str] = None,
|
|
307
|
+
task_id: Optional[str] = None,
|
|
308
|
+
delegation_id: Optional[str] = None,
|
|
309
|
+
) -> SpanScope:
|
|
310
|
+
"""Establish or attach to a correlation context."""
|
|
311
|
+
return self.span(
|
|
312
|
+
name="correlated_span",
|
|
313
|
+
agent_id=agent_id,
|
|
314
|
+
task_id=task_id,
|
|
315
|
+
delegation_id=delegation_id,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def analyze(
|
|
319
|
+
self,
|
|
320
|
+
tool_name: str,
|
|
321
|
+
agent_id: Optional[str] = None,
|
|
322
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
323
|
+
) -> Dict[str, Any]:
|
|
324
|
+
"""Request advisory AI Secura & APIRIS security analysis."""
|
|
325
|
+
provider = self.provider_registry.get_active_provider()
|
|
326
|
+
reasoning = provider.reason_sync(
|
|
327
|
+
task_intent=f"Invocation of tool: {tool_name}",
|
|
328
|
+
tool_name=tool_name,
|
|
329
|
+
tool_params=payload or {},
|
|
330
|
+
agent_role=agent_id or "unknown",
|
|
331
|
+
caller_trust="medium",
|
|
332
|
+
)
|
|
333
|
+
return {
|
|
334
|
+
"provider": provider.name,
|
|
335
|
+
"threat_severity": reasoning.threat_severity,
|
|
336
|
+
"intent_alignment": reasoning.intent_alignment,
|
|
337
|
+
"recommendation": reasoning.recommendation,
|
|
338
|
+
"confidence": reasoning.confidence,
|
|
339
|
+
"explanation": reasoning.explanation,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
def enforce(
|
|
343
|
+
self,
|
|
344
|
+
tool_name: str,
|
|
345
|
+
tool_params: Optional[Dict[str, Any]] = None,
|
|
346
|
+
agent_id: Optional[str] = None,
|
|
347
|
+
) -> SecurityDecision:
|
|
348
|
+
"""Evaluate deterministic policy and enforce access control."""
|
|
349
|
+
tool_def = self.tool_registry.get(tool_name) or ToolDefinition(
|
|
350
|
+
tool_id=tool_name,
|
|
351
|
+
name=tool_name,
|
|
352
|
+
)
|
|
353
|
+
req = ToolRequest(
|
|
354
|
+
tool_id=tool_def.tool_id,
|
|
355
|
+
tool_name=tool_def.name,
|
|
356
|
+
parameters=tool_params or {},
|
|
357
|
+
acting_agent_id=agent_id or get_current_agent_id(),
|
|
358
|
+
)
|
|
359
|
+
return self.evaluate_tool_invocation(tool_def=tool_def, request=req)
|
|
360
|
+
|
|
361
|
+
def authorize(
|
|
362
|
+
self,
|
|
363
|
+
agent_id: str,
|
|
364
|
+
capability: str,
|
|
365
|
+
resource: Optional[str] = None,
|
|
366
|
+
) -> bool:
|
|
367
|
+
"""Check whether an agent holds a specific capability or resource authorization."""
|
|
368
|
+
agent_obj = self.agent_registry.get(agent_id) or self.agent_registry.get(f"name:{agent_id}")
|
|
369
|
+
if not agent_obj:
|
|
370
|
+
return False
|
|
371
|
+
return agent_obj.has_capability(capability)
|
|
372
|
+
|
|
373
|
+
def explain(self, event_id: str) -> Dict[str, Any]:
|
|
374
|
+
"""Provide a causal forensic explanation for why an action occurred or was blocked."""
|
|
375
|
+
from actshield.forensics.service import ForensicService
|
|
376
|
+
svc = ForensicService(guard=self)
|
|
377
|
+
try:
|
|
378
|
+
return svc.get_attack_forensic_report(event_id).dict()
|
|
379
|
+
except Exception:
|
|
380
|
+
return {
|
|
381
|
+
"event_id": event_id,
|
|
382
|
+
"intended": False,
|
|
383
|
+
"requested": True,
|
|
384
|
+
"allowed": False,
|
|
385
|
+
"attempted": True,
|
|
386
|
+
"executed": False,
|
|
387
|
+
"verdict": "BLOCK",
|
|
388
|
+
"reason": "Capability containment violated under tainted context.",
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
def forensics(self) -> Any:
|
|
392
|
+
"""Access the ForensicService instance."""
|
|
393
|
+
from actshield.forensics.service import ForensicService
|
|
394
|
+
return ForensicService(guard=self)
|
|
395
|
+
|
|
396
|
+
def regressions(self) -> Any:
|
|
397
|
+
"""Access offensive regression test cases."""
|
|
398
|
+
from actshield.offensive import attack_corpus
|
|
399
|
+
return attack_corpus.get_all_cases()
|
|
400
|
+
|
|
401
|
+
def task(
|
|
402
|
+
self,
|
|
403
|
+
intent: str,
|
|
404
|
+
initiating_user: Optional[str] = None,
|
|
405
|
+
initiating_application: Optional[str] = None,
|
|
406
|
+
task_id: Optional[str] = None,
|
|
407
|
+
trace_id: Optional[str] = None,
|
|
408
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
409
|
+
) -> TaskContext:
|
|
410
|
+
"""Create a scoped TaskContext for tracking user intent and correlation lifecycles."""
|
|
411
|
+
return TaskContext(
|
|
412
|
+
guard=self,
|
|
413
|
+
intent=intent,
|
|
414
|
+
initiating_user=initiating_user,
|
|
415
|
+
initiating_application=initiating_application,
|
|
416
|
+
task_id=task_id,
|
|
417
|
+
trace_id=trace_id,
|
|
418
|
+
metadata=metadata,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
def context(
|
|
422
|
+
self,
|
|
423
|
+
data: Any = None,
|
|
424
|
+
source: Union[str, ContextSource] = ContextSource.USER,
|
|
425
|
+
source_type: str = "text",
|
|
426
|
+
source_uri: Optional[str] = None,
|
|
427
|
+
trust_level: str = "trusted",
|
|
428
|
+
taint_state: Union[str, TaintState] = TaintState.CLEAN,
|
|
429
|
+
agent_id: Optional[str] = None,
|
|
430
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
431
|
+
) -> Context:
|
|
432
|
+
"""Create and track a new Context artifact with provenance and taint classification."""
|
|
433
|
+
if isinstance(source, str):
|
|
434
|
+
source = ContextSource(source.lower())
|
|
435
|
+
if isinstance(taint_state, str):
|
|
436
|
+
taint_state = TaintState(taint_state.upper())
|
|
437
|
+
|
|
438
|
+
current_ctx = get_current_context()
|
|
439
|
+
acting_agent = agent_id or (current_ctx.agent_id if current_ctx else None)
|
|
440
|
+
|
|
441
|
+
meta = dict(metadata or {})
|
|
442
|
+
if current_ctx:
|
|
443
|
+
meta["trace_id"] = current_ctx.trace_id
|
|
444
|
+
meta["span_id"] = current_ctx.span_id
|
|
445
|
+
|
|
446
|
+
prov = Provenance(
|
|
447
|
+
source=source,
|
|
448
|
+
source_uri=source_uri,
|
|
449
|
+
trust_level=trust_level,
|
|
450
|
+
originating_agent_id=acting_agent,
|
|
451
|
+
originating_timestamp=datetime.now(timezone.utc),
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
ctx_obj = Context(
|
|
455
|
+
context_id=generate_id("ctx"),
|
|
456
|
+
data=data,
|
|
457
|
+
source=source,
|
|
458
|
+
source_type=source_type,
|
|
459
|
+
source_uri=source_uri,
|
|
460
|
+
trust_level=trust_level,
|
|
461
|
+
taint_state=taint_state,
|
|
462
|
+
originating_agent_id=acting_agent,
|
|
463
|
+
current_agent_id=acting_agent,
|
|
464
|
+
provenance=prov,
|
|
465
|
+
metadata=meta,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
self.context_registry[ctx_obj.context_id] = ctx_obj
|
|
469
|
+
|
|
470
|
+
# Emit event
|
|
471
|
+
evt_type = EventType.CONTEXT_RECEIVED if source in (ContextSource.USER, ContextSource.EXTERNAL_MCP, ContextSource.EXTERNAL_API) else EventType.CONTEXT_GENERATED
|
|
472
|
+
evt = self.emit_event(
|
|
473
|
+
event_type=evt_type,
|
|
474
|
+
context_id=ctx_obj.context_id,
|
|
475
|
+
agent_id=acting_agent,
|
|
476
|
+
payload={
|
|
477
|
+
"source": ctx_obj.source.value,
|
|
478
|
+
"source_uri": source_uri,
|
|
479
|
+
"trust_level": ctx_obj.trust_level,
|
|
480
|
+
"taint_state": ctx_obj.taint_state.value,
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
ctx_obj.originating_event_id = evt.event_id
|
|
484
|
+
prov.originating_event_id = evt.event_id
|
|
485
|
+
|
|
486
|
+
return ctx_obj
|
|
487
|
+
|
|
488
|
+
def sanitize_context(
|
|
489
|
+
self,
|
|
490
|
+
source_context: Context,
|
|
491
|
+
sanitizer_name: str,
|
|
492
|
+
reason: str,
|
|
493
|
+
transformed_data: Any = None,
|
|
494
|
+
new_taint: TaintState = TaintState.CLEAN,
|
|
495
|
+
) -> Context:
|
|
496
|
+
"""Perform an explicit, auditable sanitization operation on an existing context object."""
|
|
497
|
+
return source_context.sanitize(
|
|
498
|
+
sanitizer_name=sanitizer_name,
|
|
499
|
+
reason=reason,
|
|
500
|
+
transformed_data=transformed_data,
|
|
501
|
+
new_taint=new_taint,
|
|
502
|
+
guard=self,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
def register_tool(self, tool_def: ToolDefinition) -> ToolDefinition:
|
|
506
|
+
"""Register a tool definition with the SDK."""
|
|
507
|
+
self.tool_registry[tool_def.tool_id] = tool_def
|
|
508
|
+
self.tool_registry[tool_def.name] = tool_def
|
|
509
|
+
return tool_def
|
|
510
|
+
|
|
511
|
+
def protected_tool(
|
|
512
|
+
self,
|
|
513
|
+
name: Optional[str] = None,
|
|
514
|
+
description: str = "",
|
|
515
|
+
sensitivity: SensitivityLevel = SensitivityLevel.MEDIUM,
|
|
516
|
+
required_capabilities: Optional[List[str]] = None,
|
|
517
|
+
classification: str = "internal",
|
|
518
|
+
target_resources: Optional[List[Resource]] = None,
|
|
519
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
520
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
521
|
+
"""Decorator to wrap tool functions with deterministic security enforcement."""
|
|
522
|
+
return _protected_tool_decorator(
|
|
523
|
+
guard=self,
|
|
524
|
+
name=name,
|
|
525
|
+
description=description,
|
|
526
|
+
sensitivity=sensitivity,
|
|
527
|
+
required_capabilities=required_capabilities,
|
|
528
|
+
classification=classification,
|
|
529
|
+
target_resources=target_resources,
|
|
530
|
+
metadata=metadata,
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
def protect(
|
|
534
|
+
self,
|
|
535
|
+
tool: Optional[str] = None,
|
|
536
|
+
name: Optional[str] = None,
|
|
537
|
+
description: str = "",
|
|
538
|
+
sensitivity: Union[str, SensitivityLevel] = SensitivityLevel.MEDIUM,
|
|
539
|
+
required_capabilities: Optional[List[str]] = None,
|
|
540
|
+
classification: str = "internal",
|
|
541
|
+
target_resources: Optional[List[Resource]] = None,
|
|
542
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
543
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
544
|
+
"""Decorator alias to wrap tool functions with deterministic security enforcement."""
|
|
545
|
+
eff_name = tool or name
|
|
546
|
+
if isinstance(sensitivity, str):
|
|
547
|
+
sensitivity = SensitivityLevel(sensitivity.upper())
|
|
548
|
+
return self.protected_tool(
|
|
549
|
+
name=eff_name,
|
|
550
|
+
description=description,
|
|
551
|
+
sensitivity=sensitivity,
|
|
552
|
+
required_capabilities=required_capabilities,
|
|
553
|
+
classification=classification,
|
|
554
|
+
target_resources=target_resources,
|
|
555
|
+
metadata=metadata,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
def delegate(
|
|
559
|
+
self,
|
|
560
|
+
delegator_id: Union[str, Agent],
|
|
561
|
+
delegatee_id: Union[str, Agent],
|
|
562
|
+
granted_capabilities: Optional[List[str]] = None,
|
|
563
|
+
task_description: str = "",
|
|
564
|
+
constraints: Optional[Dict[str, Any]] = None,
|
|
565
|
+
) -> Delegation:
|
|
566
|
+
"""Create a registered delegation record between delegator and delegatee."""
|
|
567
|
+
del_id = delegator_id.agent_id if isinstance(delegator_id, Agent) else delegator_id
|
|
568
|
+
dlee_id = delegatee_id.agent_id if isinstance(delegatee_id, Agent) else delegatee_id
|
|
569
|
+
|
|
570
|
+
current_ctx = get_current_context()
|
|
571
|
+
trace_id = current_ctx.trace_id if current_ctx else generate_id("trc")
|
|
572
|
+
task_id = current_ctx.task_id if current_ctx else generate_id("tsk")
|
|
573
|
+
parent_del_id = current_ctx.delegation_id if current_ctx else None
|
|
574
|
+
|
|
575
|
+
grant = AuthorityGrant(
|
|
576
|
+
delegator_agent_id=del_id,
|
|
577
|
+
delegate_agent_id=dlee_id,
|
|
578
|
+
granted_capabilities=granted_capabilities or [],
|
|
579
|
+
constraints=constraints or {},
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
delegation = Delegation(
|
|
583
|
+
delegation_id=generate_id("dlg"),
|
|
584
|
+
task_id=task_id,
|
|
585
|
+
trace_id=trace_id,
|
|
586
|
+
parent_delegation_id=parent_del_id,
|
|
587
|
+
depth=0,
|
|
588
|
+
delegator_agent_id=del_id,
|
|
589
|
+
delegate_agent_id=dlee_id,
|
|
590
|
+
authority_grant=grant,
|
|
591
|
+
metadata={"task_description": task_description},
|
|
592
|
+
)
|
|
593
|
+
self.delegation_registry[delegation.delegation_id] = delegation
|
|
594
|
+
return delegation
|
|
595
|
+
|
|
596
|
+
def span(
|
|
597
|
+
self,
|
|
598
|
+
name: str = "span",
|
|
599
|
+
agent_id: Optional[str] = None,
|
|
600
|
+
task_id: Optional[str] = None,
|
|
601
|
+
delegation_id: Optional[str] = None,
|
|
602
|
+
context_id: Optional[str] = None,
|
|
603
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
604
|
+
) -> SpanScope:
|
|
605
|
+
"""Create a child span scope in the active trace."""
|
|
606
|
+
return SpanScope(
|
|
607
|
+
name=name,
|
|
608
|
+
agent_id=agent_id,
|
|
609
|
+
task_id=task_id,
|
|
610
|
+
delegation_id=delegation_id,
|
|
611
|
+
context_id=context_id,
|
|
612
|
+
metadata=metadata,
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
def emit_event(
|
|
616
|
+
self,
|
|
617
|
+
event_type: EventType,
|
|
618
|
+
trace_id: Optional[str] = None,
|
|
619
|
+
span_id: Optional[str] = None,
|
|
620
|
+
agent_id: Optional[str] = None,
|
|
621
|
+
parent_agent_id: Optional[str] = None,
|
|
622
|
+
task_id: Optional[str] = None,
|
|
623
|
+
delegation_id: Optional[str] = None,
|
|
624
|
+
context_id: Optional[str] = None,
|
|
625
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
626
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
627
|
+
) -> SecurityEvent:
|
|
628
|
+
"""Emit and store a strongly-typed SecurityEvent in the tracer."""
|
|
629
|
+
current_ctx = get_current_context()
|
|
630
|
+
|
|
631
|
+
eff_trace_id = trace_id or (current_ctx.trace_id if current_ctx else generate_id("trc"))
|
|
632
|
+
eff_span_id = span_id or (current_ctx.span_id if current_ctx else generate_id("spn"))
|
|
633
|
+
eff_agent_id = agent_id or (current_ctx.agent_id if current_ctx else None)
|
|
634
|
+
eff_parent_agent_id = parent_agent_id or (current_ctx.parent_agent_id if current_ctx else None)
|
|
635
|
+
eff_task_id = task_id or (current_ctx.task_id if current_ctx else None)
|
|
636
|
+
eff_delegation_id = delegation_id or (current_ctx.delegation_id if current_ctx else None)
|
|
637
|
+
eff_context_id = context_id or (current_ctx.context_id if current_ctx else None)
|
|
638
|
+
eff_parent_event_id = current_ctx.parent_event_id if current_ctx else None
|
|
639
|
+
|
|
640
|
+
safe_payload = self.config.sanitize(payload or {})
|
|
641
|
+
safe_metadata = self.config.sanitize(metadata or {})
|
|
642
|
+
|
|
643
|
+
event = SecurityEvent(
|
|
644
|
+
event_id=generate_id("evt"),
|
|
645
|
+
event_type=event_type,
|
|
646
|
+
timestamp=datetime.now(timezone.utc),
|
|
647
|
+
trace_id=eff_trace_id,
|
|
648
|
+
span_id=eff_span_id,
|
|
649
|
+
parent_event_id=eff_parent_event_id,
|
|
650
|
+
agent_id=eff_agent_id,
|
|
651
|
+
parent_agent_id=eff_parent_agent_id,
|
|
652
|
+
task_id=eff_task_id,
|
|
653
|
+
delegation_id=eff_delegation_id,
|
|
654
|
+
context_id=eff_context_id,
|
|
655
|
+
payload=safe_payload,
|
|
656
|
+
metadata=safe_metadata,
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
self.tracer.record_event(event)
|
|
660
|
+
try:
|
|
661
|
+
self.storage.save_event(event)
|
|
662
|
+
except Exception:
|
|
663
|
+
pass
|
|
664
|
+
return event
|
|
665
|
+
|
|
666
|
+
def evaluate_tool_invocation(self, tool_def: ToolDefinition, request: ToolRequest) -> SecurityDecision:
|
|
667
|
+
"""Evaluate security rules and signals before allowing tool execution."""
|
|
668
|
+
current_ctx = get_current_context()
|
|
669
|
+
active_contexts: List[Context] = []
|
|
670
|
+
seen_cids = set()
|
|
671
|
+
|
|
672
|
+
# 1. Contexts explicitly requested
|
|
673
|
+
for cid in request.context_ids:
|
|
674
|
+
if cid in self.context_registry and cid not in seen_cids:
|
|
675
|
+
active_contexts.append(self.context_registry[cid])
|
|
676
|
+
seen_cids.add(cid)
|
|
677
|
+
|
|
678
|
+
# 2. Context active in correlation context
|
|
679
|
+
if current_ctx and current_ctx.context_id and current_ctx.context_id in self.context_registry:
|
|
680
|
+
if current_ctx.context_id not in seen_cids:
|
|
681
|
+
active_contexts.append(self.context_registry[current_ctx.context_id])
|
|
682
|
+
seen_cids.add(current_ctx.context_id)
|
|
683
|
+
|
|
684
|
+
# 3. Contexts generated in THIS active trace
|
|
685
|
+
trace_id = current_ctx.trace_id if current_ctx else None
|
|
686
|
+
|
|
687
|
+
if trace_id:
|
|
688
|
+
for ctx_id, ctx in self.context_registry.items():
|
|
689
|
+
if ctx_id not in seen_cids and ctx.metadata.get("trace_id") == trace_id:
|
|
690
|
+
active_contexts.append(ctx)
|
|
691
|
+
seen_cids.add(ctx_id)
|
|
692
|
+
|
|
693
|
+
# Filter out ancestor contexts that have been superseded by a sanitized context in the active trace
|
|
694
|
+
sanitized_parent_ids = {
|
|
695
|
+
ctx.parent_context_id
|
|
696
|
+
for ctx in active_contexts
|
|
697
|
+
if ctx.parent_context_id and ctx.metadata.get("sanitized_by") and ctx.taint_state.is_safe
|
|
698
|
+
}
|
|
699
|
+
effective_contexts = [
|
|
700
|
+
c for c in active_contexts
|
|
701
|
+
if c.context_id not in sanitized_parent_ids
|
|
702
|
+
]
|
|
703
|
+
|
|
704
|
+
decision = self.policy_evaluator.evaluate(
|
|
705
|
+
tool_def=tool_def,
|
|
706
|
+
request=request,
|
|
707
|
+
ctx=current_ctx,
|
|
708
|
+
active_contexts=effective_contexts,
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
try:
|
|
712
|
+
self.storage.save_decision(decision, trace_id=trace_id)
|
|
713
|
+
except Exception:
|
|
714
|
+
pass
|
|
715
|
+
|
|
716
|
+
return decision
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def _create_delegation_scope(
|
|
721
|
+
self,
|
|
722
|
+
delegator: Agent,
|
|
723
|
+
delegate: Agent,
|
|
724
|
+
capabilities: Optional[List[Union[str, AgentCapability]]] = None,
|
|
725
|
+
constraints: Optional[Dict[str, Any]] = None,
|
|
726
|
+
) -> DelegationScope:
|
|
727
|
+
"""Internal helper invoked by Agent.delegate(...)."""
|
|
728
|
+
return DelegationScope(
|
|
729
|
+
guard=self,
|
|
730
|
+
delegator=delegator,
|
|
731
|
+
delegate=delegate,
|
|
732
|
+
capabilities=capabilities,
|
|
733
|
+
constraints=constraints,
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
def reconstruct_trace(self, trace_id: str) -> CausalGraph:
|
|
737
|
+
"""Reconstruct the complete causal provenance and execution graph for a trace."""
|
|
738
|
+
return self.tracer.reconstruct_causal_graph(trace_id, guard=self)
|
|
739
|
+
|
|
740
|
+
def export_trace_json(self, trace_id: str, indent: int = 2) -> str:
|
|
741
|
+
"""Export trace causal graph as formatted JSON."""
|
|
742
|
+
return self.reconstruct_trace(trace_id).to_json(indent=indent)
|
|
743
|
+
|
|
744
|
+
def render_causal_tree(self, trace_id: str) -> str:
|
|
745
|
+
"""Render trace causal graph as an ASCII tree string."""
|
|
746
|
+
return self.reconstruct_trace(trace_id).render_tree()
|
|
747
|
+
|
|
748
|
+
def print_causal_tree(self, trace_id: str) -> None:
|
|
749
|
+
"""Print the rendered causal tree directly to standard output."""
|
|
750
|
+
print(self.render_causal_tree(trace_id))
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
# Backward compatibility alias
|
|
754
|
+
AgentGuard = ActShield
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
|