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
proxilion/types.py ADDED
@@ -0,0 +1,339 @@
1
+ """
2
+ Core type definitions for Proxilion.
3
+
4
+ This module defines the fundamental data structures used throughout the SDK
5
+ for representing user context, agent context, tool call requests, authorization
6
+ results, and audit events.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ import uuid
14
+ from dataclasses import dataclass, field
15
+ from datetime import datetime, timezone
16
+ from typing import Any
17
+
18
+
19
+ def _generate_event_id() -> str:
20
+ """Generate a UUID v4 for event identification.
21
+
22
+ Note: UUID v7 would be ideal for time-ordering, but it's not available
23
+ in Python stdlib until 3.14. Using UUID v4 with explicit timestamps instead.
24
+ """
25
+ return str(uuid.uuid4())
26
+
27
+
28
+ def _utc_now() -> datetime:
29
+ """Get current UTC time with timezone info."""
30
+ return datetime.now(timezone.utc)
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class UserContext:
35
+ """
36
+ Represents the authenticated user making a tool call request.
37
+
38
+ This context travels through the agent to the tool, ensuring that
39
+ authorization decisions are made based on the actual user's identity
40
+ and permissions, not the agent's service account.
41
+
42
+ Attributes:
43
+ user_id: Unique identifier for the user (e.g., from your auth system).
44
+ roles: List of role names assigned to the user (e.g., ["analyst", "viewer"]).
45
+ session_id: Optional session identifier for tracking request context.
46
+ attributes: Additional custom attributes for policy decisions
47
+ (e.g., {"department": "engineering", "clearance_level": 3}).
48
+
49
+ Example:
50
+ >>> user = UserContext(
51
+ ... user_id="user_123",
52
+ ... roles=["analyst", "viewer"],
53
+ ... session_id="sess_abc",
54
+ ... attributes={"department": "engineering"}
55
+ ... )
56
+ """
57
+ user_id: str
58
+ roles: list[str] = field(default_factory=list)
59
+ session_id: str | None = None
60
+ attributes: dict[str, Any] = field(default_factory=dict)
61
+
62
+ def has_role(self, role: str) -> bool:
63
+ """Check if user has a specific role."""
64
+ return role in self.roles
65
+
66
+ def has_any_role(self, roles: list[str]) -> bool:
67
+ """Check if user has any of the specified roles."""
68
+ return any(role in self.roles for role in roles)
69
+
70
+ def has_all_roles(self, roles: list[str]) -> bool:
71
+ """Check if user has all of the specified roles."""
72
+ return all(role in self.roles for role in roles)
73
+
74
+ def get_attribute(self, key: str, default: Any = None) -> Any:
75
+ """Get a user attribute with optional default."""
76
+ return self.attributes.get(key, default)
77
+
78
+ def to_dict(self) -> dict[str, Any]:
79
+ """Convert to dictionary for serialization."""
80
+ return {
81
+ "user_id": self.user_id,
82
+ "roles": self.roles,
83
+ "session_id": self.session_id,
84
+ "attributes": self.attributes,
85
+ }
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class AgentContext:
90
+ """
91
+ Represents the AI agent making tool calls on behalf of a user.
92
+
93
+ The agent context helps track which agent is operating and can be used
94
+ to implement agent-level security policies (e.g., limiting which agents
95
+ can use sensitive tools).
96
+
97
+ Attributes:
98
+ agent_id: Unique identifier for the agent instance.
99
+ capabilities: List of capability names the agent is allowed to use.
100
+ trust_score: Float between 0-1 indicating agent trust level.
101
+ Lower scores may trigger additional verification.
102
+
103
+ Example:
104
+ >>> agent = AgentContext(
105
+ ... agent_id="agent_openai_gpt4",
106
+ ... capabilities=["read_files", "search"],
107
+ ... trust_score=0.8
108
+ ... )
109
+ """
110
+ agent_id: str
111
+ capabilities: list[str] = field(default_factory=list)
112
+ trust_score: float = 1.0
113
+
114
+ def __post_init__(self) -> None:
115
+ """Validate trust_score is within bounds."""
116
+ if not 0.0 <= self.trust_score <= 1.0:
117
+ raise ValueError(f"trust_score must be between 0 and 1, got {self.trust_score}")
118
+
119
+ def has_capability(self, capability: str) -> bool:
120
+ """Check if agent has a specific capability."""
121
+ return capability in self.capabilities
122
+
123
+ def is_high_trust(self, threshold: float = 0.8) -> bool:
124
+ """Check if agent meets high trust threshold."""
125
+ return self.trust_score >= threshold
126
+
127
+ def to_dict(self) -> dict[str, Any]:
128
+ """Convert to dictionary for serialization."""
129
+ return {
130
+ "agent_id": self.agent_id,
131
+ "capabilities": self.capabilities,
132
+ "trust_score": self.trust_score,
133
+ }
134
+
135
+
136
+ @dataclass(frozen=True)
137
+ class ToolCallRequest:
138
+ """
139
+ Represents a request to execute a tool.
140
+
141
+ This captures all the information about what tool is being called
142
+ and with what arguments, enabling validation and authorization checks.
143
+
144
+ Attributes:
145
+ tool_name: Name of the tool being invoked.
146
+ arguments: Dictionary of arguments passed to the tool.
147
+ timestamp: When the request was made (UTC).
148
+
149
+ Example:
150
+ >>> request = ToolCallRequest(
151
+ ... tool_name="database_query",
152
+ ... arguments={"query": "SELECT * FROM users", "limit": 100}
153
+ ... )
154
+ """
155
+ tool_name: str
156
+ arguments: dict[str, Any] = field(default_factory=dict)
157
+ timestamp: datetime = field(default_factory=_utc_now)
158
+
159
+ def get_argument(self, key: str, default: Any = None) -> Any:
160
+ """Get an argument value with optional default."""
161
+ return self.arguments.get(key, default)
162
+
163
+ def to_dict(self) -> dict[str, Any]:
164
+ """Convert to dictionary for serialization."""
165
+ return {
166
+ "tool_name": self.tool_name,
167
+ "arguments": self.arguments,
168
+ "timestamp": self.timestamp.isoformat(),
169
+ }
170
+
171
+
172
+ @dataclass(frozen=True)
173
+ class AuthorizationResult:
174
+ """
175
+ Result of an authorization check.
176
+
177
+ Captures whether the action was allowed, the reason for the decision,
178
+ and which policies were evaluated to reach the decision.
179
+
180
+ Attributes:
181
+ allowed: Whether the action is authorized.
182
+ reason: Human-readable explanation of the decision.
183
+ policies_evaluated: List of policy names that were checked.
184
+ metadata: Additional information about the decision
185
+ (e.g., rate limit remaining, matched rules).
186
+
187
+ Example:
188
+ >>> result = AuthorizationResult(
189
+ ... allowed=True,
190
+ ... reason="User has 'analyst' role",
191
+ ... policies_evaluated=["DatabaseQueryPolicy"]
192
+ ... )
193
+ """
194
+ allowed: bool
195
+ reason: str | None = None
196
+ policies_evaluated: list[str] = field(default_factory=list)
197
+ metadata: dict[str, Any] = field(default_factory=dict)
198
+
199
+ @classmethod
200
+ def allow(cls, reason: str | None = None,
201
+ policies: list[str] | None = None,
202
+ metadata: dict[str, Any] | None = None) -> AuthorizationResult:
203
+ """Create an allowed result."""
204
+ return cls(
205
+ allowed=True,
206
+ reason=reason,
207
+ policies_evaluated=policies or [],
208
+ metadata=metadata or {},
209
+ )
210
+
211
+ @classmethod
212
+ def deny(cls, reason: str,
213
+ policies: list[str] | None = None,
214
+ metadata: dict[str, Any] | None = None) -> AuthorizationResult:
215
+ """Create a denied result."""
216
+ return cls(
217
+ allowed=False,
218
+ reason=reason,
219
+ policies_evaluated=policies or [],
220
+ metadata=metadata or {},
221
+ )
222
+
223
+ def to_dict(self) -> dict[str, Any]:
224
+ """Convert to dictionary for serialization."""
225
+ return {
226
+ "allowed": self.allowed,
227
+ "reason": self.reason,
228
+ "policies_evaluated": self.policies_evaluated,
229
+ "metadata": self.metadata,
230
+ }
231
+
232
+
233
+ @dataclass
234
+ class AuditEvent:
235
+ """
236
+ A tamper-evident audit log entry for a tool call authorization decision.
237
+
238
+ Each event is linked to the previous event via a hash chain, providing
239
+ cryptographic proof of log integrity. Any modification to historical
240
+ events will break the hash chain and be detectable.
241
+
242
+ Attributes:
243
+ event_id: Unique identifier for this event.
244
+ timestamp: When the event occurred (UTC).
245
+ sequence_number: Monotonically increasing counter for ordering.
246
+ user_context: The user who initiated the tool call.
247
+ agent_context: The agent that made the request (optional).
248
+ tool_call: The tool call request details.
249
+ authorization_result: The authorization decision.
250
+ execution_result: Summary of tool execution (optional, no sensitive data).
251
+ previous_hash: Hash of the previous event in the chain.
252
+ event_hash: Hash of this event (computed after creation).
253
+
254
+ Example:
255
+ >>> event = AuditEvent(
256
+ ... user_context=user,
257
+ ... tool_call=request,
258
+ ... authorization_result=result,
259
+ ... sequence_number=1,
260
+ ... previous_hash="GENESIS"
261
+ ... )
262
+ >>> event.compute_hash()
263
+ """
264
+ user_context: UserContext
265
+ tool_call: ToolCallRequest
266
+ authorization_result: AuthorizationResult
267
+ sequence_number: int
268
+ previous_hash: str
269
+ event_id: str = field(default_factory=_generate_event_id)
270
+ timestamp: datetime = field(default_factory=_utc_now)
271
+ agent_context: AgentContext | None = None
272
+ execution_result: dict[str, Any] | None = None
273
+ event_hash: str = ""
274
+
275
+ def _canonical_json(self) -> str:
276
+ """
277
+ Generate canonical JSON representation for hashing.
278
+
279
+ Uses sorted keys and consistent formatting to ensure the same
280
+ data always produces the same hash.
281
+ """
282
+ data = {
283
+ "event_id": self.event_id,
284
+ "timestamp": self.timestamp.isoformat(),
285
+ "sequence_number": self.sequence_number,
286
+ "user_context": self.user_context.to_dict(),
287
+ "agent_context": self.agent_context.to_dict() if self.agent_context else None,
288
+ "tool_call": self.tool_call.to_dict(),
289
+ "authorization_result": self.authorization_result.to_dict(),
290
+ "execution_result": self.execution_result,
291
+ "previous_hash": self.previous_hash,
292
+ }
293
+ return json.dumps(data, sort_keys=True, separators=(",", ":"))
294
+
295
+ def compute_hash(self) -> str:
296
+ """
297
+ Compute and set the event hash using SHA-256.
298
+
299
+ Returns:
300
+ The computed hash as a hex string prefixed with 'sha256:'.
301
+ """
302
+ canonical = self._canonical_json()
303
+ hash_bytes = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
304
+ self.event_hash = f"sha256:{hash_bytes}"
305
+ return self.event_hash
306
+
307
+ def verify_hash(self) -> bool:
308
+ """
309
+ Verify that the stored hash matches the computed hash.
310
+
311
+ Returns:
312
+ True if the hash is valid, False if tampered.
313
+ """
314
+ if not self.event_hash:
315
+ return False
316
+ expected = self.compute_hash()
317
+ # Restore the original hash since compute_hash modifies it
318
+ current = self.event_hash
319
+ self.event_hash = current
320
+ return current == expected
321
+
322
+ def to_dict(self) -> dict[str, Any]:
323
+ """Convert to dictionary for serialization."""
324
+ return {
325
+ "event_id": self.event_id,
326
+ "timestamp": self.timestamp.isoformat(),
327
+ "sequence_number": self.sequence_number,
328
+ "user_context": self.user_context.to_dict(),
329
+ "agent_context": self.agent_context.to_dict() if self.agent_context else None,
330
+ "tool_call": self.tool_call.to_dict(),
331
+ "authorization_result": self.authorization_result.to_dict(),
332
+ "execution_result": self.execution_result,
333
+ "previous_hash": self.previous_hash,
334
+ "event_hash": self.event_hash,
335
+ }
336
+
337
+ def to_json(self) -> str:
338
+ """Convert to JSON string with canonical formatting."""
339
+ return json.dumps(self.to_dict(), sort_keys=True, indent=2)
@@ -0,0 +1,93 @@
1
+ """
2
+ Schema validation system for Proxilion.
3
+
4
+ This module provides tools for validating LLM tool call arguments
5
+ against defined schemas. It includes security validations for
6
+ path traversal, SQL injection, and IDOR protection.
7
+
8
+ Quick Start:
9
+ >>> from proxilion.validation import SchemaValidator, ToolSchema, ParameterSchema
10
+ >>>
11
+ >>> validator = SchemaValidator()
12
+ >>> schema = ToolSchema(
13
+ ... name="file_read",
14
+ ... description="Read a file",
15
+ ... parameters={
16
+ ... "path": ParameterSchema(
17
+ ... name="path",
18
+ ... type="str",
19
+ ... constraints={"allow_path_traversal": False},
20
+ ... ),
21
+ ... },
22
+ ... required_parameters=["path"],
23
+ ... risk_level="medium",
24
+ ... )
25
+ >>> validator.register_schema("file_read", schema)
26
+ >>> result = validator.validate("file_read", {"path": "/safe/path.txt"})
27
+ >>> print(result.valid)
28
+ True
29
+
30
+ Security Features:
31
+ - Path traversal detection: Rejects ".." and encoded variants
32
+ - SQL injection detection: Detects common SQL injection patterns
33
+ - Object ID validation: Validates UUID, numeric, and alphanumeric IDs
34
+ - Parameter constraints: min, max, pattern, enum, length limits
35
+ """
36
+
37
+ from proxilion.validation.schema import (
38
+ ParameterSchema,
39
+ RiskLevel,
40
+ SchemaValidator,
41
+ ToolSchema,
42
+ ValidationResult,
43
+ )
44
+
45
+ # Optional Pydantic support
46
+ try:
47
+ from proxilion.validation.pydantic_schema import (
48
+ PydanticSchemaValidator,
49
+ create_pydantic_validator,
50
+ )
51
+ HAS_PYDANTIC = True
52
+ except ImportError:
53
+ HAS_PYDANTIC = False
54
+ PydanticSchemaValidator = None # type: ignore
55
+ create_pydantic_validator = None # type: ignore
56
+
57
+
58
+ def create_validator(use_pydantic: bool = False) -> SchemaValidator:
59
+ """
60
+ Create a schema validator.
61
+
62
+ Args:
63
+ use_pydantic: If True, try to create a PydanticSchemaValidator.
64
+ Falls back to SchemaValidator if Pydantic is not installed.
65
+
66
+ Returns:
67
+ A SchemaValidator or PydanticSchemaValidator instance.
68
+
69
+ Example:
70
+ >>> validator = create_validator(use_pydantic=True)
71
+ """
72
+ if use_pydantic and HAS_PYDANTIC and create_pydantic_validator is not None:
73
+ pydantic_validator = create_pydantic_validator()
74
+ if pydantic_validator is not None:
75
+ return pydantic_validator
76
+
77
+ return SchemaValidator()
78
+
79
+
80
+ __all__ = [
81
+ # Core classes
82
+ "ToolSchema",
83
+ "ParameterSchema",
84
+ "SchemaValidator",
85
+ "ValidationResult",
86
+ "RiskLevel",
87
+ # Factory
88
+ "create_validator",
89
+ # Pydantic (optional)
90
+ "PydanticSchemaValidator",
91
+ "create_pydantic_validator",
92
+ "HAS_PYDANTIC",
93
+ ]