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.
@@ -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
+ ]