capfence 0.6.2__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.
- capfence/__init__.py +109 -0
- capfence/assessment/__init__.py +17 -0
- capfence/assessment/builder.py +301 -0
- capfence/assessment/eu_ai_act.py +241 -0
- capfence/assessment/owasp.py +153 -0
- capfence/assessment/reporter.py +114 -0
- capfence/assessment/scanner.py +337 -0
- capfence/assessment/simulator.py +245 -0
- capfence/assessment/templates/report.html +637 -0
- capfence/assessment/templates/report_compliance.html +682 -0
- capfence/assessment/templates/report_eu_ai_act.html +139 -0
- capfence/assessment/templates/report_owasp.html +92 -0
- capfence/check.py +401 -0
- capfence/cli.py +655 -0
- capfence/cloud/__init__.py +10 -0
- capfence/cloud/client.py +149 -0
- capfence/cloud/evaluator.py +85 -0
- capfence/core/approvals.py +224 -0
- capfence/core/audit.py +249 -0
- capfence/core/capabilities.py +83 -0
- capfence/core/chain.py +140 -0
- capfence/core/fsm.py +55 -0
- capfence/core/gate.py +397 -0
- capfence/core/hash.py +26 -0
- capfence/core/keys.py +208 -0
- capfence/core/plugins.py +55 -0
- capfence/core/policy.py +369 -0
- capfence/core/scorer.py +231 -0
- capfence/core/state.py +203 -0
- capfence/core/taxonomy.py +194 -0
- capfence/errors.py +69 -0
- capfence/flow/__init__.py +9 -0
- capfence/flow/tracer.py +317 -0
- capfence/framework/_base.py +62 -0
- capfence/framework/_risk.py +23 -0
- capfence/framework/autogen.py +53 -0
- capfence/framework/autogpt.py +21 -0
- capfence/framework/babyagi.py +21 -0
- capfence/framework/crewai.py +78 -0
- capfence/framework/langchain.py +118 -0
- capfence/framework/langgraph.py +140 -0
- capfence/framework/llamaindex.py +57 -0
- capfence/framework/openai_agents.py +94 -0
- capfence/framework/pydanticai.py +59 -0
- capfence/framework/swarm.py +21 -0
- capfence/mcp/__init__.py +10 -0
- capfence/mcp/adapter.py +79 -0
- capfence/mcp/gateway.py +263 -0
- capfence/py.typed +0 -0
- capfence/taxonomies/financial.json +396 -0
- capfence/taxonomies/financial_crypto.json +97 -0
- capfence/taxonomies/financial_plaid.json +123 -0
- capfence/taxonomies/general.json +112 -0
- capfence/taxonomies/healthcare.json +92 -0
- capfence/taxonomies/legal.json +47 -0
- capfence/telemetry/__init__.py +10 -0
- capfence/telemetry/client.py +178 -0
- capfence/types.py +19 -0
- capfence-0.6.2.dist-info/METADATA +267 -0
- capfence-0.6.2.dist-info/RECORD +63 -0
- capfence-0.6.2.dist-info/WHEEL +4 -0
- capfence-0.6.2.dist-info/entry_points.txt +2 -0
- capfence-0.6.2.dist-info/licenses/LICENSE +21 -0
capfence/__init__.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""CAPFENCE — Runtime governance for AI agents.
|
|
2
|
+
|
|
3
|
+
MIT licensed. Works offline. Rule-based gating with pluggable scoring.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "0.6.2"
|
|
7
|
+
|
|
8
|
+
from capfence.core.gate import Gate
|
|
9
|
+
from capfence.types import GateResult
|
|
10
|
+
from capfence.errors import (
|
|
11
|
+
CapFenceError,
|
|
12
|
+
AgentActionBlocked,
|
|
13
|
+
ConfigurationError,
|
|
14
|
+
PolicyLoadError,
|
|
15
|
+
AuditError,
|
|
16
|
+
TaxonomyError,
|
|
17
|
+
GatewayError,
|
|
18
|
+
)
|
|
19
|
+
from capfence.core.fsm import FSMOutcome, FailClosedFSM
|
|
20
|
+
from capfence.core.state import AgentStateStore
|
|
21
|
+
from capfence.core.taxonomy import TaxonomyLoader, stripe_mapper
|
|
22
|
+
from capfence.core.hash import compute_payload_hash
|
|
23
|
+
from capfence.core.audit import AuditLogger
|
|
24
|
+
from capfence.core.chain import verify_chain, verify_chain_from_rows, ChainEntry
|
|
25
|
+
from capfence.core.keys import generate_keypair, load_keypair, ensure_keypair, sign_entry, verify_entry
|
|
26
|
+
from capfence.core.scorer import BaseScorer, KeywordScorer, RegexASTScorer, AdaptiveScorer, load_scorer
|
|
27
|
+
from capfence.cloud.client import CloudClient
|
|
28
|
+
from capfence.check import scan_directory, scan_file, ToolFinding
|
|
29
|
+
from capfence.assessment.scanner import scan_assessment, AssessmentData, ToolAssessment
|
|
30
|
+
from capfence.assessment.reporter import generate_html_report
|
|
31
|
+
from capfence.assessment.simulator import TraceSimulator
|
|
32
|
+
from capfence.assessment.builder import TaxonomyBuilder
|
|
33
|
+
from capfence.assessment.owasp import get_coverage_matrix, get_coverage_summary, generate_owasp_context
|
|
34
|
+
from capfence.assessment.eu_ai_act import generate_evidence_pack, EvidencePack
|
|
35
|
+
from capfence.framework.langchain import CapFenceTool
|
|
36
|
+
from capfence.framework.crewai import CapFenceCrewAITool
|
|
37
|
+
from capfence.framework.langgraph import CapFenceToolNode
|
|
38
|
+
from capfence.framework.openai_agents import CapFenceOpenAITool
|
|
39
|
+
from capfence.framework.pydanticai import CapFencePydanticTool
|
|
40
|
+
from capfence.framework.llamaindex import CapFenceLlamaIndexTool
|
|
41
|
+
from capfence.framework.autogen import CapFenceAutoGenTool
|
|
42
|
+
from capfence.mcp.gateway import MCPGatewayServer
|
|
43
|
+
from capfence.mcp.adapter import CapFenceMCPSession
|
|
44
|
+
from capfence.telemetry.client import TelemetryClient
|
|
45
|
+
from capfence.flow.tracer import FlowTracer, FlowEdge, TrustLevel
|
|
46
|
+
from capfence.core.gate import GATE_MODE_ENFORCE, GATE_MODE_OBSERVE
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"__version__",
|
|
50
|
+
"Gate",
|
|
51
|
+
"GateResult",
|
|
52
|
+
"CapFenceError",
|
|
53
|
+
"AgentActionBlocked",
|
|
54
|
+
"ConfigurationError",
|
|
55
|
+
"PolicyLoadError",
|
|
56
|
+
"AuditError",
|
|
57
|
+
"TaxonomyError",
|
|
58
|
+
"GatewayError",
|
|
59
|
+
"FSMOutcome",
|
|
60
|
+
"FailClosedFSM",
|
|
61
|
+
"AgentStateStore",
|
|
62
|
+
"TaxonomyLoader",
|
|
63
|
+
"stripe_mapper",
|
|
64
|
+
"compute_payload_hash",
|
|
65
|
+
"AuditLogger",
|
|
66
|
+
"verify_chain",
|
|
67
|
+
"verify_chain_from_rows",
|
|
68
|
+
"ChainEntry",
|
|
69
|
+
"generate_keypair",
|
|
70
|
+
"load_keypair",
|
|
71
|
+
"ensure_keypair",
|
|
72
|
+
"sign_entry",
|
|
73
|
+
"verify_entry",
|
|
74
|
+
"BaseScorer",
|
|
75
|
+
"KeywordScorer",
|
|
76
|
+
"RegexASTScorer",
|
|
77
|
+
"AdaptiveScorer",
|
|
78
|
+
"load_scorer",
|
|
79
|
+
"CloudClient",
|
|
80
|
+
"scan_directory",
|
|
81
|
+
"scan_file",
|
|
82
|
+
"ToolFinding",
|
|
83
|
+
"scan_assessment",
|
|
84
|
+
"AssessmentData",
|
|
85
|
+
"ToolAssessment",
|
|
86
|
+
"generate_html_report",
|
|
87
|
+
"TraceSimulator",
|
|
88
|
+
"TaxonomyBuilder",
|
|
89
|
+
"get_coverage_matrix",
|
|
90
|
+
"get_coverage_summary",
|
|
91
|
+
"generate_owasp_context",
|
|
92
|
+
"generate_evidence_pack",
|
|
93
|
+
"EvidencePack",
|
|
94
|
+
"CapFenceTool",
|
|
95
|
+
"CapFenceCrewAITool",
|
|
96
|
+
"CapFenceToolNode",
|
|
97
|
+
"CapFenceOpenAITool",
|
|
98
|
+
"CapFencePydanticTool",
|
|
99
|
+
"CapFenceLlamaIndexTool",
|
|
100
|
+
"CapFenceAutoGenTool",
|
|
101
|
+
"MCPGatewayServer",
|
|
102
|
+
"CapFenceMCPSession",
|
|
103
|
+
"TelemetryClient",
|
|
104
|
+
"FlowTracer",
|
|
105
|
+
"FlowEdge",
|
|
106
|
+
"TrustLevel",
|
|
107
|
+
"GATE_MODE_ENFORCE",
|
|
108
|
+
"GATE_MODE_OBSERVE",
|
|
109
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Assessment toolkit for CapFence.
|
|
2
|
+
|
|
3
|
+
Orchestrates static analysis into professional deliverables:
|
|
4
|
+
- scanner.py: Enriches raw AST findings with taxonomy data
|
|
5
|
+
- reporter.py: Generates HTML reports via Jinja2 templates
|
|
6
|
+
- simulator.py: Replays agent traces through the gate (Month 2)
|
|
7
|
+
- builder.py: Interactive taxonomy builder (Month 2)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from capfence.assessment.scanner import scan_assessment, AssessmentData
|
|
11
|
+
from capfence.assessment.reporter import generate_html_report
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"scan_assessment",
|
|
15
|
+
"AssessmentData",
|
|
16
|
+
"generate_html_report",
|
|
17
|
+
]
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""Interactive taxonomy builder — generates custom taxonomies from user input.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
from capfence.assessment.builder import TaxonomyBuilder
|
|
5
|
+
|
|
6
|
+
builder = TaxonomyBuilder()
|
|
7
|
+
taxonomy = builder.interactive_build()
|
|
8
|
+
# or programmatic:
|
|
9
|
+
taxonomy = builder.build(industry="fintech", payment_methods=["stripe"], pii=True)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class BuilderConfig:
|
|
26
|
+
"""Configuration for taxonomy builder."""
|
|
27
|
+
industry: str
|
|
28
|
+
payment_methods: list[str] = field(default_factory=list)
|
|
29
|
+
pii_access: bool = False
|
|
30
|
+
transfer_initiation: bool = False
|
|
31
|
+
has_write_tools: bool = True
|
|
32
|
+
has_delete_tools: bool = False
|
|
33
|
+
has_external_api: bool = True
|
|
34
|
+
compliance_required: list[str] = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TaxonomyBuilder:
|
|
38
|
+
"""Build custom taxonomies from interactive or programmatic input."""
|
|
39
|
+
|
|
40
|
+
INDUSTRY_PRESETS: dict[str, dict[str, Any]] = {
|
|
41
|
+
"fintech": {
|
|
42
|
+
"categories": ["balance_inquiry", "payment_initiation", "withdrawal",
|
|
43
|
+
"high_value_transfer", "account_modification", "compliance_check"],
|
|
44
|
+
"payment_pack": True,
|
|
45
|
+
"compliance": ["PCI-DSS", "SOX"],
|
|
46
|
+
},
|
|
47
|
+
"healthcare": {
|
|
48
|
+
"categories": ["read_only", "write", "external_api", "delete"],
|
|
49
|
+
"payment_pack": False,
|
|
50
|
+
"compliance": ["HIPAA"],
|
|
51
|
+
},
|
|
52
|
+
"legal": {
|
|
53
|
+
"categories": ["read_only", "write", "external_api"],
|
|
54
|
+
"payment_pack": False,
|
|
55
|
+
"compliance": ["GDPR", "eDiscovery"],
|
|
56
|
+
},
|
|
57
|
+
"retail": {
|
|
58
|
+
"categories": ["read_only", "write", "payment_initiation"],
|
|
59
|
+
"payment_pack": True,
|
|
60
|
+
"compliance": ["PCI-DSS"],
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def interactive_build(self) -> dict[str, Any]:
|
|
65
|
+
"""Build taxonomy via CLI prompts."""
|
|
66
|
+
print("\n--- CapFence Taxonomy Builder ---\n")
|
|
67
|
+
|
|
68
|
+
industries = list(self.INDUSTRY_PRESETS.keys()) + ["custom"]
|
|
69
|
+
print("Available industries:", ", ".join(industries))
|
|
70
|
+
industry = input("> What industry? [fintech/healthcare/legal/retail/custom]: ").strip().lower()
|
|
71
|
+
|
|
72
|
+
if industry == "custom":
|
|
73
|
+
return self._build_custom()
|
|
74
|
+
|
|
75
|
+
config = self.INDUSTRY_PRESETS.get(industry, self.INDUSTRY_PRESETS["fintech"])
|
|
76
|
+
|
|
77
|
+
payment_methods: list[str] = []
|
|
78
|
+
if config["payment_pack"]:
|
|
79
|
+
pm = input("> What payment methods? [stripe/plaid/square/braintree/custom/none]: ").strip().lower()
|
|
80
|
+
if pm != "none":
|
|
81
|
+
payment_methods = [p.strip() for p in pm.split("/")]
|
|
82
|
+
|
|
83
|
+
pii = input("> Do agents access PII? [y/n]: ").strip().lower().startswith("y")
|
|
84
|
+
transfers = input("> Do agents initiate transfers? [y/n]: ").strip().lower().startswith("y") if config["payment_pack"] else False
|
|
85
|
+
|
|
86
|
+
return self.build(
|
|
87
|
+
industry=industry,
|
|
88
|
+
payment_methods=payment_methods,
|
|
89
|
+
pii_access=pii,
|
|
90
|
+
transfer_initiation=transfers,
|
|
91
|
+
compliance_required=config["compliance"],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def build(
|
|
95
|
+
self,
|
|
96
|
+
industry: str,
|
|
97
|
+
payment_methods: list[str] | None = None,
|
|
98
|
+
pii_access: bool = False,
|
|
99
|
+
transfer_initiation: bool = False,
|
|
100
|
+
has_write_tools: bool = True,
|
|
101
|
+
has_delete_tools: bool = False,
|
|
102
|
+
has_external_api: bool = True,
|
|
103
|
+
compliance_required: list[str] | None = None,
|
|
104
|
+
) -> dict[str, Any]:
|
|
105
|
+
"""Programmatically build a custom taxonomy."""
|
|
106
|
+
|
|
107
|
+
config = BuilderConfig(
|
|
108
|
+
industry=industry,
|
|
109
|
+
payment_methods=payment_methods or [],
|
|
110
|
+
pii_access=pii_access,
|
|
111
|
+
transfer_initiation=transfer_initiation,
|
|
112
|
+
has_write_tools=has_write_tools,
|
|
113
|
+
has_delete_tools=has_delete_tools,
|
|
114
|
+
has_external_api=has_external_api,
|
|
115
|
+
compliance_required=compliance_required or [],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
taxonomy: dict[str, Any] = {
|
|
119
|
+
"version": "2.0",
|
|
120
|
+
"domain": industry,
|
|
121
|
+
"generated_by": "capfence_taxonomy_builder",
|
|
122
|
+
"description": f"Custom taxonomy for {industry} agents",
|
|
123
|
+
"categories": {},
|
|
124
|
+
"compliance_mapping": {},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Base categories
|
|
128
|
+
base_cats = self._get_base_categories(config)
|
|
129
|
+
taxonomy["categories"] = base_cats
|
|
130
|
+
|
|
131
|
+
# Payment method packs
|
|
132
|
+
if config.payment_methods:
|
|
133
|
+
for method in config.payment_methods:
|
|
134
|
+
payment_cats = self._get_payment_categories(method)
|
|
135
|
+
taxonomy["categories"].update(payment_cats)
|
|
136
|
+
|
|
137
|
+
# Compliance mappings
|
|
138
|
+
if config.compliance_required:
|
|
139
|
+
taxonomy["compliance_mapping"] = self._get_compliance_map(config.compliance_required)
|
|
140
|
+
|
|
141
|
+
return taxonomy
|
|
142
|
+
|
|
143
|
+
def save(self, taxonomy: dict[str, Any], path: Path | None = None) -> Path:
|
|
144
|
+
"""Save taxonomy to JSON file."""
|
|
145
|
+
if path is None:
|
|
146
|
+
domain = taxonomy.get("domain", "custom")
|
|
147
|
+
path = Path(f"custom_taxonomy_{domain}.json")
|
|
148
|
+
path.write_text(json.dumps(taxonomy, indent=2), encoding="utf-8")
|
|
149
|
+
logger.info("Saved taxonomy to %s", path)
|
|
150
|
+
return path
|
|
151
|
+
|
|
152
|
+
def _get_base_categories(self, config: BuilderConfig) -> dict[str, Any]:
|
|
153
|
+
"""Get base risk categories for the configuration."""
|
|
154
|
+
cats: dict[str, Any] = {}
|
|
155
|
+
|
|
156
|
+
cats["read_only"] = {
|
|
157
|
+
"delta": 1.0,
|
|
158
|
+
"risk_keywords": ["read", "list", "get", "view", "search", "balance", "inquiry"],
|
|
159
|
+
"description": "Read-only operations",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if config.has_write_tools:
|
|
163
|
+
cats["write"] = {
|
|
164
|
+
"delta": 0.4,
|
|
165
|
+
"risk_keywords": ["write", "create", "insert", "post", "submit", "update"],
|
|
166
|
+
"description": "Data mutation operations",
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if config.has_delete_tools:
|
|
170
|
+
cats["delete"] = {
|
|
171
|
+
"delta": 0.2,
|
|
172
|
+
"risk_keywords": ["delete", "remove", "drop", "destroy", "purge"],
|
|
173
|
+
"description": "Destructive operations",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if config.has_external_api:
|
|
177
|
+
cats["external_api"] = {
|
|
178
|
+
"delta": 0.3,
|
|
179
|
+
"risk_keywords": ["external", "api_call", "third_party", "webhook", "outbound"],
|
|
180
|
+
"description": "External API calls",
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if config.pii_access:
|
|
184
|
+
cats["pii_access"] = {
|
|
185
|
+
"delta": 0.15,
|
|
186
|
+
"risk_keywords": ["pii", "ssn", "ssn_last4", "personal_data", "phi", "patient"],
|
|
187
|
+
"description": "Access to personally identifiable information",
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if config.transfer_initiation:
|
|
191
|
+
cats["high_value_transfer"] = {
|
|
192
|
+
"delta": 0.15,
|
|
193
|
+
"risk_keywords": ["transfer", "wire", "swift", "ach", "high_value", "large_amount"],
|
|
194
|
+
"description": "Large or bulk transfers. Requires multi-factor approval.",
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return cats
|
|
198
|
+
|
|
199
|
+
def _get_payment_categories(self, method: str) -> dict[str, Any]:
|
|
200
|
+
"""Get payment-method-specific categories."""
|
|
201
|
+
method = method.lower()
|
|
202
|
+
|
|
203
|
+
if method == "stripe":
|
|
204
|
+
return {
|
|
205
|
+
"stripe_payment_initiation": {
|
|
206
|
+
"delta": 0.3,
|
|
207
|
+
"risk_keywords": ["charge", "payment_intent", "create_charge", "stripe_charge", "capture"],
|
|
208
|
+
"description": "Stripe: Initiating customer charges",
|
|
209
|
+
},
|
|
210
|
+
"stripe_refund": {
|
|
211
|
+
"delta": 0.4,
|
|
212
|
+
"risk_keywords": ["refund", "create_refund", "reverse_charge", "stripe_refund"],
|
|
213
|
+
"description": "Stripe: Reversing completed transactions",
|
|
214
|
+
},
|
|
215
|
+
"stripe_payout": {
|
|
216
|
+
"delta": 0.2,
|
|
217
|
+
"risk_keywords": ["payout", "create_payout", "transfer_to_bank", "stripe_payout"],
|
|
218
|
+
"description": "Stripe: Moving funds to external bank accounts",
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
elif method == "plaid":
|
|
222
|
+
return {
|
|
223
|
+
"plaid_auth": {
|
|
224
|
+
"delta": 0.5,
|
|
225
|
+
"risk_keywords": ["auth", "balance", "plaid_auth", "accounts_get"],
|
|
226
|
+
"description": "Plaid: Account authentication and balance retrieval",
|
|
227
|
+
},
|
|
228
|
+
"plaid_transfer": {
|
|
229
|
+
"delta": 0.25,
|
|
230
|
+
"risk_keywords": ["transfer", "transfer_create", "plaid_transfer", "payment_initiation"],
|
|
231
|
+
"description": "Plaid: Initiating bank transfers",
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
elif method == "square":
|
|
235
|
+
return {
|
|
236
|
+
"square_payment": {
|
|
237
|
+
"delta": 0.3,
|
|
238
|
+
"risk_keywords": ["payment", "create_payment", "square_payment", "charge"],
|
|
239
|
+
"description": "Square: Processing customer payments",
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
else:
|
|
243
|
+
return {
|
|
244
|
+
f"{method}_payment": {
|
|
245
|
+
"delta": 0.3,
|
|
246
|
+
"risk_keywords": ["payment", "charge", "transfer"],
|
|
247
|
+
"description": f"Custom: {method} payment processing",
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
def _get_compliance_map(self, frameworks: list[str]) -> dict[str, Any]:
|
|
252
|
+
"""Get compliance control mappings."""
|
|
253
|
+
mapping: dict[str, Any] = {}
|
|
254
|
+
for fw in frameworks:
|
|
255
|
+
fw_upper = fw.upper()
|
|
256
|
+
if fw_upper == "PCI-DSS":
|
|
257
|
+
mapping[fw] = {
|
|
258
|
+
"3.4": "Render PAN unreadable — Hash module ensures payload integrity",
|
|
259
|
+
"10.2": "Audit trails — AuditLogger records all gate decisions",
|
|
260
|
+
"6.5": "Address common coding vulnerabilities — Gate prevents unauthorized tool execution",
|
|
261
|
+
}
|
|
262
|
+
elif fw_upper == "SOX":
|
|
263
|
+
mapping[fw] = {
|
|
264
|
+
"302": "Corporate responsibility — AgentStateStore tracks all decisions",
|
|
265
|
+
"404": "Internal controls — AuditLogger provides append-only decision log",
|
|
266
|
+
}
|
|
267
|
+
elif fw_upper == "HIPAA":
|
|
268
|
+
mapping[fw] = {
|
|
269
|
+
"164.312(a)": "Access control — Gate enforces role-based tool access",
|
|
270
|
+
"164.312(b)": "Audit controls — AuditLogger records all PHI access attempts",
|
|
271
|
+
}
|
|
272
|
+
else:
|
|
273
|
+
mapping[fw] = {"general": f"{fw} compliance controls mapped to gate decisions"}
|
|
274
|
+
return mapping
|
|
275
|
+
|
|
276
|
+
def _build_custom(self) -> dict[str, Any]:
|
|
277
|
+
"""Build a fully custom taxonomy via prompts."""
|
|
278
|
+
print("\n--- Custom Taxonomy Builder ---")
|
|
279
|
+
cats: dict[str, Any] = {}
|
|
280
|
+
while True:
|
|
281
|
+
name = input("Category name (or empty to finish): ").strip()
|
|
282
|
+
if not name:
|
|
283
|
+
break
|
|
284
|
+
delta_str = input(f" Delta for '{name}' (0.0-1.0, lower=more risky): ").strip()
|
|
285
|
+
delta = float(delta_str) if delta_str else 0.5
|
|
286
|
+
keywords = input(" Risk keywords (comma-separated): ").strip()
|
|
287
|
+
kw_list = [k.strip() for k in keywords.split(",") if k.strip()]
|
|
288
|
+
desc = input(" Description: ").strip()
|
|
289
|
+
cats[name] = {
|
|
290
|
+
"delta": delta,
|
|
291
|
+
"risk_keywords": kw_list,
|
|
292
|
+
"description": desc or f"Custom category: {name}",
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
"version": "2.0-custom",
|
|
296
|
+
"domain": "custom",
|
|
297
|
+
"generated_by": "capfence_taxonomy_builder",
|
|
298
|
+
"description": "User-defined custom taxonomy",
|
|
299
|
+
"categories": cats,
|
|
300
|
+
"compliance_mapping": {},
|
|
301
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""EU AI Act Annex IV evidence pack generator.
|
|
2
|
+
|
|
3
|
+
Produces a structured evidence package for high-risk AI system
|
|
4
|
+
conformity assessment under EU AI Act Article 12 and Annex IV.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from capfence.assessment.eu_ai_act import generate_evidence_pack
|
|
8
|
+
from capfence.assessment.scanner import scan_assessment
|
|
9
|
+
|
|
10
|
+
data = scan_assessment(Path("./src"), taxonomy_path="financial")
|
|
11
|
+
pack = generate_evidence_pack(data)
|
|
12
|
+
pack.write_html(Path("annex_iv_evidence.html"))
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from capfence.assessment.scanner import AssessmentData
|
|
23
|
+
from capfence.assessment.owasp import get_coverage_summary
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class EvidencePack:
|
|
28
|
+
"""Structured EU AI Act Annex IV evidence package."""
|
|
29
|
+
|
|
30
|
+
system_name: str
|
|
31
|
+
system_version: str
|
|
32
|
+
generated_at: str
|
|
33
|
+
risk_management: dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
data_governance: dict[str, Any] = field(default_factory=dict)
|
|
35
|
+
technical_documentation: dict[str, Any] = field(default_factory=dict)
|
|
36
|
+
record_keeping: dict[str, Any] = field(default_factory=dict)
|
|
37
|
+
transparency: dict[str, Any] = field(default_factory=dict)
|
|
38
|
+
human_oversight: dict[str, Any] = field(default_factory=dict)
|
|
39
|
+
accuracy_robustness: dict[str, Any] = field(default_factory=dict)
|
|
40
|
+
cybersecurity: dict[str, Any] = field(default_factory=dict)
|
|
41
|
+
owasp_coverage: dict[str, Any] = field(default_factory=dict)
|
|
42
|
+
|
|
43
|
+
def to_dict(self) -> dict[str, Any]:
|
|
44
|
+
return {
|
|
45
|
+
"system_name": self.system_name,
|
|
46
|
+
"system_version": self.system_version,
|
|
47
|
+
"generated_at": self.generated_at,
|
|
48
|
+
"annex_iv_sections": {
|
|
49
|
+
"1_risk_management": self.risk_management,
|
|
50
|
+
"2_data_governance": self.data_governance,
|
|
51
|
+
"3_technical_documentation": self.technical_documentation,
|
|
52
|
+
"4_record_keeping": self.record_keeping,
|
|
53
|
+
"5_transparency": self.transparency,
|
|
54
|
+
"6_human_oversight": self.human_oversight,
|
|
55
|
+
"7_accuracy_robustness": self.accuracy_robustness,
|
|
56
|
+
"8_cybersecurity": self.cybersecurity,
|
|
57
|
+
},
|
|
58
|
+
"owasp_agentic_coverage": self.owasp_coverage,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
def _validate_output_path(self, path: Path) -> Path:
|
|
62
|
+
"""Resolve and validate output path to prevent directory traversal."""
|
|
63
|
+
resolved = path.resolve()
|
|
64
|
+
# Reject paths containing parent-directory references
|
|
65
|
+
if ".." in path.parts:
|
|
66
|
+
raise ValueError(f"Output path cannot contain '..' components: {path}")
|
|
67
|
+
return resolved
|
|
68
|
+
|
|
69
|
+
def write_json(self, path: Path) -> None:
|
|
70
|
+
safe_path = self._validate_output_path(path)
|
|
71
|
+
safe_path.write_text(json.dumps(self.to_dict(), indent=2, default=str), encoding="utf-8")
|
|
72
|
+
|
|
73
|
+
def write_html(self, path: Path) -> None:
|
|
74
|
+
safe_path = self._validate_output_path(path)
|
|
75
|
+
from capfence.assessment.reporter import _get_template_dir, _risk_color, _risk_bg
|
|
76
|
+
import jinja2
|
|
77
|
+
tpl_dir = _get_template_dir()
|
|
78
|
+
env = jinja2.Environment(
|
|
79
|
+
loader=jinja2.FileSystemLoader(str(tpl_dir)),
|
|
80
|
+
autoescape=jinja2.select_autoescape(["html", "xml"]),
|
|
81
|
+
)
|
|
82
|
+
env.filters["risk_color"] = _risk_color
|
|
83
|
+
env.filters["risk_bg"] = _risk_bg
|
|
84
|
+
try:
|
|
85
|
+
template = env.get_template("report_eu_ai_act.html")
|
|
86
|
+
except jinja2.TemplateNotFound:
|
|
87
|
+
template = env.from_string(_FALLBACK_EU_TEMPLATE)
|
|
88
|
+
html = template.render(**self.to_dict())
|
|
89
|
+
safe_path.write_text(html, encoding="utf-8")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def generate_evidence_pack(
|
|
93
|
+
data: AssessmentData,
|
|
94
|
+
system_name: str = "CapFence-Governed Agent",
|
|
95
|
+
system_version: str = "0.4.0",
|
|
96
|
+
) -> EvidencePack:
|
|
97
|
+
"""Generate an Annex IV evidence pack from assessment data."""
|
|
98
|
+
import datetime
|
|
99
|
+
now = datetime.datetime.now(datetime.timezone.utc).isoformat()
|
|
100
|
+
|
|
101
|
+
owasp = get_coverage_summary()
|
|
102
|
+
|
|
103
|
+
pack = EvidencePack(
|
|
104
|
+
system_name=system_name,
|
|
105
|
+
system_version=system_version,
|
|
106
|
+
generated_at=now,
|
|
107
|
+
risk_management={
|
|
108
|
+
"description": "Risk identification and analysis for AI agent tool calls.",
|
|
109
|
+
"tool_count": data.total_tools,
|
|
110
|
+
"ungated_tools": data.ungated_count,
|
|
111
|
+
"critical_ungated": data.critical_ungated,
|
|
112
|
+
"risk_score": data.risk_score,
|
|
113
|
+
"risk_label": data.risk_label,
|
|
114
|
+
"mitigation": "CapFence Gate provides deterministic fail-closed enforcement with taxonomy-based thresholds.",
|
|
115
|
+
"residual_risk": "Low — all high-risk tool calls are intercepted and scored before execution.",
|
|
116
|
+
},
|
|
117
|
+
data_governance={
|
|
118
|
+
"description": "Training data governance is outside CapFence scope. This section covers operational data.",
|
|
119
|
+
"audit_log_integrity": "Hash-chained SQLite audit log with SHA-256 linkage. Tamper-evident.",
|
|
120
|
+
"payload_handling": "Payloads are hashed (SHA-256) before storage. Raw payloads never stored in audit log.",
|
|
121
|
+
"retention_policy": "Audit logs retained per customer policy. SQLite format supports long-term archival.",
|
|
122
|
+
},
|
|
123
|
+
technical_documentation={
|
|
124
|
+
"description": "Technical documentation of the AI system and its governance layer.",
|
|
125
|
+
"architecture": "CapFence sits between the agent and its tools. Gate evaluates every call before execution.",
|
|
126
|
+
"components": [
|
|
127
|
+
"Gate (rule-based evaluator)",
|
|
128
|
+
"TaxonomyLoader (risk category configuration)",
|
|
129
|
+
"Scorer (pluggable risk scoring)",
|
|
130
|
+
"AuditLogger (append-only hash-chained log)",
|
|
131
|
+
"AgentStateStore (behavioral K/V tracking)",
|
|
132
|
+
"FailClosedFSM (deterministic state machine)",
|
|
133
|
+
],
|
|
134
|
+
"version": system_version,
|
|
135
|
+
"source": "https://github.com/capfencelabs/capfence-python",
|
|
136
|
+
},
|
|
137
|
+
record_keeping={
|
|
138
|
+
"description": "Automatic recording of events for traceability.",
|
|
139
|
+
"audit_log_format": "SQLite with hash-chained entries.",
|
|
140
|
+
"recorded_fields": [
|
|
141
|
+
"timestamp", "agent_id", "task_context", "risk_category",
|
|
142
|
+
"decision", "risk_score", "threshold", "payload_hash", "reason", "latency_ms",
|
|
143
|
+
"prev_hash", "entry_hash", "signature",
|
|
144
|
+
],
|
|
145
|
+
"verification_command": "capfence verify --audit-log ./audit.db",
|
|
146
|
+
"retention": "Customer-defined. SQLite supports indefinite retention.",
|
|
147
|
+
},
|
|
148
|
+
transparency={
|
|
149
|
+
"description": "Transparency information for deployers and end-users.",
|
|
150
|
+
"system_purpose": "Runtime governance layer for AI agent tool calls in regulated workloads.",
|
|
151
|
+
"capabilities": "Deterministic fail-closed enforcement, tamper-evident audit logging, behavioral scoring.",
|
|
152
|
+
"limitations": "Does not inspect LLM prompts. Does not validate tool outputs. Focused on tool-call interception.",
|
|
153
|
+
"known_risks": "Prompt injection may bypass tool-call gating if the agent is manipulated to not call tools.",
|
|
154
|
+
},
|
|
155
|
+
human_oversight={
|
|
156
|
+
"description": "Human oversight measures.",
|
|
157
|
+
"measures": [
|
|
158
|
+
"Gate decisions are logged and auditable.",
|
|
159
|
+
"CI/CD integration (--fail-on-ungated) prevents deployment of ungated high-risk tools.",
|
|
160
|
+
"Assessment reports provide human-readable risk breakdowns with remediation steps.",
|
|
161
|
+
],
|
|
162
|
+
"intervention": "Administrators can adjust taxonomy thresholds, add risk keywords, or disable specific tools via taxonomy configuration.",
|
|
163
|
+
},
|
|
164
|
+
accuracy_robustness={
|
|
165
|
+
"description": "Accuracy, robustness, and cybersecurity.",
|
|
166
|
+
"accuracy": "Keyword and regex-based scoring is deterministic. Same payload always produces same score.",
|
|
167
|
+
"robustness": "Fail-closed design: any error or anomaly results in a block, not a pass.",
|
|
168
|
+
"cybersecurity": "OWASP Agentic Top 10 coverage matrix included. See separate cybersecurity section.",
|
|
169
|
+
},
|
|
170
|
+
cybersecurity={
|
|
171
|
+
"description": "Cybersecurity measures.",
|
|
172
|
+
"owasp_agentic_coverage": f"{owasp['coverage_percent']}% of OWASP Agentic AI Top 10 risks covered.",
|
|
173
|
+
"covered_risks": owasp["covered"],
|
|
174
|
+
"full_coverage": owasp["full"],
|
|
175
|
+
"partial_coverage": owasp["partial"],
|
|
176
|
+
"mitigations": [
|
|
177
|
+
"Excessive Agency: taxonomy-based gate blocks unauthorized tool categories.",
|
|
178
|
+
"Insecure Plugin Design: CapFenceTool wrapper enforces access controls and input scoring.",
|
|
179
|
+
"Agent Escape: command execution taxonomy with strict thresholds blocks shell escapes.",
|
|
180
|
+
"Denial of Service: velocity tracking (V metric) detects tool-call spikes.",
|
|
181
|
+
"Sensitive Information Disclosure: payload hashing prevents raw data exposure in logs.",
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
owasp_coverage=owasp,
|
|
185
|
+
)
|
|
186
|
+
return pack
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
_FALLBACK_EU_TEMPLATE = """<!DOCTYPE html>
|
|
190
|
+
<html lang="en">
|
|
191
|
+
<head>
|
|
192
|
+
<meta charset="UTF-8">
|
|
193
|
+
<title>EU AI Act Annex IV Evidence Pack — {{ system_name }}</title>
|
|
194
|
+
<style>
|
|
195
|
+
body { font-family: system-ui, sans-serif; margin: 40px; background: #f8f9fa; color: #1f2937; }
|
|
196
|
+
h1 { font-size: 1.8rem; }
|
|
197
|
+
h2 { font-size: 1.3rem; margin-top: 2rem; border-bottom: 2px solid #e5e7eb; padding-bottom: 0.5rem; }
|
|
198
|
+
.meta { color: #6b7280; margin-bottom: 2rem; }
|
|
199
|
+
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; }
|
|
200
|
+
th, td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid #e5e7eb; }
|
|
201
|
+
th { background: #f3f4f6; font-weight: 600; }
|
|
202
|
+
.badge { display: inline-block; padding: 0.25rem 0.5rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; }
|
|
203
|
+
</style>
|
|
204
|
+
</head>
|
|
205
|
+
<body>
|
|
206
|
+
<h1>EU AI Act Annex IV Evidence Pack</h1>
|
|
207
|
+
<p class="meta">{{ system_name }} v{{ system_version }} · Generated {{ generated_at }}</p>
|
|
208
|
+
|
|
209
|
+
{% for section_name, section in annex_iv_sections.items() %}
|
|
210
|
+
<h2>{{ section_name.replace('_', ' ').title() }}</h2>
|
|
211
|
+
<table>
|
|
212
|
+
{% for key, value in section.items() %}
|
|
213
|
+
<tr>
|
|
214
|
+
<th>{{ key.replace('_', ' ').title() }}</th>
|
|
215
|
+
<td>
|
|
216
|
+
{% if value is sequence and value is not string %}
|
|
217
|
+
<ul>
|
|
218
|
+
{% for item in value %}
|
|
219
|
+
<li>{{ item }}</li>
|
|
220
|
+
{% endfor %}
|
|
221
|
+
</ul>
|
|
222
|
+
{% else %}
|
|
223
|
+
{{ value }}
|
|
224
|
+
{% endif %}
|
|
225
|
+
</td>
|
|
226
|
+
</tr>
|
|
227
|
+
{% endfor %}
|
|
228
|
+
</table>
|
|
229
|
+
{% endfor %}
|
|
230
|
+
|
|
231
|
+
<h2>OWASP Agentic Coverage</h2>
|
|
232
|
+
<table>
|
|
233
|
+
<tr><th>Metric</th><th>Value</th></tr>
|
|
234
|
+
<tr><td>Coverage Percent</td><td>{{ owasp_agentic_coverage.coverage_percent }}%</td></tr>
|
|
235
|
+
<tr><td>Full Coverage</td><td>{{ owasp_agentic_coverage.full }}</td></tr>
|
|
236
|
+
<tr><td>Partial Coverage</td><td>{{ owasp_agentic_coverage.partial }}</td></tr>
|
|
237
|
+
<tr><td>Planned</td><td>{{ owasp_agentic_coverage.planned }}</td></tr>
|
|
238
|
+
</table>
|
|
239
|
+
</body>
|
|
240
|
+
</html>
|
|
241
|
+
"""
|