proxilion 0.0.1__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.
- proxilion/__init__.py +136 -0
- proxilion/audit/__init__.py +133 -0
- proxilion/audit/base_exporters.py +527 -0
- proxilion/audit/compliance/__init__.py +130 -0
- proxilion/audit/compliance/base.py +457 -0
- proxilion/audit/compliance/eu_ai_act.py +603 -0
- proxilion/audit/compliance/iso27001.py +544 -0
- proxilion/audit/compliance/soc2.py +491 -0
- proxilion/audit/events.py +493 -0
- proxilion/audit/explainability.py +1173 -0
- proxilion/audit/exporters/__init__.py +58 -0
- proxilion/audit/exporters/aws_s3.py +636 -0
- proxilion/audit/exporters/azure_storage.py +608 -0
- proxilion/audit/exporters/cloud_base.py +468 -0
- proxilion/audit/exporters/gcp_storage.py +570 -0
- proxilion/audit/exporters/multi_exporter.py +498 -0
- proxilion/audit/hash_chain.py +652 -0
- proxilion/audit/logger.py +543 -0
- proxilion/caching/__init__.py +49 -0
- proxilion/caching/tool_cache.py +633 -0
- proxilion/context/__init__.py +73 -0
- proxilion/context/context_window.py +556 -0
- proxilion/context/message_history.py +505 -0
- proxilion/context/session.py +735 -0
- proxilion/contrib/__init__.py +51 -0
- proxilion/contrib/anthropic.py +609 -0
- proxilion/contrib/google.py +1012 -0
- proxilion/contrib/langchain.py +641 -0
- proxilion/contrib/mcp.py +893 -0
- proxilion/contrib/openai.py +646 -0
- proxilion/core.py +3058 -0
- proxilion/decorators.py +966 -0
- proxilion/engines/__init__.py +287 -0
- proxilion/engines/base.py +266 -0
- proxilion/engines/casbin_engine.py +412 -0
- proxilion/engines/opa_engine.py +493 -0
- proxilion/engines/simple.py +437 -0
- proxilion/exceptions.py +887 -0
- proxilion/guards/__init__.py +54 -0
- proxilion/guards/input_guard.py +522 -0
- proxilion/guards/output_guard.py +634 -0
- proxilion/observability/__init__.py +198 -0
- proxilion/observability/cost_tracker.py +866 -0
- proxilion/observability/hooks.py +683 -0
- proxilion/observability/metrics.py +798 -0
- proxilion/observability/session_cost_tracker.py +1063 -0
- proxilion/policies/__init__.py +67 -0
- proxilion/policies/base.py +304 -0
- proxilion/policies/builtin.py +486 -0
- proxilion/policies/registry.py +376 -0
- proxilion/providers/__init__.py +201 -0
- proxilion/providers/adapter.py +468 -0
- proxilion/providers/anthropic_adapter.py +330 -0
- proxilion/providers/gemini_adapter.py +391 -0
- proxilion/providers/openai_adapter.py +294 -0
- proxilion/py.typed +0 -0
- proxilion/resilience/__init__.py +81 -0
- proxilion/resilience/degradation.py +615 -0
- proxilion/resilience/fallback.py +555 -0
- proxilion/resilience/retry.py +554 -0
- proxilion/scheduling/__init__.py +57 -0
- proxilion/scheduling/priority_queue.py +419 -0
- proxilion/scheduling/scheduler.py +459 -0
- proxilion/security/__init__.py +244 -0
- proxilion/security/agent_trust.py +968 -0
- proxilion/security/behavioral_drift.py +794 -0
- proxilion/security/cascade_protection.py +869 -0
- proxilion/security/circuit_breaker.py +428 -0
- proxilion/security/cost_limiter.py +690 -0
- proxilion/security/idor_protection.py +460 -0
- proxilion/security/intent_capsule.py +849 -0
- proxilion/security/intent_validator.py +495 -0
- proxilion/security/memory_integrity.py +767 -0
- proxilion/security/rate_limiter.py +509 -0
- proxilion/security/scope_enforcer.py +680 -0
- proxilion/security/sequence_validator.py +636 -0
- proxilion/security/trust_boundaries.py +784 -0
- proxilion/streaming/__init__.py +70 -0
- proxilion/streaming/detector.py +761 -0
- proxilion/streaming/transformer.py +674 -0
- proxilion/timeouts/__init__.py +55 -0
- proxilion/timeouts/decorators.py +477 -0
- proxilion/timeouts/manager.py +545 -0
- proxilion/tools/__init__.py +69 -0
- proxilion/tools/decorators.py +493 -0
- proxilion/tools/registry.py +732 -0
- proxilion/types.py +339 -0
- proxilion/validation/__init__.py +93 -0
- proxilion/validation/pydantic_schema.py +351 -0
- proxilion/validation/schema.py +651 -0
- proxilion-0.0.1.dist-info/METADATA +872 -0
- proxilion-0.0.1.dist-info/RECORD +94 -0
- proxilion-0.0.1.dist-info/WHEEL +4 -0
- proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for compliance audit exporters.
|
|
3
|
+
|
|
4
|
+
Provides common infrastructure for exporting audit logs in
|
|
5
|
+
compliance-ready formats for various regulatory frameworks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Any, Protocol, runtime_checkable
|
|
16
|
+
|
|
17
|
+
from proxilion.audit.events import AuditEventV2, EventType
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ComplianceFramework(Enum):
|
|
21
|
+
"""Supported compliance frameworks."""
|
|
22
|
+
EU_AI_ACT = "eu_ai_act"
|
|
23
|
+
SOC2 = "soc2"
|
|
24
|
+
ISO27001 = "iso27001"
|
|
25
|
+
NIST_AI_RMF = "nist_ai_rmf"
|
|
26
|
+
HIPAA = "hipaa"
|
|
27
|
+
GDPR = "gdpr"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ComplianceMetadata:
|
|
32
|
+
"""
|
|
33
|
+
Metadata for compliance reports.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
framework: The compliance framework this report targets.
|
|
37
|
+
version: Version of the framework (e.g., "2024").
|
|
38
|
+
organization: Name of the organization.
|
|
39
|
+
system_name: Name of the AI system being audited.
|
|
40
|
+
responsible_party: Contact person/team.
|
|
41
|
+
export_timestamp: When the report was generated.
|
|
42
|
+
period_start: Start of the audit period.
|
|
43
|
+
period_end: End of the audit period.
|
|
44
|
+
additional_info: Any additional metadata.
|
|
45
|
+
"""
|
|
46
|
+
framework: ComplianceFramework
|
|
47
|
+
version: str
|
|
48
|
+
organization: str
|
|
49
|
+
system_name: str
|
|
50
|
+
responsible_party: str
|
|
51
|
+
export_timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
52
|
+
period_start: datetime | None = None
|
|
53
|
+
period_end: datetime | None = None
|
|
54
|
+
additional_info: dict[str, Any] = field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
def to_dict(self) -> dict[str, Any]:
|
|
57
|
+
"""Convert to dictionary."""
|
|
58
|
+
return {
|
|
59
|
+
"framework": self.framework.value,
|
|
60
|
+
"version": self.version,
|
|
61
|
+
"organization": self.organization,
|
|
62
|
+
"system_name": self.system_name,
|
|
63
|
+
"responsible_party": self.responsible_party,
|
|
64
|
+
"export_timestamp": self.export_timestamp.isoformat(),
|
|
65
|
+
"period_start": self.period_start.isoformat() if self.period_start else None,
|
|
66
|
+
"period_end": self.period_end.isoformat() if self.period_end else None,
|
|
67
|
+
"additional_info": self.additional_info,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class ComplianceEvidence:
|
|
73
|
+
"""
|
|
74
|
+
A piece of evidence for compliance reporting.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
control_id: The control/article this evidence supports.
|
|
78
|
+
control_name: Human-readable control name.
|
|
79
|
+
evidence_type: Type of evidence (e.g., "log", "configuration").
|
|
80
|
+
description: Description of what this evidence shows.
|
|
81
|
+
events: Relevant audit events.
|
|
82
|
+
summary: Summary statistics.
|
|
83
|
+
compliant: Whether this control appears compliant.
|
|
84
|
+
notes: Additional notes or observations.
|
|
85
|
+
"""
|
|
86
|
+
control_id: str
|
|
87
|
+
control_name: str
|
|
88
|
+
evidence_type: str
|
|
89
|
+
description: str
|
|
90
|
+
events: list[dict[str, Any]] = field(default_factory=list)
|
|
91
|
+
summary: dict[str, Any] = field(default_factory=dict)
|
|
92
|
+
compliant: bool | None = None
|
|
93
|
+
notes: str | None = None
|
|
94
|
+
|
|
95
|
+
def to_dict(self) -> dict[str, Any]:
|
|
96
|
+
"""Convert to dictionary."""
|
|
97
|
+
return {
|
|
98
|
+
"control_id": self.control_id,
|
|
99
|
+
"control_name": self.control_name,
|
|
100
|
+
"evidence_type": self.evidence_type,
|
|
101
|
+
"description": self.description,
|
|
102
|
+
"event_count": len(self.events),
|
|
103
|
+
"events": self.events,
|
|
104
|
+
"summary": self.summary,
|
|
105
|
+
"compliant": self.compliant,
|
|
106
|
+
"notes": self.notes,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class ComplianceReport:
|
|
112
|
+
"""
|
|
113
|
+
A complete compliance report.
|
|
114
|
+
|
|
115
|
+
Attributes:
|
|
116
|
+
metadata: Report metadata.
|
|
117
|
+
evidence: List of evidence items.
|
|
118
|
+
summary: Overall summary.
|
|
119
|
+
recommendations: Suggested improvements.
|
|
120
|
+
"""
|
|
121
|
+
metadata: ComplianceMetadata
|
|
122
|
+
evidence: list[ComplianceEvidence] = field(default_factory=list)
|
|
123
|
+
summary: dict[str, Any] = field(default_factory=dict)
|
|
124
|
+
recommendations: list[str] = field(default_factory=list)
|
|
125
|
+
|
|
126
|
+
def to_dict(self) -> dict[str, Any]:
|
|
127
|
+
"""Convert to dictionary."""
|
|
128
|
+
return {
|
|
129
|
+
"metadata": self.metadata.to_dict(),
|
|
130
|
+
"evidence": [e.to_dict() for e in self.evidence],
|
|
131
|
+
"summary": self.summary,
|
|
132
|
+
"recommendations": self.recommendations,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
def to_json(self, pretty: bool = True) -> str:
|
|
136
|
+
"""Convert to JSON string."""
|
|
137
|
+
if pretty:
|
|
138
|
+
return json.dumps(self.to_dict(), indent=2, default=str)
|
|
139
|
+
return json.dumps(self.to_dict(), default=str)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@runtime_checkable
|
|
143
|
+
class EventSource(Protocol):
|
|
144
|
+
"""Protocol for audit event sources."""
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def events(self) -> list[AuditEventV2]:
|
|
148
|
+
"""Get all events."""
|
|
149
|
+
...
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class BaseComplianceExporter(ABC):
|
|
153
|
+
"""
|
|
154
|
+
Base class for compliance exporters.
|
|
155
|
+
|
|
156
|
+
Provides common infrastructure for filtering events,
|
|
157
|
+
generating reports, and formatting output.
|
|
158
|
+
|
|
159
|
+
Subclasses should implement framework-specific export methods.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
event_source: EventSource | list[AuditEventV2],
|
|
165
|
+
organization: str = "",
|
|
166
|
+
system_name: str = "",
|
|
167
|
+
responsible_party: str = "",
|
|
168
|
+
) -> None:
|
|
169
|
+
"""
|
|
170
|
+
Initialize the exporter.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
event_source: Source of audit events (logger or list).
|
|
174
|
+
organization: Organization name for reports.
|
|
175
|
+
system_name: AI system name for reports.
|
|
176
|
+
responsible_party: Responsible party for reports.
|
|
177
|
+
"""
|
|
178
|
+
self._event_source = event_source
|
|
179
|
+
self._organization = organization
|
|
180
|
+
self._system_name = system_name
|
|
181
|
+
self._responsible_party = responsible_party
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
@abstractmethod
|
|
185
|
+
def framework(self) -> ComplianceFramework:
|
|
186
|
+
"""Get the compliance framework this exporter targets."""
|
|
187
|
+
...
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
@abstractmethod
|
|
191
|
+
def framework_version(self) -> str:
|
|
192
|
+
"""Get the version of the compliance framework."""
|
|
193
|
+
...
|
|
194
|
+
|
|
195
|
+
def get_events(self) -> list[AuditEventV2]:
|
|
196
|
+
"""Get all events from the source."""
|
|
197
|
+
if isinstance(self._event_source, list):
|
|
198
|
+
return self._event_source
|
|
199
|
+
return self._event_source.events
|
|
200
|
+
|
|
201
|
+
def filter_events(
|
|
202
|
+
self,
|
|
203
|
+
start: datetime | None = None,
|
|
204
|
+
end: datetime | None = None,
|
|
205
|
+
event_types: list[EventType] | None = None,
|
|
206
|
+
user_id: str | None = None,
|
|
207
|
+
tool_name: str | None = None,
|
|
208
|
+
) -> list[AuditEventV2]:
|
|
209
|
+
"""
|
|
210
|
+
Filter events by various criteria.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
start: Filter events after this timestamp.
|
|
214
|
+
end: Filter events before this timestamp.
|
|
215
|
+
event_types: Filter by event types.
|
|
216
|
+
user_id: Filter by user ID.
|
|
217
|
+
tool_name: Filter by tool name.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Filtered list of events.
|
|
221
|
+
"""
|
|
222
|
+
events = self.get_events()
|
|
223
|
+
filtered = []
|
|
224
|
+
|
|
225
|
+
for event in events:
|
|
226
|
+
# Time range filter
|
|
227
|
+
if start and event.timestamp < start:
|
|
228
|
+
continue
|
|
229
|
+
if end and event.timestamp > end:
|
|
230
|
+
continue
|
|
231
|
+
|
|
232
|
+
# Event type filter
|
|
233
|
+
if event_types and event.data.event_type not in event_types:
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
# User filter
|
|
237
|
+
if user_id and event.data.user_id != user_id:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
# Tool filter
|
|
241
|
+
if tool_name and event.data.tool_name != tool_name:
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
filtered.append(event)
|
|
245
|
+
|
|
246
|
+
return filtered
|
|
247
|
+
|
|
248
|
+
def filter_by_date_range(
|
|
249
|
+
self,
|
|
250
|
+
start: datetime,
|
|
251
|
+
end: datetime,
|
|
252
|
+
) -> list[AuditEventV2]:
|
|
253
|
+
"""Filter events by date range."""
|
|
254
|
+
return self.filter_events(start=start, end=end)
|
|
255
|
+
|
|
256
|
+
def get_authorization_events(
|
|
257
|
+
self,
|
|
258
|
+
start: datetime | None = None,
|
|
259
|
+
end: datetime | None = None,
|
|
260
|
+
) -> list[AuditEventV2]:
|
|
261
|
+
"""Get authorization-related events."""
|
|
262
|
+
return self.filter_events(
|
|
263
|
+
start=start,
|
|
264
|
+
end=end,
|
|
265
|
+
event_types=[
|
|
266
|
+
EventType.AUTHORIZATION_GRANTED,
|
|
267
|
+
EventType.AUTHORIZATION_DENIED,
|
|
268
|
+
EventType.AUTHORIZATION_REQUEST,
|
|
269
|
+
],
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def get_denied_events(
|
|
273
|
+
self,
|
|
274
|
+
start: datetime | None = None,
|
|
275
|
+
end: datetime | None = None,
|
|
276
|
+
) -> list[AuditEventV2]:
|
|
277
|
+
"""Get denied authorization events."""
|
|
278
|
+
return self.filter_events(
|
|
279
|
+
start=start,
|
|
280
|
+
end=end,
|
|
281
|
+
event_types=[EventType.AUTHORIZATION_DENIED],
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def get_security_events(
|
|
285
|
+
self,
|
|
286
|
+
start: datetime | None = None,
|
|
287
|
+
end: datetime | None = None,
|
|
288
|
+
) -> list[AuditEventV2]:
|
|
289
|
+
"""Get security-related events."""
|
|
290
|
+
return self.filter_events(
|
|
291
|
+
start=start,
|
|
292
|
+
end=end,
|
|
293
|
+
event_types=[
|
|
294
|
+
EventType.RATE_LIMIT_EXCEEDED,
|
|
295
|
+
EventType.CIRCUIT_BREAKER_OPEN,
|
|
296
|
+
EventType.IDOR_VIOLATION,
|
|
297
|
+
EventType.SCHEMA_VALIDATION_FAILURE,
|
|
298
|
+
],
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def event_to_evidence_dict(self, event: AuditEventV2) -> dict[str, Any]:
|
|
302
|
+
"""Convert an event to an evidence dictionary."""
|
|
303
|
+
return {
|
|
304
|
+
"event_id": event.event_id,
|
|
305
|
+
"timestamp": event.timestamp.isoformat(),
|
|
306
|
+
"event_type": event.data.event_type.value,
|
|
307
|
+
"user_id": event.data.user_id,
|
|
308
|
+
"user_roles": event.data.user_roles,
|
|
309
|
+
"tool_name": event.data.tool_name,
|
|
310
|
+
"authorized": event.data.authorization_allowed,
|
|
311
|
+
"reason": event.data.authorization_reason,
|
|
312
|
+
"policies": event.data.policies_evaluated,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
def create_metadata(
|
|
316
|
+
self,
|
|
317
|
+
period_start: datetime | None = None,
|
|
318
|
+
period_end: datetime | None = None,
|
|
319
|
+
) -> ComplianceMetadata:
|
|
320
|
+
"""Create metadata for a report."""
|
|
321
|
+
return ComplianceMetadata(
|
|
322
|
+
framework=self.framework,
|
|
323
|
+
version=self.framework_version,
|
|
324
|
+
organization=self._organization,
|
|
325
|
+
system_name=self._system_name,
|
|
326
|
+
responsible_party=self._responsible_party,
|
|
327
|
+
period_start=period_start,
|
|
328
|
+
period_end=period_end,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def compute_summary_stats(
|
|
332
|
+
self,
|
|
333
|
+
events: list[AuditEventV2],
|
|
334
|
+
) -> dict[str, Any]:
|
|
335
|
+
"""Compute summary statistics for a list of events."""
|
|
336
|
+
if not events:
|
|
337
|
+
return {
|
|
338
|
+
"total_events": 0,
|
|
339
|
+
"unique_users": 0,
|
|
340
|
+
"unique_tools": 0,
|
|
341
|
+
"authorization_granted": 0,
|
|
342
|
+
"authorization_denied": 0,
|
|
343
|
+
"grant_rate": 0.0,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
granted = sum(1 for e in events if e.data.authorization_allowed)
|
|
347
|
+
denied = sum(1 for e in events if not e.data.authorization_allowed)
|
|
348
|
+
users = {e.data.user_id for e in events}
|
|
349
|
+
tools = {e.data.tool_name for e in events}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
"total_events": len(events),
|
|
353
|
+
"unique_users": len(users),
|
|
354
|
+
"unique_tools": len(tools),
|
|
355
|
+
"authorization_granted": granted,
|
|
356
|
+
"authorization_denied": denied,
|
|
357
|
+
"grant_rate": granted / len(events) if events else 0.0,
|
|
358
|
+
"period_start": min(e.timestamp for e in events).isoformat(),
|
|
359
|
+
"period_end": max(e.timestamp for e in events).isoformat(),
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
@abstractmethod
|
|
363
|
+
def generate_report(
|
|
364
|
+
self,
|
|
365
|
+
start: datetime,
|
|
366
|
+
end: datetime,
|
|
367
|
+
) -> ComplianceReport:
|
|
368
|
+
"""
|
|
369
|
+
Generate a compliance report for the given period.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
start: Start of the reporting period.
|
|
373
|
+
end: End of the reporting period.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Complete compliance report.
|
|
377
|
+
"""
|
|
378
|
+
...
|
|
379
|
+
|
|
380
|
+
def export_json(
|
|
381
|
+
self,
|
|
382
|
+
start: datetime,
|
|
383
|
+
end: datetime,
|
|
384
|
+
pretty: bool = True,
|
|
385
|
+
) -> str:
|
|
386
|
+
"""Export report as JSON."""
|
|
387
|
+
report = self.generate_report(start, end)
|
|
388
|
+
return report.to_json(pretty=pretty)
|
|
389
|
+
|
|
390
|
+
def export_markdown(
|
|
391
|
+
self,
|
|
392
|
+
start: datetime,
|
|
393
|
+
end: datetime,
|
|
394
|
+
) -> str:
|
|
395
|
+
"""Export report as Markdown."""
|
|
396
|
+
report = self.generate_report(start, end)
|
|
397
|
+
return self._report_to_markdown(report)
|
|
398
|
+
|
|
399
|
+
def _report_to_markdown(self, report: ComplianceReport) -> str:
|
|
400
|
+
"""Convert a report to Markdown format."""
|
|
401
|
+
lines = [
|
|
402
|
+
f"# {report.metadata.framework.value.upper()} Compliance Report",
|
|
403
|
+
"",
|
|
404
|
+
"## Metadata",
|
|
405
|
+
"",
|
|
406
|
+
f"- **Organization:** {report.metadata.organization}",
|
|
407
|
+
f"- **System:** {report.metadata.system_name}",
|
|
408
|
+
f"- **Responsible Party:** {report.metadata.responsible_party}",
|
|
409
|
+
f"- **Report Generated:** {report.metadata.export_timestamp.isoformat()}",
|
|
410
|
+
f"- **Period:** "
|
|
411
|
+
f"{report.metadata.period_start.isoformat() if report.metadata.period_start else 'N/A'}"
|
|
412
|
+
f" to "
|
|
413
|
+
f"{report.metadata.period_end.isoformat() if report.metadata.period_end else 'N/A'}",
|
|
414
|
+
"",
|
|
415
|
+
"## Summary",
|
|
416
|
+
"",
|
|
417
|
+
]
|
|
418
|
+
|
|
419
|
+
for key, value in report.summary.items():
|
|
420
|
+
lines.append(f"- **{key.replace('_', ' ').title()}:** {value}")
|
|
421
|
+
|
|
422
|
+
lines.extend(["", "## Evidence", ""])
|
|
423
|
+
|
|
424
|
+
for evidence in report.evidence:
|
|
425
|
+
lines.extend([
|
|
426
|
+
f"### {evidence.control_id}: {evidence.control_name}",
|
|
427
|
+
"",
|
|
428
|
+
f"**Type:** {evidence.evidence_type}",
|
|
429
|
+
"",
|
|
430
|
+
evidence.description,
|
|
431
|
+
"",
|
|
432
|
+
f"**Events:** {len(evidence.events)}",
|
|
433
|
+
"",
|
|
434
|
+
])
|
|
435
|
+
|
|
436
|
+
if evidence.summary:
|
|
437
|
+
lines.append("**Summary:**")
|
|
438
|
+
for key, value in evidence.summary.items():
|
|
439
|
+
lines.append(f"- {key}: {value}")
|
|
440
|
+
lines.append("")
|
|
441
|
+
|
|
442
|
+
if evidence.compliant is not None:
|
|
443
|
+
status = "Compliant" if evidence.compliant else "Non-Compliant"
|
|
444
|
+
lines.append(f"**Status:** {status}")
|
|
445
|
+
lines.append("")
|
|
446
|
+
|
|
447
|
+
if evidence.notes:
|
|
448
|
+
lines.append(f"**Notes:** {evidence.notes}")
|
|
449
|
+
lines.append("")
|
|
450
|
+
|
|
451
|
+
if report.recommendations:
|
|
452
|
+
lines.extend(["## Recommendations", ""])
|
|
453
|
+
for i, rec in enumerate(report.recommendations, 1):
|
|
454
|
+
lines.append(f"{i}. {rec}")
|
|
455
|
+
lines.append("")
|
|
456
|
+
|
|
457
|
+
return "\n".join(lines)
|