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.
Files changed (94) hide show
  1. proxilion/__init__.py +136 -0
  2. proxilion/audit/__init__.py +133 -0
  3. proxilion/audit/base_exporters.py +527 -0
  4. proxilion/audit/compliance/__init__.py +130 -0
  5. proxilion/audit/compliance/base.py +457 -0
  6. proxilion/audit/compliance/eu_ai_act.py +603 -0
  7. proxilion/audit/compliance/iso27001.py +544 -0
  8. proxilion/audit/compliance/soc2.py +491 -0
  9. proxilion/audit/events.py +493 -0
  10. proxilion/audit/explainability.py +1173 -0
  11. proxilion/audit/exporters/__init__.py +58 -0
  12. proxilion/audit/exporters/aws_s3.py +636 -0
  13. proxilion/audit/exporters/azure_storage.py +608 -0
  14. proxilion/audit/exporters/cloud_base.py +468 -0
  15. proxilion/audit/exporters/gcp_storage.py +570 -0
  16. proxilion/audit/exporters/multi_exporter.py +498 -0
  17. proxilion/audit/hash_chain.py +652 -0
  18. proxilion/audit/logger.py +543 -0
  19. proxilion/caching/__init__.py +49 -0
  20. proxilion/caching/tool_cache.py +633 -0
  21. proxilion/context/__init__.py +73 -0
  22. proxilion/context/context_window.py +556 -0
  23. proxilion/context/message_history.py +505 -0
  24. proxilion/context/session.py +735 -0
  25. proxilion/contrib/__init__.py +51 -0
  26. proxilion/contrib/anthropic.py +609 -0
  27. proxilion/contrib/google.py +1012 -0
  28. proxilion/contrib/langchain.py +641 -0
  29. proxilion/contrib/mcp.py +893 -0
  30. proxilion/contrib/openai.py +646 -0
  31. proxilion/core.py +3058 -0
  32. proxilion/decorators.py +966 -0
  33. proxilion/engines/__init__.py +287 -0
  34. proxilion/engines/base.py +266 -0
  35. proxilion/engines/casbin_engine.py +412 -0
  36. proxilion/engines/opa_engine.py +493 -0
  37. proxilion/engines/simple.py +437 -0
  38. proxilion/exceptions.py +887 -0
  39. proxilion/guards/__init__.py +54 -0
  40. proxilion/guards/input_guard.py +522 -0
  41. proxilion/guards/output_guard.py +634 -0
  42. proxilion/observability/__init__.py +198 -0
  43. proxilion/observability/cost_tracker.py +866 -0
  44. proxilion/observability/hooks.py +683 -0
  45. proxilion/observability/metrics.py +798 -0
  46. proxilion/observability/session_cost_tracker.py +1063 -0
  47. proxilion/policies/__init__.py +67 -0
  48. proxilion/policies/base.py +304 -0
  49. proxilion/policies/builtin.py +486 -0
  50. proxilion/policies/registry.py +376 -0
  51. proxilion/providers/__init__.py +201 -0
  52. proxilion/providers/adapter.py +468 -0
  53. proxilion/providers/anthropic_adapter.py +330 -0
  54. proxilion/providers/gemini_adapter.py +391 -0
  55. proxilion/providers/openai_adapter.py +294 -0
  56. proxilion/py.typed +0 -0
  57. proxilion/resilience/__init__.py +81 -0
  58. proxilion/resilience/degradation.py +615 -0
  59. proxilion/resilience/fallback.py +555 -0
  60. proxilion/resilience/retry.py +554 -0
  61. proxilion/scheduling/__init__.py +57 -0
  62. proxilion/scheduling/priority_queue.py +419 -0
  63. proxilion/scheduling/scheduler.py +459 -0
  64. proxilion/security/__init__.py +244 -0
  65. proxilion/security/agent_trust.py +968 -0
  66. proxilion/security/behavioral_drift.py +794 -0
  67. proxilion/security/cascade_protection.py +869 -0
  68. proxilion/security/circuit_breaker.py +428 -0
  69. proxilion/security/cost_limiter.py +690 -0
  70. proxilion/security/idor_protection.py +460 -0
  71. proxilion/security/intent_capsule.py +849 -0
  72. proxilion/security/intent_validator.py +495 -0
  73. proxilion/security/memory_integrity.py +767 -0
  74. proxilion/security/rate_limiter.py +509 -0
  75. proxilion/security/scope_enforcer.py +680 -0
  76. proxilion/security/sequence_validator.py +636 -0
  77. proxilion/security/trust_boundaries.py +784 -0
  78. proxilion/streaming/__init__.py +70 -0
  79. proxilion/streaming/detector.py +761 -0
  80. proxilion/streaming/transformer.py +674 -0
  81. proxilion/timeouts/__init__.py +55 -0
  82. proxilion/timeouts/decorators.py +477 -0
  83. proxilion/timeouts/manager.py +545 -0
  84. proxilion/tools/__init__.py +69 -0
  85. proxilion/tools/decorators.py +493 -0
  86. proxilion/tools/registry.py +732 -0
  87. proxilion/types.py +339 -0
  88. proxilion/validation/__init__.py +93 -0
  89. proxilion/validation/pydantic_schema.py +351 -0
  90. proxilion/validation/schema.py +651 -0
  91. proxilion-0.0.1.dist-info/METADATA +872 -0
  92. proxilion-0.0.1.dist-info/RECORD +94 -0
  93. proxilion-0.0.1.dist-info/WHEEL +4 -0
  94. proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,468 @@
1
+ """
2
+ Base classes and protocols for cloud storage exporters.
3
+
4
+ Provides common interfaces and configuration for exporting audit logs
5
+ to cloud storage providers (AWS S3, GCP Cloud Storage, Azure Blob).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import gzip
11
+ import hashlib
12
+ import logging
13
+ import threading
14
+ import time
15
+ from abc import ABC, abstractmethod
16
+ from dataclasses import dataclass, field
17
+ from datetime import datetime, timezone
18
+ from enum import Enum
19
+ from typing import Any, Literal, Protocol, runtime_checkable
20
+
21
+ from proxilion.audit.events import AuditEventV2
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class CompressionType(Enum):
27
+ """Compression types for exported files."""
28
+ NONE = "none"
29
+ GZIP = "gzip"
30
+ ZSTD = "zstd"
31
+
32
+
33
+ class ExportFormat(Enum):
34
+ """Export file formats."""
35
+ JSONL = "jsonl"
36
+ PARQUET = "parquet"
37
+
38
+
39
+ @dataclass
40
+ class CloudExporterConfig:
41
+ """
42
+ Configuration for cloud storage exporters.
43
+
44
+ Attributes:
45
+ provider: Cloud provider ("aws", "gcp", "azure").
46
+ bucket_name: Name of the bucket/container.
47
+ prefix: Path prefix within the bucket (e.g., "audit-logs/proxilion/").
48
+ region: Cloud region (e.g., "us-west-2", "us-central1").
49
+ credentials_path: Path to service account/credentials file.
50
+ use_instance_credentials: Whether to use instance/managed identity.
51
+ batch_size: Number of events per export batch.
52
+ compression: Compression type for exported files.
53
+ format: Export file format.
54
+ endpoint_url: Custom endpoint URL (for S3-compatible storage).
55
+ connection_timeout: Connection timeout in seconds.
56
+ read_timeout: Read timeout in seconds.
57
+ max_retries: Maximum number of retries for failed operations.
58
+ retry_delay: Initial delay between retries in seconds.
59
+ """
60
+ provider: Literal["aws", "gcp", "azure"]
61
+ bucket_name: str
62
+ prefix: str = ""
63
+ region: str | None = None
64
+ credentials_path: str | None = None
65
+ use_instance_credentials: bool = True
66
+ batch_size: int = 100
67
+ compression: CompressionType = CompressionType.GZIP
68
+ format: ExportFormat = ExportFormat.JSONL
69
+ endpoint_url: str | None = None
70
+ connection_timeout: float = 30.0
71
+ read_timeout: float = 60.0
72
+ max_retries: int = 3
73
+ retry_delay: float = 1.0
74
+
75
+ def __post_init__(self) -> None:
76
+ """Validate and normalize configuration."""
77
+ # Ensure prefix ends with / if not empty
78
+ if self.prefix and not self.prefix.endswith("/"):
79
+ self.prefix = self.prefix + "/"
80
+
81
+ # Convert string enums if needed
82
+ if isinstance(self.compression, str):
83
+ self.compression = CompressionType(self.compression)
84
+ if isinstance(self.format, str):
85
+ self.format = ExportFormat(self.format)
86
+
87
+
88
+ @dataclass
89
+ class ExportResult:
90
+ """
91
+ Result of an export operation.
92
+
93
+ Attributes:
94
+ success: Whether the export succeeded.
95
+ events_exported: Number of events successfully exported.
96
+ batch_id: Unique identifier for this export batch.
97
+ destination: Full path/key where data was exported.
98
+ error: Error message if export failed.
99
+ duration_ms: Export duration in milliseconds.
100
+ bytes_written: Number of bytes written.
101
+ checksum: MD5/SHA256 checksum of exported data.
102
+ """
103
+ success: bool
104
+ events_exported: int = 0
105
+ batch_id: str = ""
106
+ destination: str = ""
107
+ error: str | None = None
108
+ duration_ms: float = 0.0
109
+ bytes_written: int = 0
110
+ checksum: str | None = None
111
+
112
+
113
+ @dataclass
114
+ class ExportBatch:
115
+ """
116
+ A batch of events to export.
117
+
118
+ Attributes:
119
+ batch_id: Unique identifier for this batch.
120
+ events: List of audit events.
121
+ created_at: When the batch was created.
122
+ metadata: Additional metadata for the batch.
123
+ """
124
+ batch_id: str
125
+ events: list[AuditEventV2]
126
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
127
+ metadata: dict[str, Any] = field(default_factory=dict)
128
+
129
+ @property
130
+ def event_count(self) -> int:
131
+ """Get the number of events in the batch."""
132
+ return len(self.events)
133
+
134
+ def to_jsonl(self) -> str:
135
+ """Convert batch to JSON Lines format."""
136
+ lines = []
137
+ for event in self.events:
138
+ lines.append(event.to_json(pretty=False))
139
+ return "\n".join(lines)
140
+
141
+ def to_bytes(self, compression: CompressionType = CompressionType.NONE) -> bytes:
142
+ """
143
+ Convert batch to bytes with optional compression.
144
+
145
+ Args:
146
+ compression: Compression type to apply.
147
+
148
+ Returns:
149
+ Bytes representation of the batch.
150
+ """
151
+ content = self.to_jsonl().encode("utf-8")
152
+
153
+ if compression == CompressionType.GZIP:
154
+ return gzip.compress(content)
155
+ elif compression == CompressionType.ZSTD:
156
+ try:
157
+ import zstandard as zstd
158
+ cctx = zstd.ZstdCompressor()
159
+ return cctx.compress(content)
160
+ except ImportError:
161
+ logger.warning("zstandard not installed, falling back to gzip")
162
+ return gzip.compress(content)
163
+
164
+ return content
165
+
166
+
167
+ @runtime_checkable
168
+ class CloudExporter(Protocol):
169
+ """
170
+ Protocol for cloud storage exporters.
171
+
172
+ All cloud exporters must implement these methods.
173
+ """
174
+
175
+ def export(self, events: list[AuditEventV2]) -> ExportResult:
176
+ """
177
+ Export a list of events to cloud storage.
178
+
179
+ Args:
180
+ events: List of audit events to export.
181
+
182
+ Returns:
183
+ ExportResult with success/failure information.
184
+ """
185
+ ...
186
+
187
+ def export_batch(self, batch: ExportBatch) -> ExportResult:
188
+ """
189
+ Export a pre-formed batch to cloud storage.
190
+
191
+ Args:
192
+ batch: The batch to export.
193
+
194
+ Returns:
195
+ ExportResult with success/failure information.
196
+ """
197
+ ...
198
+
199
+ def configure(self, config: dict[str, Any]) -> None:
200
+ """
201
+ Update exporter configuration.
202
+
203
+ Args:
204
+ config: Configuration dictionary.
205
+ """
206
+ ...
207
+
208
+ def health_check(self) -> bool:
209
+ """
210
+ Check if the exporter can connect to cloud storage.
211
+
212
+ Returns:
213
+ True if healthy and connected.
214
+ """
215
+ ...
216
+
217
+
218
+ class BaseCloudExporter(ABC):
219
+ """
220
+ Abstract base class for cloud storage exporters.
221
+
222
+ Provides common functionality for all cloud exporters including:
223
+ - Key generation with time-based partitioning
224
+ - Compression handling
225
+ - Retry logic
226
+ - Thread-safe batch accumulation
227
+ """
228
+
229
+ def __init__(self, config: CloudExporterConfig) -> None:
230
+ """
231
+ Initialize the base exporter.
232
+
233
+ Args:
234
+ config: Exporter configuration.
235
+ """
236
+ self.config = config
237
+ self._lock = threading.RLock()
238
+ self._pending_events: list[AuditEventV2] = []
239
+ self._batch_counter = 0
240
+
241
+ def generate_key(
242
+ self,
243
+ timestamp: datetime | None = None,
244
+ batch_id: str | None = None,
245
+ ) -> str:
246
+ """
247
+ Generate an object key with time-based partitioning.
248
+
249
+ Format: {prefix}/{year}/{month}/{day}/{hour}/{batch_id}.{ext}
250
+
251
+ Args:
252
+ timestamp: Timestamp for partitioning (default: now).
253
+ batch_id: Unique batch identifier.
254
+
255
+ Returns:
256
+ The generated object key.
257
+ """
258
+ if timestamp is None:
259
+ timestamp = datetime.now(timezone.utc)
260
+
261
+ if batch_id is None:
262
+ with self._lock:
263
+ self._batch_counter += 1
264
+ batch_id = f"{timestamp.strftime('%Y%m%d%H%M%S')}_{self._batch_counter:06d}"
265
+
266
+ # Determine file extension
267
+ ext = self.config.format.value
268
+ if self.config.compression == CompressionType.GZIP:
269
+ ext += ".gz"
270
+ elif self.config.compression == CompressionType.ZSTD:
271
+ ext += ".zst"
272
+
273
+ # Build partitioned path
274
+ key = (
275
+ f"{self.config.prefix}"
276
+ f"{timestamp.year:04d}/"
277
+ f"{timestamp.month:02d}/"
278
+ f"{timestamp.day:02d}/"
279
+ f"{timestamp.hour:02d}/"
280
+ f"{batch_id}.{ext}"
281
+ )
282
+
283
+ return key
284
+
285
+ def prepare_batch(self, events: list[AuditEventV2]) -> ExportBatch:
286
+ """
287
+ Prepare events as an export batch.
288
+
289
+ Args:
290
+ events: Events to include in the batch.
291
+
292
+ Returns:
293
+ Prepared ExportBatch.
294
+ """
295
+ import uuid
296
+
297
+ batch_id = str(uuid.uuid4())
298
+ return ExportBatch(
299
+ batch_id=batch_id,
300
+ events=events,
301
+ metadata={
302
+ "exporter": self.__class__.__name__,
303
+ "provider": self.config.provider,
304
+ "bucket": self.config.bucket_name,
305
+ },
306
+ )
307
+
308
+ def compute_checksum(self, data: bytes) -> str:
309
+ """
310
+ Compute MD5 checksum of data.
311
+
312
+ Args:
313
+ data: Bytes to checksum.
314
+
315
+ Returns:
316
+ Hex-encoded MD5 checksum.
317
+ """
318
+ return hashlib.md5(data).hexdigest()
319
+
320
+ def with_retry(
321
+ self,
322
+ operation: callable,
323
+ *args: Any,
324
+ **kwargs: Any,
325
+ ) -> Any:
326
+ """
327
+ Execute an operation with retry logic.
328
+
329
+ Args:
330
+ operation: Function to execute.
331
+ *args: Positional arguments for operation.
332
+ **kwargs: Keyword arguments for operation.
333
+
334
+ Returns:
335
+ Result of the operation.
336
+
337
+ Raises:
338
+ Last exception if all retries fail.
339
+ """
340
+ last_error: Exception | None = None
341
+ delay = self.config.retry_delay
342
+
343
+ for attempt in range(self.config.max_retries + 1):
344
+ try:
345
+ return operation(*args, **kwargs)
346
+ except Exception as e:
347
+ last_error = e
348
+ if attempt < self.config.max_retries:
349
+ logger.warning(
350
+ f"Export attempt {attempt + 1} failed: {e}. "
351
+ f"Retrying in {delay:.1f}s..."
352
+ )
353
+ time.sleep(delay)
354
+ delay *= 2 # Exponential backoff
355
+
356
+ raise last_error # type: ignore
357
+
358
+ def export(self, events: list[AuditEventV2]) -> ExportResult:
359
+ """
360
+ Export a list of events to cloud storage.
361
+
362
+ Args:
363
+ events: List of audit events to export.
364
+
365
+ Returns:
366
+ ExportResult with success/failure information.
367
+ """
368
+ if not events:
369
+ return ExportResult(
370
+ success=True,
371
+ events_exported=0,
372
+ batch_id="",
373
+ destination="",
374
+ )
375
+
376
+ batch = self.prepare_batch(events)
377
+ return self.export_batch(batch)
378
+
379
+ @abstractmethod
380
+ def export_batch(self, batch: ExportBatch) -> ExportResult:
381
+ """
382
+ Export a batch to cloud storage.
383
+
384
+ Must be implemented by subclasses.
385
+
386
+ Args:
387
+ batch: The batch to export.
388
+
389
+ Returns:
390
+ ExportResult with success/failure information.
391
+ """
392
+ pass
393
+
394
+ def configure(self, config: dict[str, Any]) -> None:
395
+ """
396
+ Update exporter configuration.
397
+
398
+ Args:
399
+ config: Configuration dictionary.
400
+ """
401
+ for key, value in config.items():
402
+ if hasattr(self.config, key):
403
+ setattr(self.config, key, value)
404
+
405
+ @abstractmethod
406
+ def health_check(self) -> bool:
407
+ """
408
+ Check if the exporter can connect to cloud storage.
409
+
410
+ Must be implemented by subclasses.
411
+
412
+ Returns:
413
+ True if healthy and connected.
414
+ """
415
+ pass
416
+
417
+ def add_pending(self, event: AuditEventV2) -> ExportResult | None:
418
+ """
419
+ Add an event to the pending buffer.
420
+
421
+ If the buffer reaches batch_size, exports automatically.
422
+
423
+ Args:
424
+ event: Event to add.
425
+
426
+ Returns:
427
+ ExportResult if batch was exported, None otherwise.
428
+ """
429
+ with self._lock:
430
+ self._pending_events.append(event)
431
+
432
+ if len(self._pending_events) >= self.config.batch_size:
433
+ events = self._pending_events
434
+ self._pending_events = []
435
+ return self.export(events)
436
+
437
+ return None
438
+
439
+ def flush_pending(self) -> ExportResult | None:
440
+ """
441
+ Export any pending events.
442
+
443
+ Returns:
444
+ ExportResult if events were exported, None if buffer was empty.
445
+ """
446
+ with self._lock:
447
+ if not self._pending_events:
448
+ return None
449
+
450
+ events = self._pending_events
451
+ self._pending_events = []
452
+ return self.export(events)
453
+
454
+ def get_content_type(self) -> str:
455
+ """Get the content type for exported files."""
456
+ if self.config.format == ExportFormat.JSONL:
457
+ return "application/x-ndjson"
458
+ elif self.config.format == ExportFormat.PARQUET:
459
+ return "application/vnd.apache.parquet"
460
+ return "application/octet-stream"
461
+
462
+ def get_content_encoding(self) -> str | None:
463
+ """Get the content encoding for compressed files."""
464
+ if self.config.compression == CompressionType.GZIP:
465
+ return "gzip"
466
+ elif self.config.compression == CompressionType.ZSTD:
467
+ return "zstd"
468
+ return None