iris-security-core 0.1.0__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.
- iris_core/__init__.py +33 -0
- iris_core/compliance/__init__.py +0 -0
- iris_core/compliance/bundles/__init__.py +0 -0
- iris_core/compliance/bundles/colorado_ai_act.py +162 -0
- iris_core/compliance/bundles/gdpr.py +168 -0
- iris_core/compliance/bundles/hipaa.py +156 -0
- iris_core/compliance/bundles/soc2.py +156 -0
- iris_core/compliance/license.py +144 -0
- iris_core/compliance/registry.py +111 -0
- iris_core/discovery/__init__.py +15 -0
- iris_core/discovery/scanner.py +527 -0
- iris_core/engine/__init__.py +0 -0
- iris_core/engine/cedar.py +340 -0
- iris_core/engine/compiler.py +283 -0
- iris_core/evidence/__init__.py +0 -0
- iris_core/evidence/vault.py +350 -0
- iris_core/models/__init__.py +0 -0
- iris_core/models/passport.py +181 -0
- iris_core/models/policy.py +57 -0
- iris_core/models/region.py +34 -0
- iris_security_core-0.1.0.dist-info/METADATA +26 -0
- iris_security_core-0.1.0.dist-info/RECORD +28 -0
- iris_security_core-0.1.0.dist-info/WHEEL +5 -0
- iris_security_core-0.1.0.dist-info/top_level.txt +2 -0
- tests/test_colorado_compliance.py +213 -0
- tests/test_license_gate.py +136 -0
- tests/test_scanner.py +229 -0
- tests/test_vault_retention.py +231 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IRIS Pro license gate — offline-first, Terraform-style.
|
|
3
|
+
|
|
4
|
+
Free bundles (colorado-ai-act) never require a license.
|
|
5
|
+
Paid bundles (gdpr, hipaa, soc2) require a valid IRIS license key.
|
|
6
|
+
Post-funding: validate keys against the license server (no network calls today).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
LICENSE_KEY_PATTERN = re.compile(
|
|
18
|
+
r"^IRIS-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$"
|
|
19
|
+
)
|
|
20
|
+
TEST_LICENSE_KEY = "IRIS-TEST-0000-0000-0001"
|
|
21
|
+
ENV_LICENSE_KEY = "IRIS_LICENSE_KEY"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def license_file_path() -> Path:
|
|
25
|
+
return Path.home() / ".iris" / "license.key"
|
|
26
|
+
|
|
27
|
+
FREE_BUNDLES = frozenset({"colorado-ai-act"})
|
|
28
|
+
PAID_BUNDLES = frozenset({"gdpr", "hipaa", "soc2"})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class LicenseResult:
|
|
33
|
+
valid: bool
|
|
34
|
+
tier: str = "free" # "free" | "pro" | "enterprise"
|
|
35
|
+
reason: str = "valid" # "no_key" | "invalid" | "expired" | "valid"
|
|
36
|
+
expires_at: Optional[str] = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class IrisLicenseError(Exception):
|
|
40
|
+
"""Raised when a paid compliance bundle is used without a valid license."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, bundle_id: str, result: Optional[LicenseResult] = None):
|
|
43
|
+
self.bundle_id = bundle_id
|
|
44
|
+
self.result = result
|
|
45
|
+
super().__init__(build_license_required_message(bundle_id))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def build_license_required_message(bundle_id: str) -> str:
|
|
49
|
+
return f"""
|
|
50
|
+
┌─ IRIS Pro Bundle Required ─────────────────────────────┐
|
|
51
|
+
│ │
|
|
52
|
+
│ The {bundle_id} compliance bundle is a paid feature. │
|
|
53
|
+
│ │
|
|
54
|
+
│ Free bundles: colorado-ai-act │
|
|
55
|
+
│ Pro bundles: gdpr, hipaa, soc2 │
|
|
56
|
+
│ │
|
|
57
|
+
│ Get access: │
|
|
58
|
+
│ iris license activate <your-key> │
|
|
59
|
+
│ or set IRIS_LICENSE_KEY=your-key │
|
|
60
|
+
│ │
|
|
61
|
+
│ Get a license: https://iris.ai/pricing │
|
|
62
|
+
└─────────────────────────────────────────────────────────┘
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def is_paid_bundle(bundle_id: str) -> bool:
|
|
67
|
+
return bundle_id in PAID_BUNDLES
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_free_bundle(bundle_id: str) -> bool:
|
|
71
|
+
return bundle_id in FREE_BUNDLES
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _read_license_key() -> Optional[str]:
|
|
75
|
+
env_key = os.environ.get(ENV_LICENSE_KEY, "").strip()
|
|
76
|
+
if env_key:
|
|
77
|
+
return env_key
|
|
78
|
+
path = license_file_path()
|
|
79
|
+
if path.exists():
|
|
80
|
+
return path.read_text().strip()
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def validate_license_key_format(key: str) -> bool:
|
|
85
|
+
if key == TEST_LICENSE_KEY:
|
|
86
|
+
return True
|
|
87
|
+
return bool(LICENSE_KEY_PATTERN.match(key.strip()))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class IrisLicense:
|
|
91
|
+
"""Offline license validation for IRIS Pro compliance bundles."""
|
|
92
|
+
|
|
93
|
+
def check(self, bundle_id: str) -> LicenseResult:
|
|
94
|
+
if is_free_bundle(bundle_id):
|
|
95
|
+
return LicenseResult(valid=True, tier="free", reason="valid")
|
|
96
|
+
|
|
97
|
+
if not is_paid_bundle(bundle_id):
|
|
98
|
+
return LicenseResult(valid=True, tier="free", reason="valid")
|
|
99
|
+
|
|
100
|
+
key = _read_license_key()
|
|
101
|
+
if not key:
|
|
102
|
+
return LicenseResult(valid=False, tier="free", reason="no_key")
|
|
103
|
+
|
|
104
|
+
if not validate_license_key_format(key):
|
|
105
|
+
return LicenseResult(valid=False, tier="free", reason="invalid")
|
|
106
|
+
|
|
107
|
+
expires_at = "2099-12-31" if key == TEST_LICENSE_KEY else None
|
|
108
|
+
return LicenseResult(
|
|
109
|
+
valid=True,
|
|
110
|
+
tier="pro",
|
|
111
|
+
reason="valid",
|
|
112
|
+
expires_at=expires_at,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def require_license(bundle_id: str) -> None:
|
|
117
|
+
"""Raise IrisLicenseError if the bundle requires a license that is not present."""
|
|
118
|
+
if is_free_bundle(bundle_id):
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
result = IrisLicense().check(bundle_id)
|
|
122
|
+
if not result.valid:
|
|
123
|
+
raise IrisLicenseError(bundle_id, result)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def write_license_key(key: str) -> Path:
|
|
127
|
+
"""Persist a validated license key to ~/.iris/license.key."""
|
|
128
|
+
path = license_file_path()
|
|
129
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
130
|
+
path.write_text(key.strip())
|
|
131
|
+
return path
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def paid_bundles_available_note() -> str:
|
|
135
|
+
return (
|
|
136
|
+
"Pro compliance bundles (gdpr, hipaa, soc2) are available with an IRIS Pro license. "
|
|
137
|
+
"Run: iris license activate <your-key> | https://iris.ai/pricing"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def has_unlimited_evidence_retention() -> bool:
|
|
142
|
+
"""Pro/Enterprise licenses retain Evidence Vault events without the 30-day free limit."""
|
|
143
|
+
result = IrisLicense().check("gdpr")
|
|
144
|
+
return result.valid and result.tier in ("pro", "enterprise")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Compliance registry — manages active compliance bundles and rule lookups."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import List, Dict, Any, Optional, Union
|
|
5
|
+
from iris_core.models.policy import Violation, Severity
|
|
6
|
+
from iris_core.compliance.license import (
|
|
7
|
+
IrisLicense,
|
|
8
|
+
is_paid_bundle,
|
|
9
|
+
paid_bundles_available_note,
|
|
10
|
+
require_license,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_BUNDLE_LOADERS = {
|
|
15
|
+
"colorado-ai-act": ("iris_core.compliance.bundles.colorado_ai_act", "get_colorado_rules"),
|
|
16
|
+
"gdpr": ("iris_core.compliance.bundles.gdpr", "get_gdpr_rules"),
|
|
17
|
+
"hipaa": ("iris_core.compliance.bundles.hipaa", "get_hipaa_rules"),
|
|
18
|
+
"soc2": ("iris_core.compliance.bundles.soc2", "get_soc2_rules"),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_PASSPORT_CHECKERS = {
|
|
22
|
+
"gdpr": ("iris_core.compliance.bundles.gdpr", "check_gdpr_passport"),
|
|
23
|
+
"hipaa": ("iris_core.compliance.bundles.hipaa", "check_hipaa_passport"),
|
|
24
|
+
"soc2": ("iris_core.compliance.bundles.soc2", "check_soc2_passport"),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ComplianceRegistry:
|
|
29
|
+
"""Registry of all active compliance bundles."""
|
|
30
|
+
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
self.last_check_notes: List[str] = []
|
|
33
|
+
|
|
34
|
+
def _load_bundle_rules(self, bundle_id: str) -> Dict[str, Any]:
|
|
35
|
+
import importlib
|
|
36
|
+
|
|
37
|
+
module_path, func_name = _BUNDLE_LOADERS[bundle_id]
|
|
38
|
+
module = importlib.import_module(module_path)
|
|
39
|
+
return getattr(module, func_name)()
|
|
40
|
+
|
|
41
|
+
def get_rules_for_bundles(self, bundles) -> Dict[str, Any]:
|
|
42
|
+
rules: Dict[str, Any] = {}
|
|
43
|
+
for b in bundles:
|
|
44
|
+
bval = b.value if hasattr(b, "value") else str(b)
|
|
45
|
+
if bval not in _BUNDLE_LOADERS:
|
|
46
|
+
continue
|
|
47
|
+
if is_paid_bundle(bval):
|
|
48
|
+
require_license(bval)
|
|
49
|
+
rules[bval] = self._load_bundle_rules(bval)
|
|
50
|
+
return rules
|
|
51
|
+
|
|
52
|
+
def _has_valid_license(self) -> bool:
|
|
53
|
+
return IrisLicense().check("gdpr").valid
|
|
54
|
+
|
|
55
|
+
def check_passport(
|
|
56
|
+
self,
|
|
57
|
+
passport,
|
|
58
|
+
framework: Optional[Union[str, List[str]]] = None,
|
|
59
|
+
) -> List[Violation]:
|
|
60
|
+
self.last_check_notes = []
|
|
61
|
+
violations: List[Violation] = []
|
|
62
|
+
frameworks = [framework] if isinstance(framework, str) else (framework or [])
|
|
63
|
+
active = frameworks or [t.value for t in (passport.compliance_tags or [])]
|
|
64
|
+
|
|
65
|
+
licensed = self._has_valid_license()
|
|
66
|
+
skipped_paid: List[str] = []
|
|
67
|
+
|
|
68
|
+
if "colorado-ai-act" in active or not active:
|
|
69
|
+
if passport.is_high_risk_ai and not passport.evidence_vault_id:
|
|
70
|
+
violations.append(
|
|
71
|
+
Violation(
|
|
72
|
+
rule_id="CO-002",
|
|
73
|
+
severity=Severity.CRITICAL,
|
|
74
|
+
message=(
|
|
75
|
+
f"Agent '{passport.name}' is high-risk but has no impact assessment."
|
|
76
|
+
),
|
|
77
|
+
compliance_refs=["colorado-ai-act:sb-24-205:impact-assessment"],
|
|
78
|
+
remediation="Run: iris compliance assess --agent " + passport.name,
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
if not passport.intent_ref:
|
|
82
|
+
violations.append(
|
|
83
|
+
Violation(
|
|
84
|
+
rule_id="CO-003",
|
|
85
|
+
severity=Severity.HIGH,
|
|
86
|
+
message=(
|
|
87
|
+
f"Agent '{passport.name}' has no transparency disclosure "
|
|
88
|
+
"(policy-intent.md)."
|
|
89
|
+
),
|
|
90
|
+
compliance_refs=["colorado-ai-act:sb-24-205:transparency"],
|
|
91
|
+
remediation="Run: iris policy compile --agent " + passport.name,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
for paid_bundle in ("gdpr", "hipaa", "soc2"):
|
|
96
|
+
if paid_bundle not in active:
|
|
97
|
+
continue
|
|
98
|
+
if not licensed:
|
|
99
|
+
skipped_paid.append(paid_bundle)
|
|
100
|
+
continue
|
|
101
|
+
import importlib
|
|
102
|
+
|
|
103
|
+
module_path, func_name = _PASSPORT_CHECKERS[paid_bundle]
|
|
104
|
+
module = importlib.import_module(module_path)
|
|
105
|
+
checker = getattr(module, func_name)
|
|
106
|
+
violations.extend(checker(passport))
|
|
107
|
+
|
|
108
|
+
if skipped_paid:
|
|
109
|
+
self.last_check_notes.append(paid_bundles_available_note())
|
|
110
|
+
|
|
111
|
+
return violations
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Codebase discovery — detect governed and ungoverned AI agent patterns."""
|
|
2
|
+
|
|
3
|
+
from iris_core.discovery.scanner import (
|
|
4
|
+
CodebaseScanner,
|
|
5
|
+
ScanResult,
|
|
6
|
+
ShadowAgent,
|
|
7
|
+
UngovernedFinding,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"CodebaseScanner",
|
|
12
|
+
"ScanResult",
|
|
13
|
+
"ShadowAgent",
|
|
14
|
+
"UngovernedFinding",
|
|
15
|
+
]
|