iris-security-sdk 0.2.17__tar.gz → 0.2.19__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.
Files changed (19) hide show
  1. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/PKG-INFO +7 -5
  2. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/README.md +1 -1
  3. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris/__init__.py +3 -3
  4. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris/scan.py +51 -6
  5. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris_security_sdk.egg-info/PKG-INFO +7 -5
  6. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris_security_sdk.egg-info/requires.txt +1 -1
  7. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/pyproject.toml +6 -4
  8. iris_security_sdk-0.2.19/tests/test_scan.py +141 -0
  9. iris_security_sdk-0.2.17/tests/test_scan.py +0 -51
  10. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris/_telemetry.py +0 -0
  11. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris/_telemetry_config.py +0 -0
  12. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris/hitl.py +0 -0
  13. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris_sdk/__init__.py +0 -0
  14. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris_sdk/scan.py +0 -0
  15. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris_security_sdk.egg-info/SOURCES.txt +0 -0
  16. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris_security_sdk.egg-info/dependency_links.txt +0 -0
  17. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/iris_security_sdk.egg-info/top_level.txt +0 -0
  18. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/setup.cfg +0 -0
  19. {iris_security_sdk-0.2.17 → iris_security_sdk-0.2.19}/tests/test_telemetry.py +0 -0
@@ -1,11 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-security-sdk
3
- Version: 0.2.17
3
+ Version: 0.2.19
4
4
  Summary: IRIS — AI Agent Governance SDK. Govern AI agents locally.
5
5
  Author-email: Gilbert Martin <gilbert@iris-security.io>
6
6
  License: Apache-2.0
7
- Project-URL: Homepage, https://gimartinb.github.io/iris-sdk/
8
- Project-URL: Repository, https://github.com/gimartinb/iris-sdk
7
+ Project-URL: Homepage, https://iris-security.github.io/iris-sdk/
8
+ Project-URL: Repository, https://github.com/IRIS-Security/iris-sdk
9
+ Project-URL: Documentation, https://iris-security.github.io/iris-sdk/getting-started.html
10
+ Project-URL: Issues, https://github.com/IRIS-Security/iris-sdk/issues
9
11
  Keywords: ai-governance,policy-as-code,cedar,compliance,llm,ai-agents,langchain,anthropic,openai,colorado-ai-act,eu-ai-act,hipaa,fedramp,mcp,claude,iris,guardrails,responsible-ai
10
12
  Classifier: Development Status :: 3 - Alpha
11
13
  Classifier: Intended Audience :: Developers
@@ -17,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.11
17
19
  Classifier: Programming Language :: Python :: 3.12
18
20
  Requires-Python: >=3.10
19
21
  Description-Content-Type: text/markdown
20
- Requires-Dist: iris-security-core>=0.1.17
22
+ Requires-Dist: iris-security-core>=0.1.18
21
23
  Requires-Dist: pyyaml>=6.0
22
24
  Requires-Dist: pydantic>=2.0
23
25
  Requires-Dist: click>=8.1
@@ -62,7 +64,7 @@ IRIS — AI Agent Governance SDK. Govern AI agents locally.
62
64
  Discover, register, and govern AI agents with Cedar policy evaluation,
63
65
  compliance bundles, and local-first governance.
64
66
 
65
- Part of the [IRIS SDK](https://github.com/gimartinb/iris-sdk).
67
+ Part of the [IRIS SDK](https://github.com/IRIS-Security/iris-sdk).
66
68
 
67
69
  ## Install
68
70
 
@@ -5,7 +5,7 @@ IRIS — AI Agent Governance SDK. Govern AI agents locally.
5
5
  Discover, register, and govern AI agents with Cedar policy evaluation,
6
6
  compliance bundles, and local-first governance.
7
7
 
8
- Part of the [IRIS SDK](https://github.com/gimartinb/iris-sdk).
8
+ Part of the [IRIS SDK](https://github.com/IRIS-Security/iris-sdk).
9
9
 
10
10
  ## Install
11
11
 
@@ -47,9 +47,9 @@ from iris_core.evidence.vault import EvidenceVault
47
47
  from iris_core.cost.tracker import CostSummary, CostTracker, CostEntry
48
48
  from iris_core.cost.pricing import PricingRegistry
49
49
 
50
- __version__ = "0.2.17"
50
+ __version__ = "0.2.19"
51
51
 
52
- AARM_CONFORMANCE = "Core" # R1-R6 satisfied
52
+ AARM_ALIGNMENT = "designed-toward" # AARM R1–R9 partial; not Core conformant
53
53
  AARM_VERSION = "1.0"
54
54
  AIUC1_ALIGNMENT = "Q1-2026"
55
55
  __all__ = [
@@ -78,7 +78,7 @@ __all__ = [
78
78
  "CostSummary",
79
79
  "CostEntry",
80
80
  "PricingRegistry",
81
- "AARM_CONFORMANCE",
81
+ "AARM_ALIGNMENT",
82
82
  "AARM_VERSION",
83
83
  "AIUC1_ALIGNMENT",
84
84
  "__version__",
@@ -57,27 +57,43 @@ _MODEL_PROVIDER_PREFIXES: list[tuple[re.Pattern[str], str]] = [
57
57
  ]
58
58
 
59
59
  _DATA_CATEGORY_PATTERNS = {
60
+ # Note: regulation/framework names (hipaa, pci, gdpr, ...) are deliberately
61
+ # excluded here. A compliance tool's own source legitimately talks *about*
62
+ # those frameworks (rule engines, questionnaire copy, requirement labels)
63
+ # without ever handling the regulated data itself, so a bare framework
64
+ # name is not evidence of a data category — only concrete data-field
65
+ # vocabulary is. Likewise "passport" alone is excluded: this codebase's
66
+ # own AgentPassport/passport.yaml artifact naming collides with the word
67
+ # far more often than it means travel-document PII.
60
68
  "pii": re.compile(
61
- r"\b(ssn|social_security|date_of_birth|dob|passport|email_address|phone_number|"
62
- r"first_name|last_name|address_line|national_id)\b",
69
+ r"\b(ssn|social_security|date_of_birth|dob|passport_number|passport_no|"
70
+ r"email_address|phone_number|first_name|last_name|address_line|national_id)\b",
63
71
  re.I,
64
72
  ),
65
73
  "phi": re.compile(
66
- r"\b(patient_id|medical_record|diagnosis|icd_?10|hipaa|health_record|"
74
+ r"\b(patient_id|medical_record|diagnosis|icd_?10|health_record|"
67
75
  r"prescription|mrn|protected_health)\b",
68
76
  re.I,
69
77
  ),
70
78
  "financial": re.compile(
71
- r"\b(credit_card|card_number|bank_account|routing_number|iban|pci|"
79
+ r"\b(credit_card|card_number|bank_account|routing_number|iban|"
72
80
  r"account_balance|transaction_amount)\b",
73
81
  re.I,
74
82
  ),
75
83
  "biometric": re.compile(
76
- r"\b(fingerprint|face_id|retina_scan|voice_print|biometric)\b",
84
+ r"\b(fingerprint|face_id|retina_scan|voice_print)\b",
77
85
  re.I,
78
86
  ),
79
87
  }
80
88
 
89
+ # Adjacency required (after removing whitespace/quote noise) for a data-field
90
+ # term to count as source code actually *handling* that field, rather than
91
+ # just naming it in a docstring, questionnaire string, or keyword list — an
92
+ # assignment, a type annotation, or a dict/JSON key all qualify; a bare
93
+ # occurrence inside a comma-separated keyword list or prose sentence does
94
+ # not.
95
+ _CODE_ASSIGNMENT_CONTEXT = re.compile(r"\A\s{0,3}[\'\"]?\s{0,3}[:=]")
96
+
81
97
  _REQUIREMENTS_FILES = (
82
98
  "requirements.txt",
83
99
  "requirements-dev.txt",
@@ -100,6 +116,20 @@ def _iter_source_files(root: Path) -> list[Path]:
100
116
  return files
101
117
 
102
118
 
119
+ _NON_WORKLOAD_DIR_NAMES = {"tests", "test", "demo", "demos", "examples", "example", "samples", "sample"}
120
+
121
+
122
+ def _is_test_file(path: Path) -> bool:
123
+ """Test suites and demo/example trees routinely embed sample field names
124
+ and fixtures (fake ssns, illustrative "ungoverned agent" code, etc.) to
125
+ exercise detection logic or teach a concept — that sample data isn't
126
+ evidence this workload itself handles it."""
127
+ if any(part in _NON_WORKLOAD_DIR_NAMES for part in path.parts):
128
+ return True
129
+ stem = path.stem
130
+ return stem.startswith("test_") or stem.endswith("_test")
131
+
132
+
103
133
  def _scan_imports(text: str) -> tuple[set[str], set[str]]:
104
134
  providers: set[str] = set()
105
135
  frameworks: set[str] = set()
@@ -132,6 +162,20 @@ def _scan_data_categories(text: str) -> set[str]:
132
162
  return categories
133
163
 
134
164
 
165
+ def _scan_data_categories_in_source(text: str) -> set[str]:
166
+ """Like `_scan_data_categories`, but for source files: only count a term
167
+ that sits where a real field would (assignment, annotation, or dict/JSON
168
+ key), not one that merely appears in prose, a docstring, or a keyword
169
+ list describing the vocabulary."""
170
+ categories: set[str] = set()
171
+ for category, pattern in _DATA_CATEGORY_PATTERNS.items():
172
+ for match in pattern.finditer(text):
173
+ if _CODE_ASSIGNMENT_CONTEXT.match(text[match.end() : match.end() + 6]):
174
+ categories.add(category)
175
+ break
176
+ return categories
177
+
178
+
135
179
  def infer_provider_from_model(model: str) -> str | None:
136
180
  """Infer provider slug from a model identifier (shared by observability adapters)."""
137
181
  if not model:
@@ -196,7 +240,8 @@ def detect_workload(path: str = ".") -> dict[str, Any]:
196
240
  providers |= p
197
241
  frameworks |= f
198
242
  models |= _scan_models(text)
199
- data_categories |= _scan_data_categories(text)
243
+ if not _is_test_file(file_path):
244
+ data_categories |= _scan_data_categories_in_source(text)
200
245
 
201
246
  agent_hints = sum(1 for _ in root.rglob("passport.yaml"))
202
247
  autonomy = "assistive"
@@ -1,11 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-security-sdk
3
- Version: 0.2.17
3
+ Version: 0.2.19
4
4
  Summary: IRIS — AI Agent Governance SDK. Govern AI agents locally.
5
5
  Author-email: Gilbert Martin <gilbert@iris-security.io>
6
6
  License: Apache-2.0
7
- Project-URL: Homepage, https://gimartinb.github.io/iris-sdk/
8
- Project-URL: Repository, https://github.com/gimartinb/iris-sdk
7
+ Project-URL: Homepage, https://iris-security.github.io/iris-sdk/
8
+ Project-URL: Repository, https://github.com/IRIS-Security/iris-sdk
9
+ Project-URL: Documentation, https://iris-security.github.io/iris-sdk/getting-started.html
10
+ Project-URL: Issues, https://github.com/IRIS-Security/iris-sdk/issues
9
11
  Keywords: ai-governance,policy-as-code,cedar,compliance,llm,ai-agents,langchain,anthropic,openai,colorado-ai-act,eu-ai-act,hipaa,fedramp,mcp,claude,iris,guardrails,responsible-ai
10
12
  Classifier: Development Status :: 3 - Alpha
11
13
  Classifier: Intended Audience :: Developers
@@ -17,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.11
17
19
  Classifier: Programming Language :: Python :: 3.12
18
20
  Requires-Python: >=3.10
19
21
  Description-Content-Type: text/markdown
20
- Requires-Dist: iris-security-core>=0.1.17
22
+ Requires-Dist: iris-security-core>=0.1.18
21
23
  Requires-Dist: pyyaml>=6.0
22
24
  Requires-Dist: pydantic>=2.0
23
25
  Requires-Dist: click>=8.1
@@ -62,7 +64,7 @@ IRIS — AI Agent Governance SDK. Govern AI agents locally.
62
64
  Discover, register, and govern AI agents with Cedar policy evaluation,
63
65
  compliance bundles, and local-first governance.
64
66
 
65
- Part of the [IRIS SDK](https://github.com/gimartinb/iris-sdk).
67
+ Part of the [IRIS SDK](https://github.com/IRIS-Security/iris-sdk).
66
68
 
67
69
  ## Install
68
70
 
@@ -1,4 +1,4 @@
1
- iris-security-core>=0.1.17
1
+ iris-security-core>=0.1.18
2
2
  pyyaml>=6.0
3
3
  pydantic>=2.0
4
4
  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.2.17"
7
+ version = "0.2.19"
8
8
  description = "IRIS — AI Agent Governance SDK. Govern AI agents locally."
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Gilbert Martin", email = "gilbert@iris-security.io" }]
@@ -27,7 +27,7 @@ classifiers = [
27
27
  "Programming Language :: Python :: 3.12",
28
28
  ]
29
29
  dependencies = [
30
- "iris-security-core>=0.1.17",
30
+ "iris-security-core>=0.1.18",
31
31
  "pyyaml>=6.0",
32
32
  "pydantic>=2.0",
33
33
  "click>=8.1",
@@ -63,8 +63,10 @@ dev = [
63
63
  ]
64
64
 
65
65
  [project.urls]
66
- Homepage = "https://gimartinb.github.io/iris-sdk/"
67
- Repository = "https://github.com/gimartinb/iris-sdk"
66
+ Homepage = "https://iris-security.github.io/iris-sdk/"
67
+ Repository = "https://github.com/IRIS-Security/iris-sdk"
68
+ Documentation = "https://iris-security.github.io/iris-sdk/getting-started.html"
69
+ Issues = "https://github.com/IRIS-Security/iris-sdk/issues"
68
70
 
69
71
  [tool.setuptools.packages.find]
70
72
  where = ["."]
@@ -0,0 +1,141 @@
1
+ # Copyright 2024-2025 Gilbert Martin / IRIS Security, Inc.
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from unittest.mock import patch
7
+
8
+ import pytest
9
+
10
+ from iris.scan import detect_workload
11
+
12
+
13
+ @pytest.fixture
14
+ def sample_project(tmp_path: Path) -> Path:
15
+ (tmp_path / "requirements.txt").write_text(
16
+ "openai>=1.0\nlangchain>=0.2\n",
17
+ encoding="utf-8",
18
+ )
19
+ app = tmp_path / "app.py"
20
+ app.write_text(
21
+ """
22
+ import openai
23
+ from langchain_openai import ChatOpenAI
24
+
25
+ model = ChatOpenAI(model="gpt-4o")
26
+ client = openai.OpenAI()
27
+
28
+ class UserSchema:
29
+ ssn = "social_security"
30
+ email_address = "test@example.com"
31
+ """,
32
+ encoding="utf-8",
33
+ )
34
+ return tmp_path
35
+
36
+
37
+ def test_detect_workload_finds_openai_and_langchain(sample_project: Path):
38
+ profile = detect_workload(str(sample_project))
39
+ assert "openai" in profile["providers"]
40
+ assert "langchain" in profile["frameworks"]
41
+ assert any("gpt" in m for m in profile["models"])
42
+ assert "pii" in profile["data_categories"]
43
+
44
+
45
+ def test_detect_workload_offline_no_network(sample_project: Path):
46
+ def fail_socket(*args, **kwargs):
47
+ raise AssertionError("Network call attempted during offline scan")
48
+
49
+ with patch("socket.socket", side_effect=fail_socket):
50
+ profile = detect_workload(str(sample_project))
51
+ assert profile["source"] == "sdk_scan"
52
+
53
+
54
+ # Regression coverage for the dogfood false positive: detect_workload() used
55
+ # to flag data_categories=[biometric, financial, phi, pii] when scanning a
56
+ # governance/compliance tool's own source, because that source legitimately
57
+ # talks *about* those categories (DataClassification enum members, framework
58
+ # names like HIPAA/PCI, questionnaire copy, AgentPassport/passport.yaml
59
+ # vocabulary) without ever handling real data of that kind. apps/iris-cli/ in
60
+ # this repo is exactly that kind of codebase, so scanning it is the natural
61
+ # regression fixture.
62
+ _REPO_ROOT = Path(__file__).resolve().parents[3]
63
+ _IRIS_CLI_ROOT = _REPO_ROOT / "apps" / "iris-cli"
64
+
65
+
66
+ @pytest.mark.skipif(not _IRIS_CLI_ROOT.is_dir(), reason="apps/iris-cli not present in this checkout")
67
+ def test_detect_workload_does_not_flag_own_compliance_vocabulary():
68
+ profile = detect_workload(str(_IRIS_CLI_ROOT))
69
+ assert profile["data_categories"] == []
70
+
71
+
72
+ @pytest.mark.skipif(not _IRIS_CLI_ROOT.is_dir(), reason="apps/iris-cli not present in this checkout")
73
+ def test_detect_workload_does_not_flag_iris_python_own_source():
74
+ iris_python_root = Path(__file__).resolve().parents[1]
75
+ profile = detect_workload(str(iris_python_root))
76
+ assert profile["data_categories"] == []
77
+
78
+
79
+ def test_detect_workload_ignores_vocabulary_in_keyword_lists_and_prose(tmp_path: Path):
80
+ (tmp_path / "linter.py").write_text(
81
+ '''
82
+ """Detects high-risk domains in someone else's code."""
83
+
84
+ high_risk_keywords = ["loan", "credit", "insurance", "medical", "diagnosis"]
85
+
86
+ QUESTIONS = [
87
+ "Yes -- biometric or sensitive data",
88
+ "HIPAA (healthcare)",
89
+ "PCI DSS (payment cards)",
90
+ ]
91
+
92
+ def load_passport(agent):
93
+ passport = (agent / "passport.yaml").read_text()
94
+ return passport
95
+ ''',
96
+ encoding="utf-8",
97
+ )
98
+ profile = detect_workload(str(tmp_path))
99
+ assert profile["data_categories"] == []
100
+
101
+
102
+ def test_detect_workload_still_flags_real_field_assignment(tmp_path: Path):
103
+ (tmp_path / "models.py").write_text(
104
+ '''
105
+ class Patient:
106
+ patient_id: str
107
+ diagnosis = ""
108
+
109
+ record = {"credit_card": "4111111111111111", "fingerprint": template}
110
+ ''',
111
+ encoding="utf-8",
112
+ )
113
+ profile = detect_workload(str(tmp_path))
114
+ assert set(profile["data_categories"]) == {"phi", "financial", "biometric"}
115
+
116
+
117
+ def test_detect_workload_skips_data_category_matches_in_test_files(tmp_path: Path):
118
+ tests_dir = tmp_path / "tests"
119
+ tests_dir.mkdir()
120
+ (tests_dir / "test_fixture.py").write_text(
121
+ 'ssn = "123-45-6789"\ncredit_card = "4111111111111111"\n',
122
+ encoding="utf-8",
123
+ )
124
+ profile = detect_workload(str(tmp_path))
125
+ assert profile["data_categories"] == []
126
+
127
+
128
+ def test_detect_workload_skips_data_category_matches_in_demo_files(tmp_path: Path):
129
+ """Regression for the public iris-sdk repo's bundled demo/ tree: it
130
+ intentionally simulates an "ungoverned agent" with realistic-looking
131
+ field names to teach a sales demo — that's not evidence the SDK itself
132
+ handles real customer PII/PHI."""
133
+ demo_dir = tmp_path / "demo" / "customers" / "meridian_health" / "agents"
134
+ demo_dir.mkdir(parents=True)
135
+ (demo_dir / "patient_summarizer.py").write_text(
136
+ 'def summarize_patient_record(patient_id: str, record: dict) -> str:\n'
137
+ ' return record.get("ssn")\n',
138
+ encoding="utf-8",
139
+ )
140
+ profile = detect_workload(str(tmp_path))
141
+ assert profile["data_categories"] == []
@@ -1,51 +0,0 @@
1
- # Copyright 2024-2025 Gilbert Martin / IRIS Security, Inc.
2
-
3
- from __future__ import annotations
4
-
5
- from pathlib import Path
6
- from unittest.mock import patch
7
-
8
- import pytest
9
-
10
- from iris.scan import detect_workload
11
-
12
-
13
- @pytest.fixture
14
- def sample_project(tmp_path: Path) -> Path:
15
- (tmp_path / "requirements.txt").write_text(
16
- "openai>=1.0\nlangchain>=0.2\n",
17
- encoding="utf-8",
18
- )
19
- app = tmp_path / "app.py"
20
- app.write_text(
21
- """
22
- import openai
23
- from langchain_openai import ChatOpenAI
24
-
25
- model = ChatOpenAI(model="gpt-4o")
26
- client = openai.OpenAI()
27
-
28
- class UserSchema:
29
- ssn = "social_security"
30
- email_address = "test@example.com"
31
- """,
32
- encoding="utf-8",
33
- )
34
- return tmp_path
35
-
36
-
37
- def test_detect_workload_finds_openai_and_langchain(sample_project: Path):
38
- profile = detect_workload(str(sample_project))
39
- assert "openai" in profile["providers"]
40
- assert "langchain" in profile["frameworks"]
41
- assert any("gpt" in m for m in profile["models"])
42
- assert "pii" in profile["data_categories"]
43
-
44
-
45
- def test_detect_workload_offline_no_network(sample_project: Path):
46
- def fail_socket(*args, **kwargs):
47
- raise AssertionError("Network call attempted during offline scan")
48
-
49
- with patch("socket.socket", side_effect=fail_socket):
50
- profile = detect_workload(str(sample_project))
51
- assert profile["source"] == "sdk_scan"