iris-security-sdk 0.1.7__tar.gz → 0.2.0__tar.gz
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.
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/PKG-INFO +2 -2
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/iris/__init__.py +28 -6
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/iris/_telemetry.py +34 -1
- iris_security_sdk-0.2.0/iris/hitl.py +81 -0
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/iris_security_sdk.egg-info/PKG-INFO +2 -2
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/iris_security_sdk.egg-info/SOURCES.txt +1 -0
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/iris_security_sdk.egg-info/requires.txt +1 -1
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/pyproject.toml +3 -2
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/README.md +0 -0
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/iris/_telemetry_config.py +0 -0
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/iris_security_sdk.egg-info/dependency_links.txt +0 -0
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/iris_security_sdk.egg-info/top_level.txt +0 -0
- {iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iris-security-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: IRIS — AI Agent Governance SDK. Govern AI agents locally.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/gimartinb/iris-sdk
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
|
-
Requires-Dist: iris-security-core>=0.1.
|
|
19
|
+
Requires-Dist: iris-security-core>=0.1.8
|
|
20
20
|
Requires-Dist: pyyaml>=6.0
|
|
21
21
|
Requires-Dist: pydantic>=2.0
|
|
22
22
|
Requires-Dist: click>=8.1
|
|
@@ -34,17 +34,20 @@ from iris_core.models.passport import (
|
|
|
34
34
|
Environment,
|
|
35
35
|
ComplianceTag,
|
|
36
36
|
ToolPermission,
|
|
37
|
+
UserContext,
|
|
37
38
|
)
|
|
39
|
+
from iris_core.hitl.models import HITLConfig, HITLConditionRule, HITLStatus
|
|
40
|
+
from iris_core.hitl.error import IrisHITLRequiredError
|
|
41
|
+
from iris.hitl import HITLPoller
|
|
38
42
|
from iris_core.models.policy import PolicyResult, Violation, Severity
|
|
39
43
|
from iris_core.engine.cedar import CedarEngine, EvaluationContext
|
|
40
|
-
from iris_core.rbac.context import UserContext
|
|
41
44
|
from iris_core.engine.compiler import PolicyCompiler, CompilationResult
|
|
42
45
|
from iris_core.compliance.registry import ComplianceRegistry
|
|
43
46
|
from iris_core.evidence.vault import EvidenceVault
|
|
44
47
|
from iris_core.cost.tracker import CostSummary, CostTracker, CostEntry
|
|
45
48
|
from iris_core.cost.pricing import PricingRegistry
|
|
46
49
|
|
|
47
|
-
__version__ = "0.
|
|
50
|
+
__version__ = "0.2.0"
|
|
48
51
|
__all__ = [
|
|
49
52
|
# Main classes
|
|
50
53
|
"IrisAgent",
|
|
@@ -56,6 +59,7 @@ __all__ = [
|
|
|
56
59
|
"Environment",
|
|
57
60
|
"ComplianceTag",
|
|
58
61
|
"ToolPermission",
|
|
62
|
+
"UserContext",
|
|
59
63
|
"EvaluationContext",
|
|
60
64
|
"PolicyResult",
|
|
61
65
|
"Violation",
|
|
@@ -72,6 +76,11 @@ __all__ = [
|
|
|
72
76
|
"PricingRegistry",
|
|
73
77
|
"IrisViolationError",
|
|
74
78
|
"IrisCrisisDetectedError",
|
|
79
|
+
"IrisHITLRequiredError",
|
|
80
|
+
"HITLConfig",
|
|
81
|
+
"HITLConditionRule",
|
|
82
|
+
"HITLStatus",
|
|
83
|
+
"HITLPoller",
|
|
75
84
|
]
|
|
76
85
|
|
|
77
86
|
|
|
@@ -119,11 +128,15 @@ class IrisAgent:
|
|
|
119
128
|
environment: Optional[str] = None,
|
|
120
129
|
user_email: Optional[str] = None,
|
|
121
130
|
user_role: Optional[str] = None,
|
|
131
|
+
user_context: Optional[UserContext] = None,
|
|
122
132
|
):
|
|
123
133
|
compliance_tags = [ComplianceTag(c) for c in (compliance or [])]
|
|
124
134
|
envs = [Environment(e) for e in (environments or ["dev", "test", "staging", "production"])]
|
|
125
135
|
current_env = Environment(environment or os.environ.get("IRIS_ENV", "dev"))
|
|
126
|
-
self._user_ctx = UserContext.from_params(
|
|
136
|
+
self._user_ctx = user_context or UserContext.from_params(
|
|
137
|
+
user_email=user_email, user_role=user_role
|
|
138
|
+
)
|
|
139
|
+
self._default_user_context = user_context
|
|
127
140
|
|
|
128
141
|
self.passport = AgentPassport(
|
|
129
142
|
name=name,
|
|
@@ -190,6 +203,7 @@ class IrisAgent:
|
|
|
190
203
|
data_region: Optional[str] = None,
|
|
191
204
|
destination_region: Optional[str] = None,
|
|
192
205
|
data_classification: Optional[str] = None,
|
|
206
|
+
user_context: Optional[UserContext] = None,
|
|
193
207
|
) -> Callable:
|
|
194
208
|
"""
|
|
195
209
|
Decorator that intercepts function calls and evaluates them against policy.
|
|
@@ -205,6 +219,10 @@ class IrisAgent:
|
|
|
205
219
|
def decorator(func: Callable) -> Callable:
|
|
206
220
|
@wraps(func)
|
|
207
221
|
def wrapper(*args, **kwargs) -> Any:
|
|
222
|
+
effective_user = user_context or self._default_user_context
|
|
223
|
+
user_fields = self._user_ctx.evaluation_fields()
|
|
224
|
+
if effective_user:
|
|
225
|
+
user_fields = effective_user.evaluation_fields()
|
|
208
226
|
ctx = EvaluationContext(
|
|
209
227
|
agent_id=self.passport.agent_id,
|
|
210
228
|
action=action,
|
|
@@ -214,7 +232,8 @@ class IrisAgent:
|
|
|
214
232
|
data_region=data_region,
|
|
215
233
|
destination_region=destination_region,
|
|
216
234
|
data_classification=data_classification,
|
|
217
|
-
|
|
235
|
+
user_context=effective_user,
|
|
236
|
+
**user_fields,
|
|
218
237
|
)
|
|
219
238
|
result = self.evaluate(ctx)
|
|
220
239
|
|
|
@@ -231,7 +250,7 @@ class IrisAgent:
|
|
|
231
250
|
def evaluate(self, context: EvaluationContext) -> PolicyResult:
|
|
232
251
|
"""Direct policy evaluation without the decorator pattern."""
|
|
233
252
|
result = self._engine.evaluate(self.passport, context)
|
|
234
|
-
self._vault.record(context, result)
|
|
253
|
+
self._vault.record(context, result, passport=self.passport)
|
|
235
254
|
from iris._telemetry import maybe_fire_first_policy_run
|
|
236
255
|
|
|
237
256
|
maybe_fire_first_policy_run()
|
|
@@ -278,8 +297,11 @@ class IrisViolationError(Exception):
|
|
|
278
297
|
explanation of what was blocked and how to remediate.
|
|
279
298
|
"""
|
|
280
299
|
|
|
281
|
-
def __init__(self, result: PolicyResult):
|
|
300
|
+
def __init__(self, result: PolicyResult, is_compliance_block: bool = False):
|
|
282
301
|
self.result = result
|
|
302
|
+
self.is_compliance_block = is_compliance_block or getattr(
|
|
303
|
+
result, "is_compliance_block", False
|
|
304
|
+
)
|
|
283
305
|
primary = result.violations[0] if result.violations else None
|
|
284
306
|
message = (
|
|
285
307
|
f"\n[IRIS POLICY VIOLATION]\n"
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
"""First-run telemetry for IRIS SDK installs. Opt-out via IRIS_TELEMETRY_OPT_OUT=1.
|
|
1
|
+
"""First-run telemetry for IRIS SDK installs. Opt-out via IRIS_TELEMETRY_OPT_OUT=1.
|
|
2
|
+
|
|
3
|
+
Each event includes an ISO 8601 UTC timestamp for server-side DoD/WoW/MoM aggregation.
|
|
4
|
+
"""
|
|
2
5
|
|
|
3
6
|
from __future__ import annotations
|
|
4
7
|
|
|
@@ -19,6 +22,35 @@ _FIRST_RUN_SENTINEL = _IRIS_DIR / ".telemetry_sent"
|
|
|
19
22
|
_FIRST_POLICY_SENTINEL = _IRIS_DIR / ".first_policy_sent"
|
|
20
23
|
|
|
21
24
|
|
|
25
|
+
def detect_country() -> str:
|
|
26
|
+
"""Infer ISO 3166-1 alpha-2 country from system locale (not GPS or IP)."""
|
|
27
|
+
candidates: list[str] = []
|
|
28
|
+
for env in ("LC_ALL", "LC_CTYPE", "LANG"):
|
|
29
|
+
val = os.environ.get(env, "").strip()
|
|
30
|
+
if val:
|
|
31
|
+
candidates.append(val)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
import locale
|
|
35
|
+
|
|
36
|
+
for getter in (locale.getlocale, locale.getdefaultlocale):
|
|
37
|
+
try:
|
|
38
|
+
loc = getter()
|
|
39
|
+
if loc and loc[0]:
|
|
40
|
+
candidates.append(loc[0])
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
for tag in candidates:
|
|
47
|
+
normalized = tag.split(".")[0].replace("-", "_")
|
|
48
|
+
parts = normalized.split("_")
|
|
49
|
+
if len(parts) >= 2 and len(parts[-1]) == 2 and parts[-1].isalpha():
|
|
50
|
+
return parts[-1].upper()
|
|
51
|
+
return "unknown"
|
|
52
|
+
|
|
53
|
+
|
|
22
54
|
def detect_install_method() -> str:
|
|
23
55
|
"""Detect how the iris CLI was installed from the executable path."""
|
|
24
56
|
iris_path = shutil.which("iris")
|
|
@@ -69,6 +101,7 @@ def _build_payload(event: str) -> dict[str, str]:
|
|
|
69
101
|
"event": event,
|
|
70
102
|
"install_id": _get_or_create_install_id(),
|
|
71
103
|
"install_method": detect_install_method(),
|
|
104
|
+
"country": detect_country(),
|
|
72
105
|
"python_version": sys.version,
|
|
73
106
|
"platform": sys.platform,
|
|
74
107
|
"iris_version": _get_iris_version(),
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""HITL polling helper for application-layer wait loops."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from typing import Callable, Optional
|
|
8
|
+
|
|
9
|
+
from iris_core.hitl.models import HITLStatus
|
|
10
|
+
from iris_core.hitl.queue import HITLQueue
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HITLPoller:
|
|
14
|
+
"""
|
|
15
|
+
Helper for applications that need to poll for HITL resolution.
|
|
16
|
+
Handles the polling loop, timeout, and retry logic.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, review_id: str, queue: Optional[HITLQueue] = None):
|
|
20
|
+
self.review_id = review_id
|
|
21
|
+
self._queue = queue or HITLQueue()
|
|
22
|
+
|
|
23
|
+
def wait(
|
|
24
|
+
self,
|
|
25
|
+
timeout: int = 300,
|
|
26
|
+
poll_interval: int = 5,
|
|
27
|
+
on_status_change: Optional[Callable[[HITLStatus], None]] = None,
|
|
28
|
+
) -> HITLStatus:
|
|
29
|
+
deadline = time.time() + timeout
|
|
30
|
+
last_status: Optional[HITLStatus] = None
|
|
31
|
+
while time.time() < deadline:
|
|
32
|
+
review = self._queue.get(self.review_id)
|
|
33
|
+
if review is None:
|
|
34
|
+
time.sleep(poll_interval)
|
|
35
|
+
continue
|
|
36
|
+
if review.status != last_status:
|
|
37
|
+
if on_status_change:
|
|
38
|
+
on_status_change(review.status)
|
|
39
|
+
last_status = review.status
|
|
40
|
+
if review.status in (
|
|
41
|
+
HITLStatus.APPROVED,
|
|
42
|
+
HITLStatus.REJECTED,
|
|
43
|
+
HITLStatus.TIMED_OUT,
|
|
44
|
+
HITLStatus.ESCALATED,
|
|
45
|
+
):
|
|
46
|
+
return review.status
|
|
47
|
+
if self._queue.is_expired(review):
|
|
48
|
+
resolved = self._queue.apply_timeout_policy(review)
|
|
49
|
+
return resolved.status
|
|
50
|
+
time.sleep(poll_interval)
|
|
51
|
+
return HITLStatus.TIMED_OUT
|
|
52
|
+
|
|
53
|
+
async def wait_async(
|
|
54
|
+
self,
|
|
55
|
+
timeout: int = 300,
|
|
56
|
+
poll_interval: int = 5,
|
|
57
|
+
on_status_change: Optional[Callable[[HITLStatus], None]] = None,
|
|
58
|
+
) -> HITLStatus:
|
|
59
|
+
deadline = time.time() + timeout
|
|
60
|
+
last_status: Optional[HITLStatus] = None
|
|
61
|
+
while time.time() < deadline:
|
|
62
|
+
review = self._queue.get(self.review_id)
|
|
63
|
+
if review is None:
|
|
64
|
+
await asyncio.sleep(poll_interval)
|
|
65
|
+
continue
|
|
66
|
+
if review.status != last_status:
|
|
67
|
+
if on_status_change:
|
|
68
|
+
on_status_change(review.status)
|
|
69
|
+
last_status = review.status
|
|
70
|
+
if review.status in (
|
|
71
|
+
HITLStatus.APPROVED,
|
|
72
|
+
HITLStatus.REJECTED,
|
|
73
|
+
HITLStatus.TIMED_OUT,
|
|
74
|
+
HITLStatus.ESCALATED,
|
|
75
|
+
):
|
|
76
|
+
return review.status
|
|
77
|
+
if self._queue.is_expired(review):
|
|
78
|
+
resolved = self._queue.apply_timeout_policy(review)
|
|
79
|
+
return resolved.status
|
|
80
|
+
await asyncio.sleep(poll_interval)
|
|
81
|
+
return HITLStatus.TIMED_OUT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iris-security-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: IRIS — AI Agent Governance SDK. Govern AI agents locally.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Project-URL: Homepage, https://github.com/gimartinb/iris-sdk
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
|
-
Requires-Dist: iris-security-core>=0.1.
|
|
19
|
+
Requires-Dist: iris-security-core>=0.1.8
|
|
20
20
|
Requires-Dist: pyyaml>=6.0
|
|
21
21
|
Requires-Dist: pydantic>=2.0
|
|
22
22
|
Requires-Dist: click>=8.1
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "iris-security-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "IRIS — AI Agent Governance SDK. Govern AI agents locally."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -28,7 +28,7 @@ classifiers = [
|
|
|
28
28
|
"Programming Language :: Python :: 3.12",
|
|
29
29
|
]
|
|
30
30
|
dependencies = [
|
|
31
|
-
"iris-security-core>=0.1.
|
|
31
|
+
"iris-security-core>=0.1.8",
|
|
32
32
|
"pyyaml>=6.0",
|
|
33
33
|
"pydantic>=2.0",
|
|
34
34
|
"click>=8.1",
|
|
@@ -65,6 +65,7 @@ Repository = "https://github.com/gimartinb/iris-sdk"
|
|
|
65
65
|
|
|
66
66
|
[tool.setuptools.packages.find]
|
|
67
67
|
where = ["."]
|
|
68
|
+
exclude = ["tools*", "demo*", "docs*"]
|
|
68
69
|
|
|
69
70
|
[tool.ruff]
|
|
70
71
|
line-length = 100
|
|
File without changes
|
|
File without changes
|
{iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/iris_security_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{iris_security_sdk-0.1.7 → iris_security_sdk-0.2.0}/iris_security_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|