actshield 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- actshield/__init__.py +439 -0
- actshield/__main__.py +46 -0
- actshield/agents/__init__.py +12 -0
- actshield/agents/agent.py +200 -0
- actshield/agents/identity.py +114 -0
- actshield/api/__init__.py +34 -0
- actshield/api/models.py +198 -0
- actshield/api/server.py +669 -0
- actshield/api/service.py +599 -0
- actshield/approval/__init__.py +15 -0
- actshield/approval/approval.py +164 -0
- actshield/cli/__init__.py +8 -0
- actshield/cli/graph.py +49 -0
- actshield/cli/main.py +802 -0
- actshield/cli/shell.py +406 -0
- actshield/cli/theme.py +110 -0
- actshield/client.py +757 -0
- actshield/config.py +88 -0
- actshield/context/__init__.py +14 -0
- actshield/context/context.py +161 -0
- actshield/context/provenance.py +108 -0
- actshield/context/taint.py +48 -0
- actshield/decisions/decision.py +74 -0
- actshield/delegation/__init__.py +11 -0
- actshield/delegation/authority.py +63 -0
- actshield/delegation/delegation.py +180 -0
- actshield/demo.py +139 -0
- actshield/doctor.py +94 -0
- actshield/drift/__init__.py +27 -0
- actshield/drift/drift_engine.py +149 -0
- actshield/drift/drift_models.py +63 -0
- actshield/evaluation/__init__.py +1 -0
- actshield/evaluation/cases/__init__.py +1 -0
- actshield/evaluation/eval_suite.py +254 -0
- actshield/forensics/__init__.py +53 -0
- actshield/forensics/access_graph.py +216 -0
- actshield/forensics/authority_graph.py +186 -0
- actshield/forensics/delegation_graph.py +119 -0
- actshield/forensics/explanation.py +254 -0
- actshield/forensics/models.py +488 -0
- actshield/forensics/queries.py +367 -0
- actshield/forensics/serializers.py +68 -0
- actshield/forensics/service.py +310 -0
- actshield/forensics/snapshots.py +174 -0
- actshield/gates/__init__.py +13 -0
- actshield/gates/gate_models.py +46 -0
- actshield/gates/security_gate.py +191 -0
- actshield/gateway/__init__.py +13 -0
- actshield/gateway/http.py +191 -0
- actshield/gateway/mcp.py +292 -0
- actshield/incidents/__init__.py +25 -0
- actshield/incidents/incident.py +94 -0
- actshield/incidents/incident_engine.py +163 -0
- actshield/incidents/incident_rules.py +116 -0
- actshield/incidents/incident_state.py +58 -0
- actshield/integrations/__init__.py +24 -0
- actshield/integrations/ai_secura.py +169 -0
- actshield/integrations/ai_secura_analysis.py +236 -0
- actshield/integrations/apiris.py +122 -0
- actshield/integrations/few_shot.py +104 -0
- actshield/integrations/ollama_adapter.py +538 -0
- actshield/integrations/prompts/__init__.py +1 -0
- actshield/integrations/prompts/examples/__init__.py +1 -0
- actshield/integrations/security_packet.py +82 -0
- actshield/llm_config.py +89 -0
- actshield/offensive/__init__.py +95 -0
- actshield/offensive/adaptive/__init__.py +22 -0
- actshield/offensive/adaptive/adaptive_engine.py +165 -0
- actshield/offensive/adaptive/attack_selector.py +51 -0
- actshield/offensive/adaptive/boundary_search.py +99 -0
- actshield/offensive/adaptive/campaign_memory.py +123 -0
- actshield/offensive/adaptive/feedback.py +84 -0
- actshield/offensive/adaptive/mutation_planner.py +57 -0
- actshield/offensive/attack.py +78 -0
- actshield/offensive/campaign.py +219 -0
- actshield/offensive/corpus.py +155 -0
- actshield/offensive/engine.py +466 -0
- actshield/offensive/evaluator.py +116 -0
- actshield/offensive/evidence.py +63 -0
- actshield/offensive/generator.py +128 -0
- actshield/offensive/mutation.py +101 -0
- actshield/offensive/results.py +77 -0
- actshield/offensive/safety.py +80 -0
- actshield/offensive/strategies/__init__.py +22 -0
- actshield/offensive/strategies/authority_escalation.py +100 -0
- actshield/offensive/strategies/base.py +34 -0
- actshield/offensive/strategies/context_manipulation.py +100 -0
- actshield/offensive/strategies/data_exfiltration.py +100 -0
- actshield/offensive/strategies/delegation_attacks.py +100 -0
- actshield/offensive/strategies/mcp_attacks.py +100 -0
- actshield/offensive/strategies/prompt_injection.py +100 -0
- actshield/offensive/strategies/tool_poisoning.py +100 -0
- actshield/offensive/target.py +113 -0
- actshield/offensive/vulnerable_target.py +121 -0
- actshield/persistence/__init__.py +18 -0
- actshield/persistence/siem.py +102 -0
- actshield/persistence/storage.py +240 -0
- actshield/policy/__init__.py +12 -0
- actshield/policy/evaluator.py +294 -0
- actshield/policy/intent.py +113 -0
- actshield/policy/policy.py +48 -0
- actshield/posture/__init__.py +26 -0
- actshield/posture/posture_engine.py +186 -0
- actshield/posture/posture_models.py +152 -0
- actshield/posture/posture_rules.py +137 -0
- actshield/providers/__init__.py +32 -0
- actshield/providers/anthropic_provider.py +146 -0
- actshield/providers/base.py +160 -0
- actshield/providers/gemini_provider.py +133 -0
- actshield/providers/null_provider.py +40 -0
- actshield/providers/ollama_provider.py +135 -0
- actshield/providers/openai_provider.py +142 -0
- actshield/providers/registry.py +244 -0
- actshield/py.typed +0 -0
- actshield/response/__init__.py +14 -0
- actshield/response/response_actions.py +39 -0
- actshield/response/response_audit.py +32 -0
- actshield/response/response_engine.py +139 -0
- actshield/risk/models.py +104 -0
- actshield/serve.py +117 -0
- actshield/static/404.html +1 -0
- actshield/static/__next.__PAGE__.txt +24 -0
- actshield/static/__next._full.txt +20 -0
- actshield/static/__next._tree.txt +4 -0
- actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_buildManifest.js +11 -0
- actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_clientMiddlewareManifest.js +1 -0
- actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_ssgManifest.js +1 -0
- actshield/static/_next/static/chunks/09kny2eqjc2or.js +1 -0
- actshield/static/_next/static/chunks/0c1zba1u9whdp.js +1 -0
- actshield/static/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
- actshield/static/_next/static/chunks/0dbjjeg01x25o.js +1 -0
- actshield/static/_next/static/chunks/0duobs55ne238.js +1 -0
- actshield/static/_next/static/chunks/0iaz41l6068e4.js +1 -0
- actshield/static/_next/static/chunks/0mjhyv72x2idp.js +1 -0
- actshield/static/_next/static/chunks/0nelpqfu_ciq6.js +1 -0
- actshield/static/_next/static/chunks/0p1bci9ch797v.js +1 -0
- actshield/static/_next/static/chunks/0tld_ygho5tgm.js +1 -0
- actshield/static/_next/static/chunks/0w1-y7oni66j9.js +1 -0
- actshield/static/_next/static/chunks/10537zyl427zj.js +1 -0
- actshield/static/_next/static/chunks/11pc_jkl3aggn.js +1 -0
- actshield/static/_next/static/chunks/16k7nw0b3t8ys.js +1 -0
- actshield/static/_next/static/chunks/19mx3mg6lkumu.js +1 -0
- actshield/static/_next/static/chunks/1_8p47b-kxg2a.js +1 -0
- actshield/static/_next/static/chunks/1dszjozwfqegx.js +1 -0
- actshield/static/_next/static/chunks/1h43jcb90-m2r.js +22 -0
- actshield/static/_next/static/chunks/1ly04cvt_wh_j.js +1 -0
- actshield/static/_next/static/chunks/1odxrypuwuwp_.js +1 -0
- actshield/static/_next/static/chunks/1oes4c35qz5ku.js +1 -0
- actshield/static/_next/static/chunks/1ow883jl4ln-r.js +16 -0
- actshield/static/_next/static/chunks/1po7bna1as8tc.js +1 -0
- actshield/static/_next/static/chunks/1rsh0og8ifkzx.js +1 -0
- actshield/static/_next/static/chunks/1v0lwzv-859z_.js +1 -0
- actshield/static/_next/static/chunks/25iat2epwx25a.js +1 -0
- actshield/static/_next/static/chunks/25uvt4s2f39a4.js +1 -0
- actshield/static/_next/static/chunks/27t_qfc-3_lzs.js +1 -0
- actshield/static/_next/static/chunks/28bkmy8mez7gm.js +1 -0
- actshield/static/_next/static/chunks/2b6brmw_drw1p.js +1 -0
- actshield/static/_next/static/chunks/2fnhtxi4j6vge.js +1 -0
- actshield/static/_next/static/chunks/2p087eftxemg4.js +6 -0
- actshield/static/_next/static/chunks/2s8pao_z8pgtq.js +1 -0
- actshield/static/_next/static/chunks/2yj7ooc2c-zpg.js +1 -0
- actshield/static/_next/static/chunks/3-mrrr8udt5tk.js +1 -0
- actshield/static/_next/static/chunks/310vm2bl3xxpt.js +31 -0
- actshield/static/_next/static/chunks/35mvsv251jx1r.css +3 -0
- actshield/static/_next/static/chunks/370vf0oetlsx9.js +1 -0
- actshield/static/_next/static/chunks/3e-2m72tyo556.js +1 -0
- actshield/static/_next/static/chunks/3fntmmi971322.js +1 -0
- actshield/static/_next/static/chunks/3fzw-g0hycl2c.js +1 -0
- actshield/static/_next/static/chunks/3lopq3lhmcjxg.js +129 -0
- actshield/static/_next/static/chunks/3mpe2jejel0v2.css +1 -0
- actshield/static/_next/static/chunks/3t8o4f2lzr2sf.js +1 -0
- actshield/static/_next/static/chunks/3y-v1neyllne5.js +1 -0
- actshield/static/_next/static/chunks/43oiwy8mx6cs1.js +1 -0
- actshield/static/_next/static/chunks/turbopack-23wzuebcy_lxm.js +1 -0
- actshield/static/_next/static/media/1bffadaabf893a1e-s.3-6t-g6q0vh0a.woff2 +0 -0
- actshield/static/_next/static/media/2bbe8d2671613f1f-s.0k62hbripvv8p.woff2 +0 -0
- actshield/static/_next/static/media/2c55a0e60120577a-s.0-dom-5bn10r2.woff2 +0 -0
- actshield/static/_next/static/media/5476f68d60460930-s.2uwcyprjm3xu3.woff2 +0 -0
- actshield/static/_next/static/media/83afe278b6a6bb3c-s.p.2bn3s6zvc0dyp.woff2 +0 -0
- actshield/static/_next/static/media/9c72aa0f40e4eef8-s.1y4-pdgsjb-pw.woff2 +0 -0
- actshield/static/_next/static/media/ad66f9afd8947f86-s.3lvt2whj97whp.woff2 +0 -0
- actshield/static/_next/static/media/favicon.2vob68tjqpejf.ico +0 -0
- actshield/static/_not-found/__next._full.txt +17 -0
- actshield/static/_not-found/__next._not-found/__PAGE__.txt +23 -0
- actshield/static/_not-found/__next._tree.txt +3 -0
- actshield/static/_not-found.html +1 -0
- actshield/static/_not-found.txt +17 -0
- actshield/static/access/matrix/__next._full.txt +24 -0
- actshield/static/access/matrix/__next._tree.txt +4 -0
- actshield/static/access/matrix/__next.access/matrix/__PAGE__.txt +30 -0
- actshield/static/access/matrix.html +1 -0
- actshield/static/access/matrix.txt +24 -0
- actshield/static/activity/__next._full.txt +22 -0
- actshield/static/activity/__next._tree.txt +4 -0
- actshield/static/activity/__next.activity/__PAGE__.txt +27 -0
- actshield/static/activity.html +1 -0
- actshield/static/activity.txt +22 -0
- actshield/static/agents/__next._full.txt +22 -0
- actshield/static/agents/__next._tree.txt +4 -0
- actshield/static/agents/__next.agents/__PAGE__.txt +27 -0
- actshield/static/agents.html +1 -0
- actshield/static/agents.txt +22 -0
- actshield/static/ai-secura/__next._full.txt +22 -0
- actshield/static/ai-secura/__next._tree.txt +4 -0
- actshield/static/ai-secura/__next.ai-secura/__PAGE__.txt +27 -0
- actshield/static/ai-secura.html +1 -0
- actshield/static/ai-secura.txt +22 -0
- actshield/static/api/__next._full.txt +22 -0
- actshield/static/api/__next._tree.txt +4 -0
- actshield/static/api/__next.api/__PAGE__.txt +27 -0
- actshield/static/api.html +15 -0
- actshield/static/api.txt +22 -0
- actshield/static/apiris/__next._full.txt +22 -0
- actshield/static/apiris/__next._tree.txt +4 -0
- actshield/static/apiris/__next.apiris/__PAGE__.txt +27 -0
- actshield/static/apiris.html +1 -0
- actshield/static/apiris.txt +22 -0
- actshield/static/attack-graph/__next._full.txt +23 -0
- actshield/static/attack-graph/__next._tree.txt +5 -0
- actshield/static/attack-graph/__next.attack-graph/__PAGE__.txt +28 -0
- actshield/static/attack-graph.html +2 -0
- actshield/static/attack-graph.txt +23 -0
- actshield/static/campaigns/__next._full.txt +22 -0
- actshield/static/campaigns/__next._tree.txt +4 -0
- actshield/static/campaigns/__next.campaigns/__PAGE__.txt +27 -0
- actshield/static/campaigns.html +1 -0
- actshield/static/campaigns.txt +22 -0
- actshield/static/context/__next._full.txt +22 -0
- actshield/static/context/__next._tree.txt +4 -0
- actshield/static/context/__next.context/__PAGE__.txt +27 -0
- actshield/static/context.html +1 -0
- actshield/static/context.txt +22 -0
- actshield/static/dashboard/__next._full.txt +22 -0
- actshield/static/dashboard/__next._tree.txt +4 -0
- actshield/static/dashboard/__next.dashboard/__PAGE__.txt +27 -0
- actshield/static/dashboard.html +1 -0
- actshield/static/dashboard.txt +22 -0
- actshield/static/delegations/__next._full.txt +22 -0
- actshield/static/delegations/__next._tree.txt +4 -0
- actshield/static/delegations/__next.delegations/__PAGE__.txt +27 -0
- actshield/static/delegations.html +1 -0
- actshield/static/delegations.txt +22 -0
- actshield/static/demo/__next._full.txt +22 -0
- actshield/static/demo/__next._tree.txt +4 -0
- actshield/static/demo/__next.demo/__PAGE__.txt +27 -0
- actshield/static/demo.html +1 -0
- actshield/static/demo.txt +22 -0
- actshield/static/drift/__next._full.txt +22 -0
- actshield/static/drift/__next._tree.txt +4 -0
- actshield/static/drift/__next.drift/__PAGE__.txt +27 -0
- actshield/static/drift.html +1 -0
- actshield/static/drift.txt +22 -0
- actshield/static/favicon.ico +0 -0
- actshield/static/file.svg +1 -0
- actshield/static/forensics/__next._full.txt +22 -0
- actshield/static/forensics/__next._tree.txt +4 -0
- actshield/static/forensics/__next.forensics/__PAGE__.txt +27 -0
- actshield/static/forensics.html +1 -0
- actshield/static/forensics.txt +22 -0
- actshield/static/globe.svg +1 -0
- actshield/static/incidents/__next._full.txt +22 -0
- actshield/static/incidents/__next._tree.txt +4 -0
- actshield/static/incidents/__next.incidents/__PAGE__.txt +27 -0
- actshield/static/incidents.html +1 -0
- actshield/static/incidents.txt +22 -0
- actshield/static/index.html +3 -0
- actshield/static/index.txt +20 -0
- actshield/static/logo.png +0 -0
- actshield/static/metrics/__next._full.txt +22 -0
- actshield/static/metrics/__next._tree.txt +4 -0
- actshield/static/metrics/__next.metrics/__PAGE__.txt +27 -0
- actshield/static/metrics.html +1 -0
- actshield/static/metrics.txt +22 -0
- actshield/static/next.svg +1 -0
- actshield/static/offensive/__next._full.txt +22 -0
- actshield/static/offensive/__next._tree.txt +4 -0
- actshield/static/offensive/__next.offensive/__PAGE__.txt +27 -0
- actshield/static/offensive.html +1 -0
- actshield/static/offensive.txt +22 -0
- actshield/static/policies/__next._full.txt +22 -0
- actshield/static/policies/__next._tree.txt +4 -0
- actshield/static/policies/__next.policies/__PAGE__.txt +27 -0
- actshield/static/policies.html +1 -0
- actshield/static/policies.txt +22 -0
- actshield/static/posture/__next._full.txt +22 -0
- actshield/static/posture/__next._tree.txt +4 -0
- actshield/static/posture/__next.posture/__PAGE__.txt +27 -0
- actshield/static/posture.html +1 -0
- actshield/static/posture.txt +22 -0
- actshield/static/regressions/__next._full.txt +22 -0
- actshield/static/regressions/__next._tree.txt +4 -0
- actshield/static/regressions/__next.regressions/__PAGE__.txt +27 -0
- actshield/static/regressions.html +1 -0
- actshield/static/regressions.txt +22 -0
- actshield/static/reports/__next._full.txt +22 -0
- actshield/static/reports/__next._tree.txt +4 -0
- actshield/static/reports/__next.reports/__PAGE__.txt +27 -0
- actshield/static/reports.html +1 -0
- actshield/static/reports.txt +22 -0
- actshield/static/resources/__next._full.txt +22 -0
- actshield/static/resources/__next._tree.txt +4 -0
- actshield/static/resources/__next.resources/__PAGE__.txt +27 -0
- actshield/static/resources.html +1 -0
- actshield/static/resources.txt +22 -0
- actshield/static/security-gates/__next._full.txt +22 -0
- actshield/static/security-gates/__next._tree.txt +4 -0
- actshield/static/security-gates/__next.security-gates/__PAGE__.txt +27 -0
- actshield/static/security-gates.html +1 -0
- actshield/static/security-gates.txt +22 -0
- actshield/static/settings/__next._full.txt +22 -0
- actshield/static/settings/__next._tree.txt +4 -0
- actshield/static/settings/__next.settings/__PAGE__.txt +27 -0
- actshield/static/settings.html +1 -0
- actshield/static/settings.txt +22 -0
- actshield/static/tasks/__next._full.txt +22 -0
- actshield/static/tasks/__next._tree.txt +4 -0
- actshield/static/tasks/__next.tasks/__PAGE__.txt +27 -0
- actshield/static/tasks.html +1 -0
- actshield/static/tasks.txt +22 -0
- actshield/static/threats/__next._full.txt +22 -0
- actshield/static/threats/__next._tree.txt +4 -0
- actshield/static/threats/__next.threats/__PAGE__.txt +27 -0
- actshield/static/threats.html +1 -0
- actshield/static/threats.txt +22 -0
- actshield/static/tools/__next._full.txt +22 -0
- actshield/static/tools/__next._tree.txt +4 -0
- actshield/static/tools/__next.tools/__PAGE__.txt +27 -0
- actshield/static/tools.html +1 -0
- actshield/static/tools.txt +22 -0
- actshield/static/traces/__next._full.txt +22 -0
- actshield/static/traces/__next._tree.txt +4 -0
- actshield/static/traces/__next.traces/__PAGE__.txt +27 -0
- actshield/static/traces.html +1 -0
- actshield/static/traces.txt +22 -0
- actshield/static/vercel.svg +1 -0
- actshield/static/window.svg +1 -0
- actshield/tasks/__init__.py +6 -0
- actshield/tasks/task.py +143 -0
- actshield/threatmodel/__init__.py +70 -0
- actshield/threatmodel/actors.py +210 -0
- actshield/threatmodel/analyzer.py +131 -0
- actshield/threatmodel/assets.py +167 -0
- actshield/threatmodel/boundaries.py +165 -0
- actshield/threatmodel/exporters.py +49 -0
- actshield/threatmodel/mitigations.py +209 -0
- actshield/threatmodel/models.py +80 -0
- actshield/threatmodel/report.py +341 -0
- actshield/threatmodel/scenarios.py +212 -0
- actshield/threatmodel/scoring.py +66 -0
- actshield/threatmodel/threats.py +387 -0
- actshield/tools/__init__.py +20 -0
- actshield/tools/interceptor.py +256 -0
- actshield/tools/tool.py +71 -0
- actshield/tracing/__init__.py +38 -0
- actshield/tracing/correlation.py +173 -0
- actshield/tracing/events.py +75 -0
- actshield/tracing/tracer.py +305 -0
- actshield/watch.py +129 -0
- actshield-0.9.0.dist-info/METADATA +57 -0
- actshield-0.9.0.dist-info/RECORD +366 -0
- actshield-0.9.0.dist-info/WHEEL +5 -0
- actshield-0.9.0.dist-info/entry_points.txt +4 -0
- actshield-0.9.0.dist-info/top_level.txt +2 -0
- agentguard/__init__.py +39 -0
- agentguard/__main__.py +8 -0
- agentguard/py.typed +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Tools package."""
|
|
2
|
+
|
|
3
|
+
from actshield.tools.interceptor import protected_tool
|
|
4
|
+
from actshield.tools.tool import (
|
|
5
|
+
Resource,
|
|
6
|
+
SensitivityLevel,
|
|
7
|
+
ToolDefinition,
|
|
8
|
+
ToolRequest,
|
|
9
|
+
ToolResult,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Resource",
|
|
14
|
+
"SensitivityLevel",
|
|
15
|
+
"ToolDefinition",
|
|
16
|
+
"ToolRequest",
|
|
17
|
+
"ToolResult",
|
|
18
|
+
"protected_tool",
|
|
19
|
+
]
|
|
20
|
+
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Tool execution interception and runtime policy enforcement wrapper."""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import inspect
|
|
5
|
+
import time
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
|
|
7
|
+
|
|
8
|
+
from actshield.context.context import Context
|
|
9
|
+
from actshield.context.provenance import ContextSource
|
|
10
|
+
from actshield.context.taint import TaintState
|
|
11
|
+
from actshield.decisions.decision import DecisionAction, SecurityDecision
|
|
12
|
+
from actshield.tools.tool import Resource, SensitivityLevel, ToolDefinition, ToolRequest, ToolResult
|
|
13
|
+
from actshield.tracing.correlation import generate_id, get_current_context
|
|
14
|
+
from actshield.tracing.events import EventType
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from actshield.client import ActShield
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def protected_tool(
|
|
21
|
+
guard: "ActShield",
|
|
22
|
+
name: Optional[str] = None,
|
|
23
|
+
description: str = "",
|
|
24
|
+
sensitivity: SensitivityLevel = SensitivityLevel.MEDIUM,
|
|
25
|
+
required_capabilities: Optional[List[str]] = None,
|
|
26
|
+
classification: str = "internal",
|
|
27
|
+
target_resources: Optional[List[Resource]] = None,
|
|
28
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
29
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
30
|
+
"""Decorator to register and wrap a tool function with ActShield security enforcement."""
|
|
31
|
+
|
|
32
|
+
def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
33
|
+
tool_name = name or fn.__name__
|
|
34
|
+
tool_desc = description or fn.__doc__ or ""
|
|
35
|
+
req_caps = required_capabilities or []
|
|
36
|
+
res_list = target_resources or []
|
|
37
|
+
|
|
38
|
+
tool_def = ToolDefinition(
|
|
39
|
+
tool_id=generate_id("tool"),
|
|
40
|
+
name=tool_name,
|
|
41
|
+
description=tool_desc,
|
|
42
|
+
sensitivity=sensitivity,
|
|
43
|
+
required_capabilities=req_caps,
|
|
44
|
+
classification=classification,
|
|
45
|
+
target_resources=res_list,
|
|
46
|
+
metadata=metadata or {},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
guard.register_tool(tool_def)
|
|
50
|
+
|
|
51
|
+
if inspect.iscoroutinefunction(fn):
|
|
52
|
+
@functools.wraps(fn)
|
|
53
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
54
|
+
return await _execute_protected_tool_async(guard, tool_def, fn, *args, **kwargs)
|
|
55
|
+
async_wrapper.tool_def = tool_def # type: ignore
|
|
56
|
+
return async_wrapper
|
|
57
|
+
else:
|
|
58
|
+
@functools.wraps(fn)
|
|
59
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
60
|
+
return _execute_protected_tool_sync(guard, tool_def, fn, *args, **kwargs)
|
|
61
|
+
sync_wrapper.tool_def = tool_def # type: ignore
|
|
62
|
+
return sync_wrapper
|
|
63
|
+
|
|
64
|
+
return decorator
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _prepare_tool_request(
|
|
68
|
+
guard: "ActShield",
|
|
69
|
+
tool_def: ToolDefinition,
|
|
70
|
+
fn: Callable[..., Any],
|
|
71
|
+
*args: Any,
|
|
72
|
+
**kwargs: Any,
|
|
73
|
+
) -> ToolRequest:
|
|
74
|
+
ctx = get_current_context()
|
|
75
|
+
agent_id = ctx.agent_id if ctx else None
|
|
76
|
+
|
|
77
|
+
# Map positional args to param names
|
|
78
|
+
sig = inspect.signature(fn)
|
|
79
|
+
bound_args = sig.bind_partial(*args, **kwargs)
|
|
80
|
+
bound_args.apply_defaults()
|
|
81
|
+
raw_arguments = dict(bound_args.arguments)
|
|
82
|
+
|
|
83
|
+
# Sanitize arguments for logging/event storage
|
|
84
|
+
safe_arguments = guard.config.sanitize(raw_arguments)
|
|
85
|
+
|
|
86
|
+
context_ids = []
|
|
87
|
+
if ctx and ctx.context_id:
|
|
88
|
+
context_ids.append(ctx.context_id)
|
|
89
|
+
|
|
90
|
+
target_res = tool_def.target_resources[0] if tool_def.target_resources else None
|
|
91
|
+
|
|
92
|
+
return ToolRequest(
|
|
93
|
+
request_id=generate_id("req"),
|
|
94
|
+
tool_id=tool_def.tool_id,
|
|
95
|
+
tool_name=tool_def.name,
|
|
96
|
+
agent_id=agent_id,
|
|
97
|
+
arguments=safe_arguments,
|
|
98
|
+
context_ids=context_ids,
|
|
99
|
+
target_resource=target_res,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _execute_protected_tool_sync(
|
|
104
|
+
guard: "ActShield",
|
|
105
|
+
tool_def: ToolDefinition,
|
|
106
|
+
fn: Callable[..., Any],
|
|
107
|
+
*args: Any,
|
|
108
|
+
**kwargs: Any,
|
|
109
|
+
) -> Any:
|
|
110
|
+
request = _prepare_tool_request(guard, tool_def, fn, *args, **kwargs)
|
|
111
|
+
ctx = get_current_context()
|
|
112
|
+
|
|
113
|
+
# Emit tool.requested
|
|
114
|
+
guard.emit_event(
|
|
115
|
+
event_type=EventType.TOOL_REQUESTED,
|
|
116
|
+
agent_id=request.agent_id,
|
|
117
|
+
payload={
|
|
118
|
+
"tool_id": tool_def.tool_id,
|
|
119
|
+
"tool_name": tool_def.name,
|
|
120
|
+
"arguments": request.arguments,
|
|
121
|
+
"sensitivity": tool_def.sensitivity.value,
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Emit resource.access_requested if applicable
|
|
126
|
+
if request.target_resource:
|
|
127
|
+
guard.emit_event(
|
|
128
|
+
event_type=EventType.RESOURCE_ACCESS_REQUESTED,
|
|
129
|
+
agent_id=request.agent_id,
|
|
130
|
+
payload={
|
|
131
|
+
"resource_id": request.target_resource.resource_id,
|
|
132
|
+
"resource_name": request.target_resource.name,
|
|
133
|
+
"sensitivity": request.target_resource.sensitivity.value,
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Evaluate security policy
|
|
138
|
+
decision: SecurityDecision = guard.evaluate_tool_invocation(tool_def, request)
|
|
139
|
+
|
|
140
|
+
if decision.is_blocked:
|
|
141
|
+
raise PermissionError(
|
|
142
|
+
f"ActShield Policy Enforced: Tool '{tool_def.name}' invocation BLOCKED. "
|
|
143
|
+
f"Reason: [{decision.reason_code}] {decision.explanation}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
start_t = time.perf_counter()
|
|
147
|
+
err_str = None
|
|
148
|
+
try:
|
|
149
|
+
raw_output = fn(*args, **kwargs)
|
|
150
|
+
elapsed_ms = (time.perf_counter() - start_t) * 1000
|
|
151
|
+
output_for_event = guard.config.sanitize(raw_output)
|
|
152
|
+
|
|
153
|
+
# Emit tool.invoked
|
|
154
|
+
guard.emit_event(
|
|
155
|
+
event_type=EventType.TOOL_INVOKED,
|
|
156
|
+
agent_id=request.agent_id,
|
|
157
|
+
payload={
|
|
158
|
+
"tool_id": tool_def.tool_id,
|
|
159
|
+
"tool_name": tool_def.name,
|
|
160
|
+
"success": True,
|
|
161
|
+
"execution_time_ms": elapsed_ms,
|
|
162
|
+
"output_summary": str(output_for_event)[:200],
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
return raw_output
|
|
166
|
+
except Exception as e:
|
|
167
|
+
elapsed_ms = (time.perf_counter() - start_t) * 1000
|
|
168
|
+
err_str = str(e)
|
|
169
|
+
guard.emit_event(
|
|
170
|
+
event_type=EventType.TOOL_INVOKED,
|
|
171
|
+
agent_id=request.agent_id,
|
|
172
|
+
payload={
|
|
173
|
+
"tool_id": tool_def.tool_id,
|
|
174
|
+
"tool_name": tool_def.name,
|
|
175
|
+
"success": False,
|
|
176
|
+
"error": err_str,
|
|
177
|
+
"execution_time_ms": elapsed_ms,
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
raise
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
async def _execute_protected_tool_async(
|
|
184
|
+
guard: "ActShield",
|
|
185
|
+
tool_def: ToolDefinition,
|
|
186
|
+
fn: Callable[..., Any],
|
|
187
|
+
*args: Any,
|
|
188
|
+
**kwargs: Any,
|
|
189
|
+
) -> Any:
|
|
190
|
+
request = _prepare_tool_request(guard, tool_def, fn, *args, **kwargs)
|
|
191
|
+
|
|
192
|
+
# Emit tool.requested
|
|
193
|
+
guard.emit_event(
|
|
194
|
+
event_type=EventType.TOOL_REQUESTED,
|
|
195
|
+
agent_id=request.agent_id,
|
|
196
|
+
payload={
|
|
197
|
+
"tool_id": tool_def.tool_id,
|
|
198
|
+
"tool_name": tool_def.name,
|
|
199
|
+
"arguments": request.arguments,
|
|
200
|
+
"sensitivity": tool_def.sensitivity.value,
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if request.target_resource:
|
|
205
|
+
guard.emit_event(
|
|
206
|
+
event_type=EventType.RESOURCE_ACCESS_REQUESTED,
|
|
207
|
+
agent_id=request.agent_id,
|
|
208
|
+
payload={
|
|
209
|
+
"resource_id": request.target_resource.resource_id,
|
|
210
|
+
"resource_name": request.target_resource.name,
|
|
211
|
+
"sensitivity": request.target_resource.sensitivity.value,
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
decision: SecurityDecision = guard.evaluate_tool_invocation(tool_def, request)
|
|
216
|
+
|
|
217
|
+
if decision.is_blocked:
|
|
218
|
+
raise PermissionError(
|
|
219
|
+
f"ActShield Policy Enforced: Tool '{tool_def.name}' invocation BLOCKED. "
|
|
220
|
+
f"Reason: [{decision.reason_code}] {decision.explanation}"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
start_t = time.perf_counter()
|
|
224
|
+
try:
|
|
225
|
+
raw_output = await fn(*args, **kwargs)
|
|
226
|
+
elapsed_ms = (time.perf_counter() - start_t) * 1000
|
|
227
|
+
output_for_event = guard.config.sanitize(raw_output)
|
|
228
|
+
|
|
229
|
+
guard.emit_event(
|
|
230
|
+
event_type=EventType.TOOL_INVOKED,
|
|
231
|
+
agent_id=request.agent_id,
|
|
232
|
+
payload={
|
|
233
|
+
"tool_id": tool_def.tool_id,
|
|
234
|
+
"tool_name": tool_def.name,
|
|
235
|
+
"success": True,
|
|
236
|
+
"execution_time_ms": elapsed_ms,
|
|
237
|
+
"output_summary": str(output_for_event)[:200],
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
return raw_output
|
|
241
|
+
except Exception as e:
|
|
242
|
+
elapsed_ms = (time.perf_counter() - start_t) * 1000
|
|
243
|
+
guard.emit_event(
|
|
244
|
+
event_type=EventType.TOOL_INVOKED,
|
|
245
|
+
agent_id=request.agent_id,
|
|
246
|
+
payload={
|
|
247
|
+
"tool_id": tool_def.tool_id,
|
|
248
|
+
"tool_name": tool_def.name,
|
|
249
|
+
"success": False,
|
|
250
|
+
"error": str(e),
|
|
251
|
+
"execution_time_ms": elapsed_ms,
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
raise
|
|
255
|
+
|
|
256
|
+
|
actshield/tools/tool.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Tool and resource models for protected execution and access governance."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from actshield.risk.models import RiskLevel
|
|
8
|
+
from actshield.tracing.correlation import generate_id
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SensitivityLevel(str, Enum):
|
|
12
|
+
"""Sensitivity classification of tools and resources."""
|
|
13
|
+
LOW = "LOW"
|
|
14
|
+
MEDIUM = "MEDIUM"
|
|
15
|
+
HIGH = "HIGH"
|
|
16
|
+
CRITICAL = "CRITICAL"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Resource(BaseModel):
|
|
20
|
+
"""A protected asset, system, database, or API target accessed via tools."""
|
|
21
|
+
resource_id: str = Field(default_factory=lambda: generate_id("rsc"))
|
|
22
|
+
name: str = Field(..., description="Resource name (e.g. 'financial_database', 'user_pii_vault')")
|
|
23
|
+
resource_type: str = Field(default="api", description="Resource type ('database', 'filesystem', 'api', 'model')")
|
|
24
|
+
uri: Optional[str] = Field(default=None, description="URI or path locator")
|
|
25
|
+
sensitivity: SensitivityLevel = Field(default=SensitivityLevel.MEDIUM)
|
|
26
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ToolDefinition(BaseModel):
|
|
30
|
+
"""Metadata and security properties defining an executable tool or MCP endpoint."""
|
|
31
|
+
|
|
32
|
+
tool_id: str = Field(default_factory=lambda: generate_id("tool"))
|
|
33
|
+
name: str = Field(..., description="Canonical tool name (e.g. 'execute_sql_query', 'fetch_webpage')")
|
|
34
|
+
description: str = Field(default="", description="Functional description of the tool")
|
|
35
|
+
sensitivity: SensitivityLevel = Field(default=SensitivityLevel.MEDIUM, description="Sensitivity classification")
|
|
36
|
+
required_capabilities: List[str] = Field(
|
|
37
|
+
default_factory=list,
|
|
38
|
+
description="List of capability names an agent must possess to invoke this tool"
|
|
39
|
+
)
|
|
40
|
+
classification: str = Field(
|
|
41
|
+
default="internal",
|
|
42
|
+
description="Origin classification: 'internal', 'external', 'mcp', 'sensitive'"
|
|
43
|
+
)
|
|
44
|
+
target_resources: List[Resource] = Field(
|
|
45
|
+
default_factory=list,
|
|
46
|
+
description="Underlying resources accessed by this tool"
|
|
47
|
+
)
|
|
48
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ToolRequest(BaseModel):
|
|
52
|
+
"""A concrete invocation request targeting a tool."""
|
|
53
|
+
|
|
54
|
+
request_id: str = Field(default_factory=lambda: generate_id("req"))
|
|
55
|
+
tool_id: str = Field(default_factory=lambda: generate_id("tool"), description="Tool ID being invoked")
|
|
56
|
+
tool_name: str = Field(..., description="Tool name")
|
|
57
|
+
agent_id: Optional[str] = Field(default=None, description="Requesting agent ID")
|
|
58
|
+
|
|
59
|
+
arguments: Dict[str, Any] = Field(default_factory=dict, description="Tool invocation parameters")
|
|
60
|
+
context_ids: List[str] = Field(default_factory=list, description="IDs of context objects influencing this request")
|
|
61
|
+
target_resource: Optional[Resource] = Field(default=None, description="Specific target resource if applicable")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ToolResult(BaseModel):
|
|
65
|
+
"""The result or error produced by a tool execution."""
|
|
66
|
+
|
|
67
|
+
success: bool = Field(default=True)
|
|
68
|
+
output: Any = Field(default=None)
|
|
69
|
+
error: Optional[str] = Field(default=None)
|
|
70
|
+
execution_time_ms: float = Field(default=0.0)
|
|
71
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Tracing package."""
|
|
2
|
+
|
|
3
|
+
from actshield.tracing.correlation import (
|
|
4
|
+
CorrelationContext,
|
|
5
|
+
SpanScope,
|
|
6
|
+
generate_id,
|
|
7
|
+
get_current_agent_id,
|
|
8
|
+
get_current_context,
|
|
9
|
+
get_current_delegation_id,
|
|
10
|
+
get_current_span_id,
|
|
11
|
+
get_current_task_id,
|
|
12
|
+
get_current_trace_id,
|
|
13
|
+
reset_current_context,
|
|
14
|
+
set_current_context,
|
|
15
|
+
)
|
|
16
|
+
from actshield.tracing.events import EventType, SecurityEvent
|
|
17
|
+
from actshield.tracing.tracer import CausalEdge, CausalGraph, CausalNode, TraceManager
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"CorrelationContext",
|
|
21
|
+
"SpanScope",
|
|
22
|
+
"generate_id",
|
|
23
|
+
"get_current_context",
|
|
24
|
+
"set_current_context",
|
|
25
|
+
"reset_current_context",
|
|
26
|
+
"get_current_trace_id",
|
|
27
|
+
"get_current_span_id",
|
|
28
|
+
"get_current_agent_id",
|
|
29
|
+
"get_current_task_id",
|
|
30
|
+
"get_current_delegation_id",
|
|
31
|
+
"EventType",
|
|
32
|
+
"SecurityEvent",
|
|
33
|
+
"CausalNode",
|
|
34
|
+
"CausalEdge",
|
|
35
|
+
"CausalGraph",
|
|
36
|
+
"TraceManager",
|
|
37
|
+
]
|
|
38
|
+
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Correlation context management for distributed and multi-agent tracing."""
|
|
2
|
+
|
|
3
|
+
import contextvars
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_id(prefix: str = "") -> str:
|
|
11
|
+
"""Generate a unique hex/uuid based ID with an optional prefix."""
|
|
12
|
+
unique_part = uuid.uuid4().hex[:16]
|
|
13
|
+
if prefix:
|
|
14
|
+
return f"{prefix}_{unique_part}"
|
|
15
|
+
return unique_part
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class CorrelationContext:
|
|
20
|
+
"""Immutable correlation context representing current execution coordinates."""
|
|
21
|
+
trace_id: str
|
|
22
|
+
span_id: str
|
|
23
|
+
parent_span_id: Optional[str] = None
|
|
24
|
+
task_id: Optional[str] = None
|
|
25
|
+
agent_id: Optional[str] = None
|
|
26
|
+
parent_agent_id: Optional[str] = None
|
|
27
|
+
delegation_id: Optional[str] = None
|
|
28
|
+
parent_event_id: Optional[str] = None
|
|
29
|
+
context_id: Optional[str] = None
|
|
30
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
31
|
+
|
|
32
|
+
def derive_child_span(
|
|
33
|
+
self,
|
|
34
|
+
span_id: Optional[str] = None,
|
|
35
|
+
agent_id: Optional[str] = None,
|
|
36
|
+
task_id: Optional[str] = None,
|
|
37
|
+
delegation_id: Optional[str] = None,
|
|
38
|
+
context_id: Optional[str] = None,
|
|
39
|
+
parent_event_id: Optional[str] = None,
|
|
40
|
+
extra_metadata: Optional[Dict[str, Any]] = None,
|
|
41
|
+
) -> "CorrelationContext":
|
|
42
|
+
"""Derive a child correlation context preserving the trace_id and updating span/agent relationships."""
|
|
43
|
+
new_span_id = span_id or generate_id("spn")
|
|
44
|
+
new_metadata = dict(self.metadata)
|
|
45
|
+
if extra_metadata:
|
|
46
|
+
new_metadata.update(extra_metadata)
|
|
47
|
+
|
|
48
|
+
return CorrelationContext(
|
|
49
|
+
trace_id=self.trace_id,
|
|
50
|
+
span_id=new_span_id,
|
|
51
|
+
parent_span_id=self.span_id,
|
|
52
|
+
task_id=task_id if task_id is not None else self.task_id,
|
|
53
|
+
agent_id=agent_id if agent_id is not None else self.agent_id,
|
|
54
|
+
parent_agent_id=self.agent_id if (agent_id is not None and agent_id != self.agent_id) else self.parent_agent_id,
|
|
55
|
+
delegation_id=delegation_id if delegation_id is not None else self.delegation_id,
|
|
56
|
+
parent_event_id=parent_event_id if parent_event_id is not None else self.parent_event_id,
|
|
57
|
+
context_id=context_id if context_id is not None else self.context_id,
|
|
58
|
+
metadata=new_metadata,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
_current_correlation_ctx: contextvars.ContextVar[Optional[CorrelationContext]] = contextvars.ContextVar(
|
|
63
|
+
"agentguard_correlation_ctx", default=None
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_current_context() -> Optional[CorrelationContext]:
|
|
68
|
+
"""Retrieve the active correlation context for the current async/sync task."""
|
|
69
|
+
return _current_correlation_ctx.get()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def set_current_context(ctx: Optional[CorrelationContext]) -> contextvars.Token:
|
|
73
|
+
"""Set the active correlation context and return the token to reset later."""
|
|
74
|
+
return _current_correlation_ctx.set(ctx)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def reset_current_context(token: contextvars.Token) -> None:
|
|
78
|
+
"""Reset the correlation context using a saved token."""
|
|
79
|
+
_current_correlation_ctx.reset(token)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_current_trace_id() -> Optional[str]:
|
|
83
|
+
"""Get the active trace ID if in an active trace scope."""
|
|
84
|
+
ctx = get_current_context()
|
|
85
|
+
return ctx.trace_id if ctx else None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_current_span_id() -> Optional[str]:
|
|
89
|
+
"""Get the active span ID if in an active span scope."""
|
|
90
|
+
ctx = get_current_context()
|
|
91
|
+
return ctx.span_id if ctx else None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_current_agent_id() -> Optional[str]:
|
|
95
|
+
"""Get the active agent ID."""
|
|
96
|
+
ctx = get_current_context()
|
|
97
|
+
return ctx.agent_id if ctx else None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_current_task_id() -> Optional[str]:
|
|
101
|
+
"""Get the active task ID."""
|
|
102
|
+
ctx = get_current_context()
|
|
103
|
+
return ctx.task_id if ctx else None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_current_delegation_id() -> Optional[str]:
|
|
107
|
+
"""Get the active delegation ID."""
|
|
108
|
+
ctx = get_current_context()
|
|
109
|
+
return ctx.delegation_id if ctx else None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class SpanScope:
|
|
113
|
+
"""Context manager for establishing or nesting a correlation span."""
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
name: str = "span",
|
|
118
|
+
trace_id: Optional[str] = None,
|
|
119
|
+
span_id: Optional[str] = None,
|
|
120
|
+
agent_id: Optional[str] = None,
|
|
121
|
+
task_id: Optional[str] = None,
|
|
122
|
+
delegation_id: Optional[str] = None,
|
|
123
|
+
context_id: Optional[str] = None,
|
|
124
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
self.name = name
|
|
127
|
+
self._requested_trace_id = trace_id
|
|
128
|
+
self._requested_span_id = span_id
|
|
129
|
+
self._agent_id = agent_id
|
|
130
|
+
self._task_id = task_id
|
|
131
|
+
self._delegation_id = delegation_id
|
|
132
|
+
self._context_id = context_id
|
|
133
|
+
self._metadata = metadata or {}
|
|
134
|
+
self._token: Optional[contextvars.Token] = None
|
|
135
|
+
self.context: Optional[CorrelationContext] = None
|
|
136
|
+
self.start_time: float = 0.0
|
|
137
|
+
|
|
138
|
+
def __enter__(self) -> CorrelationContext:
|
|
139
|
+
self.start_time = time.time()
|
|
140
|
+
parent_ctx = get_current_context()
|
|
141
|
+
if parent_ctx is not None:
|
|
142
|
+
self.context = parent_ctx.derive_child_span(
|
|
143
|
+
span_id=self._requested_span_id,
|
|
144
|
+
agent_id=self._agent_id,
|
|
145
|
+
task_id=self._task_id,
|
|
146
|
+
delegation_id=self._delegation_id,
|
|
147
|
+
context_id=self._context_id,
|
|
148
|
+
extra_metadata=self._metadata,
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
trace_id = self._requested_trace_id or generate_id("trc")
|
|
152
|
+
span_id = self._requested_span_id or generate_id("spn")
|
|
153
|
+
self.context = CorrelationContext(
|
|
154
|
+
trace_id=trace_id,
|
|
155
|
+
span_id=span_id,
|
|
156
|
+
task_id=self._task_id,
|
|
157
|
+
agent_id=self._agent_id,
|
|
158
|
+
delegation_id=self._delegation_id,
|
|
159
|
+
context_id=self._context_id,
|
|
160
|
+
metadata=self._metadata,
|
|
161
|
+
)
|
|
162
|
+
self._token = set_current_context(self.context)
|
|
163
|
+
return self.context
|
|
164
|
+
|
|
165
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
166
|
+
if self._token is not None:
|
|
167
|
+
reset_current_context(self._token)
|
|
168
|
+
|
|
169
|
+
async def __aenter__(self) -> CorrelationContext:
|
|
170
|
+
return self.__enter__()
|
|
171
|
+
|
|
172
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
173
|
+
self.__exit__(exc_type, exc_val, exc_tb)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Strongly typed security events for multi-agent observability and enforcement."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
|
|
8
|
+
from actshield.tracing.correlation import generate_id
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EventType(str, Enum):
|
|
12
|
+
"""Enumeration of standard security-relevant event types."""
|
|
13
|
+
AGENT_CREATED = "agent.created"
|
|
14
|
+
AGENT_INVOKED = "agent.invoked"
|
|
15
|
+
TASK_STARTED = "task.started"
|
|
16
|
+
TASK_COMPLETED = "task.completed"
|
|
17
|
+
AGENT_DELEGATED = "agent.delegated"
|
|
18
|
+
CONTEXT_RECEIVED = "context.received"
|
|
19
|
+
CONTEXT_GENERATED = "context.generated"
|
|
20
|
+
CONTEXT_PROPAGATED = "context.propagated"
|
|
21
|
+
CONTEXT_SANITIZED = "context.sanitized"
|
|
22
|
+
TOOL_REQUESTED = "tool.requested"
|
|
23
|
+
TOOL_INVOKED = "tool.invoked"
|
|
24
|
+
TOOL_COMPLETED = "tool.completed"
|
|
25
|
+
RESOURCE_ACCESS_REQUESTED = "resource.access_requested"
|
|
26
|
+
SECURITY_EVALUATED = "security.evaluated"
|
|
27
|
+
SECURITY_DECISION = "security.decision"
|
|
28
|
+
HUMAN_APPROVAL_REQUESTED = "human.approval_requested"
|
|
29
|
+
HUMAN_APPROVAL_RECEIVED = "human.approval_received"
|
|
30
|
+
APPROVAL_REQUESTED = "approval.requested"
|
|
31
|
+
APPROVAL_GRANTED = "approval.granted"
|
|
32
|
+
APPROVAL_REJECTED = "approval.rejected"
|
|
33
|
+
INCIDENT_CREATED = "incident.created"
|
|
34
|
+
AI_ANALYSIS_COMPLETED = "ai.analysis_completed"
|
|
35
|
+
POSTURE_EVALUATED = "posture.evaluated"
|
|
36
|
+
INCIDENT_TRANSITIONED = "incident.transitioned"
|
|
37
|
+
RESPONSE_EXECUTED = "response.executed"
|
|
38
|
+
DRIFT_DETECTED = "drift.detected"
|
|
39
|
+
GATE_EVALUATED = "gate.evaluated"
|
|
40
|
+
CUSTOM_EVENT = "custom.event"
|
|
41
|
+
SECURITY_ALERT = "security.alert"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SecurityEvent(BaseModel):
|
|
47
|
+
"""Core security event model for distributed provenance and causal reconstruction."""
|
|
48
|
+
|
|
49
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
50
|
+
|
|
51
|
+
event_id: str = Field(default_factory=lambda: generate_id("evt"), description="Unique event ID")
|
|
52
|
+
event_type: EventType = Field(..., description="Categorical type of the security event")
|
|
53
|
+
timestamp: datetime = Field(
|
|
54
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
55
|
+
description="UTC timestamp of when the event occurred"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Correlation coordinates
|
|
59
|
+
trace_id: str = Field(..., description="Root trace ID for causal correlation")
|
|
60
|
+
span_id: str = Field(..., description="Span ID representing the current execution boundary")
|
|
61
|
+
parent_event_id: Optional[str] = Field(default=None, description="Immediate causal parent event ID")
|
|
62
|
+
|
|
63
|
+
# Agent coordinates
|
|
64
|
+
agent_id: Optional[str] = Field(default=None, description="Acting agent ID")
|
|
65
|
+
parent_agent_id: Optional[str] = Field(default=None, description="Delegating or parent agent ID")
|
|
66
|
+
|
|
67
|
+
# Execution coordinates
|
|
68
|
+
task_id: Optional[str] = Field(default=None, description="Active task ID")
|
|
69
|
+
delegation_id: Optional[str] = Field(default=None, description="Active delegation context ID")
|
|
70
|
+
context_id: Optional[str] = Field(default=None, description="Associated context or data lineage ID")
|
|
71
|
+
|
|
72
|
+
# Event data
|
|
73
|
+
payload: Dict[str, Any] = Field(default_factory=dict, description="Structured event payload")
|
|
74
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Custom metadata and tags")
|
|
75
|
+
|