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,486 @@
1
+ """
2
+ Built-in policies for Proxilion.
3
+
4
+ This module provides commonly used policy implementations that can be
5
+ used directly or extended for custom authorization logic.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ from proxilion.policies.base import Policy
14
+
15
+ if TYPE_CHECKING:
16
+ from proxilion.types import UserContext
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class DenyAllPolicy(Policy[Any]):
22
+ """
23
+ Policy that denies all actions.
24
+
25
+ This is the safest default policy - it denies everything unless
26
+ explicitly allowed. Use this as a base class or as the default
27
+ policy in the registry to ensure secure-by-default behavior.
28
+
29
+ Example:
30
+ >>> registry = PolicyRegistry(default_policy=DenyAllPolicy)
31
+ >>> # Any unregistered resource will be denied
32
+ """
33
+
34
+ def can_execute(self, context: dict[str, Any]) -> bool:
35
+ """Deny execution."""
36
+ return False
37
+
38
+ def can_read(self, context: dict[str, Any]) -> bool:
39
+ """Deny read access."""
40
+ return False
41
+
42
+ def can_write(self, context: dict[str, Any]) -> bool:
43
+ """Deny write access."""
44
+ return False
45
+
46
+ def can_delete(self, context: dict[str, Any]) -> bool:
47
+ """Deny delete access."""
48
+ return False
49
+
50
+ def can_create(self, context: dict[str, Any]) -> bool:
51
+ """Deny create access."""
52
+ return False
53
+
54
+ def can_update(self, context: dict[str, Any]) -> bool:
55
+ """Deny update access."""
56
+ return False
57
+
58
+ def can_list(self, context: dict[str, Any]) -> bool:
59
+ """Deny list access."""
60
+ return False
61
+
62
+ def authorize(self, action: str, context: dict[str, Any] | None = None) -> bool:
63
+ """
64
+ Override authorize to deny all actions.
65
+
66
+ Even actions without explicit can_<action> methods are denied.
67
+ """
68
+ logger.debug(
69
+ f"DenyAllPolicy: Denying action '{action}' for user '{self.user.user_id}'"
70
+ )
71
+ return False
72
+
73
+
74
+ class AllowAllPolicy(Policy[Any]):
75
+ """
76
+ Policy that allows all actions.
77
+
78
+ WARNING: This policy should ONLY be used for testing or in
79
+ development environments. Using it in production is a security risk.
80
+
81
+ A warning is logged every time this policy is instantiated to
82
+ help catch accidental production usage.
83
+
84
+ Example:
85
+ >>> # For testing only!
86
+ >>> @registry.policy("test_resource")
87
+ >>> class TestPolicy(AllowAllPolicy):
88
+ ... pass
89
+ """
90
+
91
+ def __init__(self, user: UserContext, resource: Any = None) -> None:
92
+ """Initialize with a security warning."""
93
+ super().__init__(user, resource)
94
+ logger.warning(
95
+ f"AllowAllPolicy instantiated for user '{user.user_id}'. "
96
+ "This policy allows ALL actions and should NOT be used in production!"
97
+ )
98
+
99
+ def can_execute(self, context: dict[str, Any]) -> bool:
100
+ """Allow execution."""
101
+ return True
102
+
103
+ def can_read(self, context: dict[str, Any]) -> bool:
104
+ """Allow read access."""
105
+ return True
106
+
107
+ def can_write(self, context: dict[str, Any]) -> bool:
108
+ """Allow write access."""
109
+ return True
110
+
111
+ def can_delete(self, context: dict[str, Any]) -> bool:
112
+ """Allow delete access."""
113
+ return True
114
+
115
+ def can_create(self, context: dict[str, Any]) -> bool:
116
+ """Allow create access."""
117
+ return True
118
+
119
+ def can_update(self, context: dict[str, Any]) -> bool:
120
+ """Allow update access."""
121
+ return True
122
+
123
+ def can_list(self, context: dict[str, Any]) -> bool:
124
+ """Allow list access."""
125
+ return True
126
+
127
+ def authorize(self, action: str, context: dict[str, Any] | None = None) -> bool:
128
+ """
129
+ Override authorize to allow all actions.
130
+
131
+ Even actions without explicit can_<action> methods are allowed.
132
+ """
133
+ logger.debug(
134
+ f"AllowAllPolicy: Allowing action '{action}' for user '{self.user.user_id}'"
135
+ )
136
+ return True
137
+
138
+
139
+ class RoleBasedPolicy(Policy[Any]):
140
+ """
141
+ Base class for role-based authorization policies.
142
+
143
+ This policy checks if the user has any of the required roles
144
+ for a given action. Subclasses define the role requirements
145
+ by setting the `allowed_roles` class attribute.
146
+
147
+ Attributes:
148
+ allowed_roles: Dictionary mapping action names to lists of
149
+ roles that are allowed to perform that action.
150
+ default_allowed: Whether to allow actions not in allowed_roles.
151
+
152
+ Example:
153
+ >>> class DocumentPolicy(RoleBasedPolicy):
154
+ ... allowed_roles = {
155
+ ... "read": ["viewer", "editor", "admin"],
156
+ ... "write": ["editor", "admin"],
157
+ ... "delete": ["admin"],
158
+ ... }
159
+ >>>
160
+ >>> # User with "editor" role can read and write, but not delete
161
+ >>> policy = DocumentPolicy(user_with_editor_role, document)
162
+ >>> policy.can("read") # True
163
+ >>> policy.can("write") # True
164
+ >>> policy.can("delete") # False
165
+ """
166
+
167
+ # Subclasses should override this
168
+ allowed_roles: dict[str, list[str]] = {}
169
+
170
+ # Whether to allow actions not explicitly listed in allowed_roles
171
+ default_allowed: bool = False
172
+
173
+ def authorize(self, action: str, context: dict[str, Any] | None = None) -> bool:
174
+ """
175
+ Check if user has a required role for the action.
176
+
177
+ Args:
178
+ action: The action to check.
179
+ context: Additional context (unused in basic role check).
180
+
181
+ Returns:
182
+ True if user has a required role, False otherwise.
183
+ """
184
+ if action not in self.allowed_roles:
185
+ if self.default_allowed:
186
+ logger.debug(
187
+ f"RoleBasedPolicy: Action '{action}' not in allowed_roles, "
188
+ f"allowing by default"
189
+ )
190
+ return True
191
+ logger.debug(
192
+ f"RoleBasedPolicy: Action '{action}' not in allowed_roles, "
193
+ f"denying by default"
194
+ )
195
+ return False
196
+
197
+ required_roles = self.allowed_roles[action]
198
+ user_roles = set(self.user.roles)
199
+ allowed_role_set = set(required_roles)
200
+
201
+ has_role = bool(user_roles & allowed_role_set)
202
+
203
+ if has_role:
204
+ matching = user_roles & allowed_role_set
205
+ logger.debug(
206
+ f"RoleBasedPolicy: User '{self.user.user_id}' has role(s) "
207
+ f"{matching} for action '{action}'"
208
+ )
209
+ else:
210
+ logger.debug(
211
+ f"RoleBasedPolicy: User '{self.user.user_id}' lacks required "
212
+ f"role(s) {required_roles} for action '{action}'"
213
+ )
214
+
215
+ return has_role
216
+
217
+ @classmethod
218
+ def with_roles(
219
+ cls,
220
+ roles: dict[str, list[str]],
221
+ default_allowed: bool = False,
222
+ ) -> type[RoleBasedPolicy]:
223
+ """
224
+ Create a RoleBasedPolicy subclass with specific roles.
225
+
226
+ Factory method for creating role-based policies dynamically.
227
+
228
+ Args:
229
+ roles: Dictionary mapping actions to allowed roles.
230
+ default_allowed: Whether to allow unlisted actions.
231
+
232
+ Returns:
233
+ A new RoleBasedPolicy subclass.
234
+
235
+ Example:
236
+ >>> ApiPolicy = RoleBasedPolicy.with_roles({
237
+ ... "read": ["user", "admin"],
238
+ ... "write": ["admin"],
239
+ ... })
240
+ >>> policy = ApiPolicy(user, api_resource)
241
+ """
242
+ class DynamicRolePolicy(cls): # type: ignore[valid-type, misc]
243
+ pass
244
+
245
+ DynamicRolePolicy.allowed_roles = roles
246
+ DynamicRolePolicy.default_allowed = default_allowed
247
+ return DynamicRolePolicy
248
+
249
+
250
+ class AttributeBasedPolicy(Policy[Any]):
251
+ """
252
+ Base class for attribute-based authorization policies.
253
+
254
+ This policy allows defining rules based on user attributes,
255
+ resource attributes, and environmental conditions. More flexible
256
+ than role-based policies but more complex to configure.
257
+
258
+ Subclasses should override the `evaluate_rules` method or
259
+ set the `rules` class attribute.
260
+
261
+ Example:
262
+ >>> class DocumentPolicy(AttributeBasedPolicy):
263
+ ... def evaluate_rules(
264
+ ... self, action: str, context: dict
265
+ ... ) -> bool:
266
+ ... # Allow if user is owner
267
+ ... if self.resource.owner_id == self.user.user_id:
268
+ ... return True
269
+ ... # Allow if user is in same department
270
+ ... if self.user.get_attribute("department") == \
271
+ ... self.resource.department:
272
+ ... return action == "read"
273
+ ... return False
274
+ """
275
+
276
+ def evaluate_rules(
277
+ self,
278
+ action: str,
279
+ context: dict[str, Any],
280
+ ) -> bool:
281
+ """
282
+ Evaluate authorization rules.
283
+
284
+ Override this method to implement custom attribute-based logic.
285
+
286
+ Args:
287
+ action: The action being checked.
288
+ context: Additional context for the decision.
289
+
290
+ Returns:
291
+ True if authorized, False otherwise.
292
+ """
293
+ # Default implementation denies all
294
+ return False
295
+
296
+ def authorize(self, action: str, context: dict[str, Any] | None = None) -> bool:
297
+ """
298
+ Check authorization using attribute-based rules.
299
+
300
+ Args:
301
+ action: The action to check.
302
+ context: Additional context for the decision.
303
+
304
+ Returns:
305
+ True if authorized, False otherwise.
306
+ """
307
+ ctx = context or {}
308
+
309
+ # Add user attributes to context for convenience
310
+ ctx["user_id"] = self.user.user_id
311
+ ctx["user_roles"] = self.user.roles
312
+ ctx["user_attributes"] = self.user.attributes
313
+
314
+ result = self.evaluate_rules(action, ctx)
315
+
316
+ logger.debug(
317
+ f"AttributeBasedPolicy: Action '{action}' for user "
318
+ f"'{self.user.user_id}' -> {'allowed' if result else 'denied'}"
319
+ )
320
+
321
+ return result
322
+
323
+
324
+ class OwnershipPolicy(Policy[Any]):
325
+ """
326
+ Policy that allows actions only if the user owns the resource.
327
+
328
+ This policy checks if the resource has an owner_id or user_id
329
+ attribute that matches the user's ID.
330
+
331
+ Attributes:
332
+ owner_field: Name of the attribute on the resource that contains
333
+ the owner's user ID. Defaults to "owner_id".
334
+ owner_actions: Actions that require ownership. Other actions
335
+ may be allowed based on `allow_non_owner_actions`.
336
+ allow_non_owner_actions: List of actions allowed for non-owners.
337
+
338
+ Example:
339
+ >>> class DocumentPolicy(OwnershipPolicy):
340
+ ... owner_field = "created_by"
341
+ ... allow_non_owner_actions = ["read"] # Anyone can read
342
+ """
343
+
344
+ owner_field: str = "owner_id"
345
+ owner_actions: list[str] = ["write", "delete", "update"]
346
+ allow_non_owner_actions: list[str] = []
347
+
348
+ def is_owner(self) -> bool:
349
+ """Check if the user owns the resource."""
350
+ if self.resource is None:
351
+ return False
352
+
353
+ owner_id = getattr(self.resource, self.owner_field, None)
354
+ if owner_id is None:
355
+ # Try alternative field names
356
+ owner_id = getattr(self.resource, "user_id", None)
357
+
358
+ return owner_id == self.user.user_id
359
+
360
+ def authorize(self, action: str, context: dict[str, Any] | None = None) -> bool:
361
+ """
362
+ Check if user is authorized based on ownership.
363
+
364
+ Args:
365
+ action: The action to check.
366
+ context: Additional context (unused).
367
+
368
+ Returns:
369
+ True if user is owner or action is in allow_non_owner_actions.
370
+ """
371
+ if action in self.allow_non_owner_actions:
372
+ logger.debug(
373
+ f"OwnershipPolicy: Action '{action}' allowed for non-owners"
374
+ )
375
+ return True
376
+
377
+ is_owner = self.is_owner()
378
+
379
+ if is_owner:
380
+ logger.debug(
381
+ f"OwnershipPolicy: User '{self.user.user_id}' is owner, "
382
+ f"allowing action '{action}'"
383
+ )
384
+ else:
385
+ logger.debug(
386
+ f"OwnershipPolicy: User '{self.user.user_id}' is not owner, "
387
+ f"denying action '{action}'"
388
+ )
389
+
390
+ return is_owner
391
+
392
+
393
+ class CompositePolicy(Policy[Any]):
394
+ """
395
+ Policy that combines multiple policies with AND/OR logic.
396
+
397
+ Use this to create complex authorization rules from simpler policies.
398
+
399
+ Attributes:
400
+ policies: List of policy classes to combine.
401
+ require_all: If True, all policies must allow (AND).
402
+ If False, any policy can allow (OR).
403
+
404
+ Example:
405
+ >>> class SecureDocumentPolicy(CompositePolicy):
406
+ ... policies = [RoleBasedPolicy, OwnershipPolicy]
407
+ ... require_all = True # Must have role AND be owner
408
+ """
409
+
410
+ policies: list[type[Policy[Any]]] = []
411
+ require_all: bool = True
412
+
413
+ def authorize(self, action: str, context: dict[str, Any] | None = None) -> bool:
414
+ """
415
+ Check authorization using all configured policies.
416
+
417
+ Args:
418
+ action: The action to check.
419
+ context: Additional context passed to each policy.
420
+
421
+ Returns:
422
+ True if authorized according to combination logic.
423
+ """
424
+ if not self.policies:
425
+ logger.warning("CompositePolicy has no policies configured")
426
+ return False
427
+
428
+ results = []
429
+ for policy_class in self.policies:
430
+ policy = policy_class(self.user, self.resource)
431
+ result = policy.authorize(action, context)
432
+ results.append(result)
433
+
434
+ # Short-circuit evaluation
435
+ if self.require_all and not result:
436
+ logger.debug(
437
+ f"CompositePolicy: {policy_class.__name__} denied "
438
+ f"action '{action}' (require_all=True)"
439
+ )
440
+ return False
441
+ if not self.require_all and result:
442
+ logger.debug(
443
+ f"CompositePolicy: {policy_class.__name__} allowed "
444
+ f"action '{action}' (require_all=False)"
445
+ )
446
+ return True
447
+
448
+ # If we get here with require_all=True, all passed
449
+ # If we get here with require_all=False, all failed
450
+ final_result = self.require_all
451
+ logger.debug(
452
+ f"CompositePolicy: Final result for action '{action}': "
453
+ f"{'allowed' if final_result else 'denied'}"
454
+ )
455
+ return final_result
456
+
457
+ @classmethod
458
+ def combine(
459
+ cls,
460
+ *policy_classes: type[Policy[Any]],
461
+ require_all: bool = True,
462
+ ) -> type[CompositePolicy]:
463
+ """
464
+ Create a CompositePolicy combining multiple policies.
465
+
466
+ Factory method for creating composite policies dynamically.
467
+
468
+ Args:
469
+ *policy_classes: Policy classes to combine.
470
+ require_all: Whether all must allow (AND) or any (OR).
471
+
472
+ Returns:
473
+ A new CompositePolicy subclass.
474
+
475
+ Example:
476
+ >>> CombinedPolicy = CompositePolicy.combine(
477
+ ... RolePolicy, OwnerPolicy,
478
+ ... require_all=True
479
+ ... )
480
+ """
481
+ class DynamicCompositePolicy(cls):
482
+ pass
483
+
484
+ DynamicCompositePolicy.policies = list(policy_classes)
485
+ DynamicCompositePolicy.require_all = require_all
486
+ return DynamicCompositePolicy