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.
Files changed (63) hide show
  1. capfence/__init__.py +109 -0
  2. capfence/assessment/__init__.py +17 -0
  3. capfence/assessment/builder.py +301 -0
  4. capfence/assessment/eu_ai_act.py +241 -0
  5. capfence/assessment/owasp.py +153 -0
  6. capfence/assessment/reporter.py +114 -0
  7. capfence/assessment/scanner.py +337 -0
  8. capfence/assessment/simulator.py +245 -0
  9. capfence/assessment/templates/report.html +637 -0
  10. capfence/assessment/templates/report_compliance.html +682 -0
  11. capfence/assessment/templates/report_eu_ai_act.html +139 -0
  12. capfence/assessment/templates/report_owasp.html +92 -0
  13. capfence/check.py +401 -0
  14. capfence/cli.py +655 -0
  15. capfence/cloud/__init__.py +10 -0
  16. capfence/cloud/client.py +149 -0
  17. capfence/cloud/evaluator.py +85 -0
  18. capfence/core/approvals.py +224 -0
  19. capfence/core/audit.py +249 -0
  20. capfence/core/capabilities.py +83 -0
  21. capfence/core/chain.py +140 -0
  22. capfence/core/fsm.py +55 -0
  23. capfence/core/gate.py +397 -0
  24. capfence/core/hash.py +26 -0
  25. capfence/core/keys.py +208 -0
  26. capfence/core/plugins.py +55 -0
  27. capfence/core/policy.py +369 -0
  28. capfence/core/scorer.py +231 -0
  29. capfence/core/state.py +203 -0
  30. capfence/core/taxonomy.py +194 -0
  31. capfence/errors.py +69 -0
  32. capfence/flow/__init__.py +9 -0
  33. capfence/flow/tracer.py +317 -0
  34. capfence/framework/_base.py +62 -0
  35. capfence/framework/_risk.py +23 -0
  36. capfence/framework/autogen.py +53 -0
  37. capfence/framework/autogpt.py +21 -0
  38. capfence/framework/babyagi.py +21 -0
  39. capfence/framework/crewai.py +78 -0
  40. capfence/framework/langchain.py +118 -0
  41. capfence/framework/langgraph.py +140 -0
  42. capfence/framework/llamaindex.py +57 -0
  43. capfence/framework/openai_agents.py +94 -0
  44. capfence/framework/pydanticai.py +59 -0
  45. capfence/framework/swarm.py +21 -0
  46. capfence/mcp/__init__.py +10 -0
  47. capfence/mcp/adapter.py +79 -0
  48. capfence/mcp/gateway.py +263 -0
  49. capfence/py.typed +0 -0
  50. capfence/taxonomies/financial.json +396 -0
  51. capfence/taxonomies/financial_crypto.json +97 -0
  52. capfence/taxonomies/financial_plaid.json +123 -0
  53. capfence/taxonomies/general.json +112 -0
  54. capfence/taxonomies/healthcare.json +92 -0
  55. capfence/taxonomies/legal.json +47 -0
  56. capfence/telemetry/__init__.py +10 -0
  57. capfence/telemetry/client.py +178 -0
  58. capfence/types.py +19 -0
  59. capfence-0.6.2.dist-info/METADATA +267 -0
  60. capfence-0.6.2.dist-info/RECORD +63 -0
  61. capfence-0.6.2.dist-info/WHEEL +4 -0
  62. capfence-0.6.2.dist-info/entry_points.txt +2 -0
  63. 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 }} &middot; 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
+ """