iris-security-cli 0.2.17__tar.gz → 0.2.18__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 (76) hide show
  1. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/PKG-INFO +8 -6
  2. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/README.md +1 -1
  3. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/audit_log.py +10 -5
  4. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/certify.py +4 -2
  5. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/compliance_scan_cmd.py +25 -5
  6. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/cost.py +2 -0
  7. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/drift.py +1 -0
  8. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/evidence.py +6 -1
  9. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/explain.py +1 -1
  10. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/hitl.py +68 -3
  11. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/main.py +4 -3
  12. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/mcp_server.py +86 -0
  13. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/quickstart.py +2 -2
  14. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/scm.py +6 -1
  15. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/users.py +8 -2
  16. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_security_cli.egg-info/PKG-INFO +8 -6
  17. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_security_cli.egg-info/SOURCES.txt +3 -1
  18. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_security_cli.egg-info/requires.txt +2 -2
  19. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/pyproject.toml +7 -5
  20. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_audit_log.py +8 -4
  21. iris_security_cli-0.2.18/tests/test_cli_free_tier_boundary.py +256 -0
  22. iris_security_cli-0.2.18/tests/test_compliance_scan_fail_on.py +113 -0
  23. iris_security_cli-0.2.18/tests/test_docs_pages_check.py +92 -0
  24. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_entitlements_display.py +4 -4
  25. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_evidence.py +44 -45
  26. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_evidence_query.py +5 -3
  27. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_framework_test.py +3 -1
  28. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_hitl_cli.py +54 -0
  29. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_mcp_model_governance.py +3 -2
  30. iris_security_cli-0.2.18/tests/vault_fixtures.py +14 -0
  31. iris_security_cli-0.2.17/tests/test_compliance_scan_fail_on.py +0 -56
  32. iris_security_cli-0.2.17/tests/test_docs_pages_check.py +0 -45
  33. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/__init__.py +0 -0
  34. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/action_plan.py +0 -0
  35. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/assess.py +0 -0
  36. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/cedar_parser.py +0 -0
  37. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/compiler_config.py +0 -0
  38. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/compliance_check_cmd.py +0 -0
  39. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/compliance_fix.py +0 -0
  40. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/declare.py +0 -0
  41. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/delegation.py +0 -0
  42. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/discover.py +0 -0
  43. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/dlp_cmd.py +0 -0
  44. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/enforce.py +0 -0
  45. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/entitlements_cmd.py +0 -0
  46. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/framework_suggest.py +0 -0
  47. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/framework_test.py +0 -0
  48. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/license_cmd.py +0 -0
  49. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/list_cmd.py +0 -0
  50. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/models_cmd.py +0 -0
  51. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/onboarding_report.py +0 -0
  52. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/org_policy.py +0 -0
  53. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/policy_cache.py +0 -0
  54. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/policy_commit.py +0 -0
  55. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/policy_diff.py +0 -0
  56. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/policy_status.py +0 -0
  57. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/preview.py +0 -0
  58. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/redteam.py +0 -0
  59. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/regulatory.py +0 -0
  60. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/scan_govern.py +0 -0
  61. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/scan_report.py +0 -0
  62. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/sentinel.py +0 -0
  63. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/status_cmd.py +0 -0
  64. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/vault.py +0 -0
  65. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/watch.py +0 -0
  66. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_cli/witness.py +0 -0
  67. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_security_cli.egg-info/dependency_links.txt +0 -0
  68. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_security_cli.egg-info/entry_points.txt +0 -0
  69. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/iris_security_cli.egg-info/top_level.txt +0 -0
  70. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/setup.cfg +0 -0
  71. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_evidence_export.py +0 -0
  72. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_framework_suggest.py +0 -0
  73. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_list_cmd.py +0 -0
  74. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_policy_diff.py +0 -0
  75. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_quickstart.py +0 -0
  76. {iris_security_cli-0.2.17 → iris_security_cli-0.2.18}/tests/test_vocabulary.py +0 -0
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-security-cli
3
- Version: 0.2.17
3
+ Version: 0.2.18
4
4
  Summary: IRIS CLI — declare, compile, preview, enforce, witness, certify, sentinel
5
5
  License: Apache-2.0
6
- Project-URL: Homepage, https://github.com/gimartinb/iris-sdk
7
- Project-URL: Repository, https://github.com/gimartinb/iris-sdk
6
+ Project-URL: Homepage, https://iris-security.github.io/iris-sdk/
7
+ Project-URL: Repository, https://github.com/IRIS-Security/iris-sdk
8
+ Project-URL: Documentation, https://iris-security.github.io/iris-sdk/cli-reference.html
9
+ Project-URL: Issues, https://github.com/IRIS-Security/iris-sdk/issues
8
10
  Classifier: Development Status :: 3 - Alpha
9
11
  Classifier: Intended Audience :: Developers
10
12
  Classifier: Topic :: Security
@@ -15,8 +17,8 @@ Classifier: Programming Language :: Python :: 3.11
15
17
  Classifier: Programming Language :: Python :: 3.12
16
18
  Requires-Python: >=3.10
17
19
  Description-Content-Type: text/markdown
18
- Requires-Dist: iris-security-core>=0.1.17
19
- Requires-Dist: iris-security-sdk>=0.2.17
20
+ Requires-Dist: iris-security-core>=0.1.18
21
+ Requires-Dist: iris-security-sdk>=0.2.18
20
22
  Requires-Dist: click>=8.1
21
23
  Requires-Dist: rich>=13.0
22
24
  Requires-Dist: pyyaml>=6.0
@@ -29,7 +31,7 @@ IRIS CLI — `iris scan`, `iris register`, `iris policy`, `iris compliance`.
29
31
 
30
32
  Command-line tools for AI agent governance and Colorado AI Act compliance.
31
33
 
32
- Part of the [IRIS SDK](https://github.com/gimartinb/iris-sdk).
34
+ Part of the [IRIS SDK](https://github.com/IRIS-Security/iris-sdk).
33
35
 
34
36
  ## Install
35
37
 
@@ -4,7 +4,7 @@ IRIS CLI — `iris scan`, `iris register`, `iris policy`, `iris compliance`.
4
4
 
5
5
  Command-line tools for AI agent governance and Colorado AI Act compliance.
6
6
 
7
- Part of the [IRIS SDK](https://github.com/gimartinb/iris-sdk).
7
+ Part of the [IRIS SDK](https://github.com/IRIS-Security/iris-sdk).
8
8
 
9
9
  ## Install
10
10
 
@@ -55,6 +55,11 @@ def _event_policy(event: dict) -> str:
55
55
  return "—"
56
56
 
57
57
 
58
+ def _event_r5_satisfied(event: dict) -> bool:
59
+ """R5 tamper-evidence applies only to HMAC-signed vault events (Evidence Vault v2)."""
60
+ return bool(event.get("signature"))
61
+
62
+
58
63
  def _event_hash(event: dict) -> str:
59
64
  if event.get("hash"):
60
65
  return str(event["hash"])
@@ -150,7 +155,7 @@ def _format_splunk(event: dict) -> dict:
150
155
  "iris_regulation": _event_regulation(event),
151
156
  "iris_event_id": event.get("event_id", ""),
152
157
  "iris_hash": _event_hash(event),
153
- "aarm_r5": True,
158
+ "aarm_r5": _event_r5_satisfied(event),
154
159
  "aiuc1_control": _aiuc1_control(event),
155
160
  },
156
161
  }
@@ -195,7 +200,7 @@ def _format_elastic(event: dict) -> dict:
195
200
  "risk.score": _event_risk_score(event),
196
201
  "labels": {
197
202
  "iris_regulation": _event_regulation(event),
198
- "aarm_r5": "true",
203
+ "aarm_r5": "true" if _event_r5_satisfied(event) else "false",
199
204
  },
200
205
  }
201
206
 
@@ -247,7 +252,7 @@ def _format_otel_envelope(events: List[dict]) -> dict:
247
252
  _otel_attribute("service.name", "iris-cli"),
248
253
  _otel_attribute("service.version", IRIS_VERSION),
249
254
  _otel_attribute("iris.vendor", "IRIS Security, Inc."),
250
- _otel_attribute("aarm.conformance", "Core"),
255
+ _otel_attribute("aarm.alignment", "designed-toward"),
251
256
  ],
252
257
  },
253
258
  "scopeSpans": [
@@ -409,7 +414,7 @@ def _print_export_summary(
409
414
  "Formats: Splunk HEC · Datadog · Elastic ECS · OTel OTLP"
410
415
  )
411
416
  err_console.print(
412
- "AARM R8 conformant · https://iris-security.io"
417
+ "OTel export · https://iris-security.io"
413
418
  )
414
419
 
415
420
 
@@ -524,7 +529,7 @@ def audit_log_export(
524
529
  Plug IRIS evidence directly into your existing security stack.
525
530
  No IRIS account required — reads from local Evidence Vault.
526
531
 
527
- AARM R8 conformant: standard telemetry export.
532
+ Exports governance telemetry in standard SIEM formats (including OTel OTLP).
528
533
 
529
534
  Examples:
530
535
  iris audit-log export --format splunk --output events.json
@@ -404,11 +404,13 @@ def certify_cmd(
404
404
 
405
405
  loader = DynamicBundleLoader(pinned_version=registry_version)
406
406
 
407
+ machine_output = output_format in ("json", "markdown", "aiuc1-export")
407
408
  if not offline and not registry_version:
408
409
  if not loader.is_cache_fresh():
409
- console.print("[dim]Checking for regulatory updates...[/dim]")
410
+ if not machine_output:
411
+ console.print("[dim]Checking for regulatory updates...[/dim]")
410
412
  updated = loader.refresh_registry()
411
- if updated:
413
+ if updated and not machine_output:
412
414
  console.print("[green]✓ Regulatory intelligence updated[/green]")
413
415
  console.print("[dim] Run: iris regulatory check for change summary[/dim]")
414
416
 
@@ -17,8 +17,8 @@ from rich.table import Table
17
17
 
18
18
  from iris.scan import detect_workload
19
19
  from iris_core.compliance.workload_eval import (
20
- applicable_frameworks,
21
20
  evaluate_workload_profile,
21
+ framework_coverage,
22
22
  top_recommended_actions,
23
23
  )
24
24
 
@@ -34,7 +34,8 @@ def _exit_on_fail_threshold(obligations: list[dict], fail_on: str) -> None:
34
34
  blocking = [
35
35
  obligation
36
36
  for obligation in obligations
37
- if _SEVERITY_RANK.get(obligation.get("severity", "recommended"), 9) <= threshold
37
+ if not obligation.get("informational", False)
38
+ and _SEVERITY_RANK.get(obligation.get("severity", "recommended"), 9) <= threshold
38
39
  ]
39
40
  if blocking:
40
41
  sys.exit(1)
@@ -119,13 +120,32 @@ def _print_profile(profile: dict) -> None:
119
120
 
120
121
 
121
122
  def _print_frameworks(profile: dict) -> None:
123
+ coverage = framework_coverage(profile)
122
124
  table = Table(title="Applicable frameworks", show_header=True, header_style="bold")
123
125
  table.add_column("Framework")
124
- table.add_column("Triggered by")
125
- for fw, reasons in applicable_frameworks(profile):
126
- table.add_row(fw, ", ".join(reasons))
126
+ table.add_column("Why this applies to you")
127
+ table.add_column("Depth")
128
+ for entry in coverage:
129
+ depth = (
130
+ f"[yellow]mapped — thin[/yellow]"
131
+ if entry["thin"]
132
+ else f"{entry['rule_count']} rules"
133
+ )
134
+ table.add_row(entry["bundle_id"], entry["why"], depth)
127
135
  console.print(table)
128
136
 
137
+ thin = [entry for entry in coverage if entry["thin"]]
138
+ if thin:
139
+ console.print(
140
+ "[dim]Thin coverage means the registry has this framework mapped but "
141
+ "not yet deeply evaluated here — run one of the following for a real "
142
+ "control-by-control check:[/dim]"
143
+ )
144
+ for entry in thin:
145
+ console.print(
146
+ f" [dim]•[/dim] iris compliance check --framework {entry['bundle_id']}"
147
+ )
148
+
129
149
 
130
150
  def _print_actions(obligations: list[dict]) -> None:
131
151
  actions = top_recommended_actions(obligations, limit=5)
@@ -18,6 +18,7 @@ from rich.table import Table
18
18
 
19
19
  from iris_core.cost.pricing import DEFAULT_PRICING, overrides_path, PricingRegistry
20
20
  from iris_core.cost.tracker import CostSummary, CostTracker, discover_agent_trackers
21
+ from iris_core.entitlements import Entitlements, Feature
21
22
 
22
23
  console = Console()
23
24
 
@@ -298,6 +299,7 @@ def cost_report(agent: Optional[str], days: int, output_format: str, since: Opti
298
299
  @click.option("--since", default=None, help="ISO date string for period start")
299
300
  def cost_summary(days: int, since: Optional[str]) -> None:
300
301
  """CFO report — cost across all agents sorted by spend."""
302
+ Entitlements().require(Feature.COST_ORG_SUMMARY, context="org-wide cost summary")
301
303
  since_iso = _since_from_date(since, days)
302
304
  trackers = discover_agent_trackers()
303
305
  if not trackers:
@@ -254,6 +254,7 @@ def drift_alert_config(slack_webhook: Optional[str], email: Optional[str], webho
254
254
  _require_pro_license("Email drift alerts", Feature.DRIFT_EMAIL_ALERT)
255
255
  config["email"] = email
256
256
  if webhook:
257
+ _require_pro_license("Webhook drift alerts", Feature.DRIFT_WEBHOOK_ALERT)
257
258
  config["webhook"] = webhook
258
259
 
259
260
  if not any([slack_webhook, email, webhook]):
@@ -981,7 +981,8 @@ def evidence_query(
981
981
  to exactly the events that need attention.
982
982
 
983
983
  This is the CLI equivalent of Evidence Vault Signal mode.
984
- AARM R5 conformant all entries are tamper-evident.
984
+ Local vault events are unsigned JSONL by default; Evidence Vault v2 adds
985
+ HMAC signing and hash chaining for tamper-evident receipts.
985
986
 
986
987
  Examples:
987
988
  iris evidence query --decision deny
@@ -1150,6 +1151,10 @@ def evidence_export(agent, output_path, output_format, governance_dir, vault_dir
1150
1151
  return
1151
1152
 
1152
1153
  if output_format == "oscal":
1154
+ Entitlements().require(
1155
+ Feature.EVIDENCE_EXPORT_AUDITOR,
1156
+ context="evidence export --format oscal (auditor deliverable)",
1157
+ )
1153
1158
  vault = _open_vault(agent, vault_dir)
1154
1159
  vault_data = vault.export_vault()
1155
1160
  oscal_payload = {
@@ -30,7 +30,7 @@ Your existing tests will pass without modification.
30
30
 
31
31
  To verify: run your test suite after adding IRIS.
32
32
  If any test fails, report it as a bug at:
33
- github.com/gimartinb/iris-sdk/issues"""
33
+ github.com/IRIS-Security/iris-sdk/issues"""
34
34
 
35
35
  TECHNICAL_EXPLANATION = """\
36
36
  Proxy pattern (identical attribute forwarding):
@@ -214,6 +214,35 @@ def hitl_setup(agent: str):
214
214
  rules.append(HITLConditionRule(condition=condition, reason=reason, notify_channel=channel))
215
215
  config.condition_rules = rules
216
216
 
217
+ console.print(
218
+ "\nRisk tiers — gate the clearly-irreversible, auto-allow the routine.\n"
219
+ "Leave any of these blank to skip; an empty list never triggers HITL.\n"
220
+ )
221
+ if click.confirm("Require HITL for specific violation severities?", default=False):
222
+ levels = click.prompt(
223
+ "Severities (comma-separated, e.g. CRITICAL,HIGH)", default="CRITICAL"
224
+ )
225
+ config.required_for_risk_levels = [
226
+ lvl.strip().upper() for lvl in levels.split(",") if lvl.strip()
227
+ ]
228
+ if click.confirm(
229
+ "Require HITL for specific actions in staging/production (e.g. write, delete)?",
230
+ default=False,
231
+ ):
232
+ actions = click.prompt("Actions (comma-separated)", default="write,delete")
233
+ config.step_up_actions = [a.strip() for a in actions.split(",") if a.strip()]
234
+ if click.confirm(
235
+ "Require HITL for specific data classifications (e.g. phi, pii)?", default=False
236
+ ):
237
+ classes = click.prompt("Data classifications (comma-separated)", default="phi,pii")
238
+ config.sensitive_data_classifications = [
239
+ c.strip() for c in classes.split(",") if c.strip()
240
+ ]
241
+ config.step_up_on_intent_drift = click.confirm(
242
+ "Require HITL when an action drifts outside the agent's declared intent?",
243
+ default=False,
244
+ )
245
+
217
246
  if click.confirm("Configure Slack webhook URL (Pro)?", default=False):
218
247
  config.slack_webhook_url = click.prompt("Slack webhook URL", default="")
219
248
  if click.confirm("Configure email recipients (Pro)?", default=False):
@@ -223,7 +252,15 @@ def hitl_setup(agent: str):
223
252
  path = _save_hitl_config(agent, config)
224
253
  console.print(f"[green]✓[/green] HITL configured for {agent}")
225
254
  console.print(f"[green]✓[/green] {len(rules)} condition rule(s) declared")
226
- console.print("[green]✓[/green] HITL fires ONLY when a declared rule matches")
255
+ if config.required_for_risk_levels:
256
+ console.print(f"[green]✓[/green] Step-up on severities: {config.required_for_risk_levels}")
257
+ if config.step_up_actions:
258
+ console.print(f"[green]✓[/green] Step-up on actions: {config.step_up_actions}")
259
+ if config.sensitive_data_classifications:
260
+ console.print(f"[green]✓[/green] Step-up on data classifications: {config.sensitive_data_classifications}")
261
+ if config.step_up_on_intent_drift:
262
+ console.print("[green]✓[/green] Step-up on intent drift: enabled")
263
+ console.print("[green]✓[/green] HITL fires ONLY for what you declared above — everything else auto-allows")
227
264
  console.print(f"[green]✓[/green] Saved to {path}")
228
265
 
229
266
 
@@ -255,10 +292,38 @@ def hitl_rules(agent: str):
255
292
  console.print(f" Reason: {entry['reason']}")
256
293
  console.print(" Channel: All channels")
257
294
 
295
+ if config.required_for_risk_levels:
296
+ console.print(
297
+ f"\nWILL trigger HITL — violation severity in "
298
+ f"{config.required_for_risk_levels}:"
299
+ )
300
+ console.print(
301
+ "● Any policy violation at these severities steps up instead of "
302
+ "logging silently or auto-denying"
303
+ )
304
+
305
+ if config.step_up_actions:
306
+ console.print(f"\nWILL trigger HITL — declared step-up actions in staging/production:")
307
+ console.print(f"● {config.step_up_actions}")
308
+
309
+ if config.sensitive_data_classifications:
310
+ console.print(
311
+ f"\nWILL trigger HITL — sensitive data classifications:"
312
+ )
313
+ console.print(f"● {config.sensitive_data_classifications}")
314
+
315
+ if config.step_up_on_intent_drift:
316
+ console.print(
317
+ "\nWILL trigger HITL — action drifts outside the agent's declared "
318
+ "intent (semantic distance > 0.7)"
319
+ )
320
+
258
321
  console.print("\nWILL NOT trigger HITL — everything else:")
259
322
  console.print("✗ Calls with no matching condition rule → automatic")
260
- console.print("✗ Risk level alone → never triggers HITL")
261
- console.print("✗ Data classification alone → never triggers HITL")
323
+ if not config.required_for_risk_levels:
324
+ console.print("✗ Risk level alone → never triggers HITL (declare required_for_risk_levels to change this)")
325
+ if not config.sensitive_data_classifications:
326
+ console.print("✗ Data classification alone → never triggers HITL (declare sensitive_data_classifications to change this)")
262
327
  console.print("\nRun: iris hitl test --agent {agent} to verify.")
263
328
 
264
329
 
@@ -50,10 +50,11 @@ class IrisCLI(click.Group):
50
50
 
51
51
  @click.group(cls=IrisCLI)
52
52
  @click.version_option(
53
- version="0.2.17",
53
+ version="0.2.18",
54
54
  prog_name="iris",
55
- message="%(prog)s %(version)s · AARM Core conformant (R1–R6) · "
56
- "AIUC-1 Q1 2026 aligned · https://iris-security.io",
55
+ message="%(prog)s %(version)s · Evaluates agent actions through an in-process "
56
+ "Cedar engine before adapter-wrapped calls proceed; records decisions "
57
+ "to a local Evidence Vault.",
57
58
  )
58
59
  def cli():
59
60
  """
@@ -19,6 +19,7 @@ Setup:
19
19
 
20
20
  import json
21
21
  import asyncio
22
+ import re
22
23
  from pathlib import Path
23
24
  from typing import Any
24
25
  from datetime import datetime
@@ -142,6 +143,75 @@ MCP_TOOLS = [
142
143
 
143
144
  # ── Tool handlers ─────────────────────────────────────────────────────────────
144
145
 
146
+ _MODEL_ID_PATTERN = re.compile(r"""model\s*=\s*["']([^"']+)["']""")
147
+
148
+
149
+ def _governance_root(workspace: Path) -> Path | None:
150
+ candidate = workspace / "governance"
151
+ return candidate if candidate.exists() else None
152
+
153
+
154
+ def _model_governance_violations(code: str, workspace: Path) -> list[dict]:
155
+ from iris_core.engine.model_governance import check_model_governance
156
+ from iris_core.models.context import EvaluationContext
157
+ from iris_core.models.directives import DirectiveRegistry
158
+ from iris_core.models.model_registry import ModelRegistry
159
+ from iris_core.models.passport import AgentPassport, Environment
160
+
161
+ model_ids = _MODEL_ID_PATTERN.findall(code)
162
+ if not model_ids:
163
+ return []
164
+
165
+ gov_root = _governance_root(workspace)
166
+ registry = ModelRegistry.load(gov_root)
167
+ directives = DirectiveRegistry.load(gov_root)
168
+ passport = AgentPassport(
169
+ name="mcp-scan",
170
+ owner="unknown@local",
171
+ allowed_model_tiers=["standard", "frontier", "frontier-restricted"],
172
+ )
173
+ violations: list[dict] = []
174
+ for model_id in model_ids:
175
+ ctx = EvaluationContext(
176
+ agent_id="mcp-scan",
177
+ action="call",
178
+ resource="llm-api",
179
+ resource_type="model",
180
+ environment=Environment.DEV,
181
+ model_id=model_id,
182
+ )
183
+ for violation in check_model_governance(passport, ctx, registry, directives):
184
+ violations.append(
185
+ {
186
+ "rule_id": violation.rule_id,
187
+ "severity": violation.severity.value,
188
+ "message": violation.message,
189
+ "compliance_refs": violation.compliance_refs,
190
+ "fix": violation.remediation,
191
+ }
192
+ )
193
+ return violations
194
+
195
+
196
+ def handle_models_status(params: dict) -> dict:
197
+ """Return bundled/user model registry and active directives."""
198
+ from iris_core.models.directives import DirectiveRegistry
199
+ from iris_core.models.model_registry import ModelRegistry
200
+
201
+ workspace = Path(params.get("workspace_path", "."))
202
+ gov_root = _governance_root(workspace)
203
+ registry = ModelRegistry.load(gov_root)
204
+ directives = DirectiveRegistry.load(gov_root)
205
+ return {
206
+ "models": {
207
+ model_id: capability.to_dict()
208
+ for model_id, capability in registry.models.items()
209
+ },
210
+ "active_directives": [d.to_dict() for d in directives.active_directives()],
211
+ "docs": "docs/MODEL_GOVERNANCE.md",
212
+ }
213
+
214
+
145
215
  def handle_check_agent_code(params: dict) -> dict:
146
216
  """
147
217
  Analyze agent code for compliance violations.
@@ -150,6 +220,7 @@ def handle_check_agent_code(params: dict) -> dict:
150
220
  code = params.get("code", "")
151
221
  file_path = params.get("file_path", "unknown")
152
222
  framework = params.get("framework", "colorado-ai-act")
223
+ workspace = Path(params.get("workspace_path", "."))
153
224
 
154
225
  violations = []
155
226
  suggestions = []
@@ -242,6 +313,8 @@ def handle_check_agent_code(params: dict) -> dict:
242
313
  "command": f"iris register --name my-agent --owner you@company.com --compliance colorado-ai-act",
243
314
  })
244
315
 
316
+ violations.extend(_model_governance_violations(code, workspace))
317
+
245
318
  return {
246
319
  "file": file_path,
247
320
  "framework": framework,
@@ -386,6 +459,18 @@ agent = IrisAgent(
386
459
  "explanation": "Declare the destination region so IRIS can enforce cross-region policy",
387
460
  "code_snippet": "@agent.guard(tool=\"storage\", action=\"write\", data_region=\"us-east-1\", destination_region=\"us-east-1\")",
388
461
  },
462
+ "IRIS-MODEL-001": {
463
+ "explanation": "Model is suspended by an active directive — switch to the approved fallback",
464
+ "command": "Review governance/directives/active.yaml and use the fallback_model listed there",
465
+ },
466
+ "IRIS-MODEL-004": {
467
+ "explanation": "Export-control restricted model requires valid user work authorization",
468
+ "code_snippet": "Set user_work_authorization (e.g. us-citizen) in EvaluationContext or use an unrestricted model",
469
+ },
470
+ "IRIS-MODEL-005": {
471
+ "explanation": "Frontier model requires HITL approval in staging/production",
472
+ "code_snippet": "Set hitl_approved=True after security review, or use iris HITL workflow",
473
+ },
389
474
  }
390
475
 
391
476
  fix = fixes.get(rule_id, {
@@ -469,6 +554,7 @@ HANDLERS = {
469
554
  "iris_suggest_policy": handle_suggest_policy,
470
555
  "iris_fix_violation": handle_fix_violation,
471
556
  "iris_scan_workspace": handle_scan_workspace,
557
+ "iris_models_status": handle_models_status,
472
558
  }
473
559
 
474
560
 
@@ -167,7 +167,7 @@ def _print_welcome() -> None:
167
167
  " · How to register and govern a new agent\n"
168
168
  " · Your compliance score in real time\n\n"
169
169
  "No API key required for most steps.\n"
170
- "github.com/gimartinb/iris-sdk",
170
+ "github.com/IRIS-Security/iris-sdk",
171
171
  title="Welcome to IRIS",
172
172
  style="blue",
173
173
  )
@@ -363,7 +363,7 @@ def _print_summary(findings_count: int) -> None:
363
363
  " [cyan]from iris_anthropic import IrisAnthropic[/cyan]\n"
364
364
  " [cyan]client = IrisAnthropic(passport=passport)[/cyan]\n\n"
365
365
  " 4. Read the full guide:\n"
366
- " github.com/gimartinb/iris-sdk/blob/main/QUICKSTART.md\n\n"
366
+ " github.com/IRIS-Security/iris-sdk/blob/main/QUICKSTART.md\n\n"
367
367
  "Questions? gilbert.martin@gmail.com",
368
368
  title="Quickstart complete",
369
369
  style="green",
@@ -9,6 +9,8 @@ from pathlib import Path
9
9
  import click
10
10
  from rich.console import Console
11
11
 
12
+ from iris_core.entitlements import Entitlements, Feature
13
+
12
14
  console = Console()
13
15
 
14
16
 
@@ -94,6 +96,9 @@ def scan_org(
94
96
  export GITHUB_TOKEN=ghp_... && iris scm scan-org --platform github --org my-org
95
97
  iris scm scan-org --platform gitlab --group my-group --token $GITLAB_TOKEN
96
98
  """
99
+ Entitlements().require(Feature.SCM_ORG_SCANNER, context="org-wide SCM scan")
100
+ if comment:
101
+ Entitlements().require(Feature.SCM_PR_COMMENTS, context="SCM PR comments")
97
102
  if platform == "github":
98
103
  if not org:
99
104
  console.print("[red]--org is required for GitHub scans[/red]")
@@ -330,7 +335,7 @@ Step 1: Go to github.com/organizations/{org_name}/settings/apps
330
335
  Step 2: Click "New GitHub App"
331
336
  Step 3: Fill in:
332
337
  Name: IRIS Governance
333
- Homepage URL: https://github.com/gimartinb/iris-sdk
338
+ Homepage URL: https://github.com/IRIS-Security/iris-sdk
334
339
  Webhook: leave unchecked for now
335
340
  Step 4: Set permissions (read-only):
336
341
  Repository permissions:
@@ -8,12 +8,14 @@ import click
8
8
  from rich.console import Console
9
9
  from rich.table import Table
10
10
 
11
+ from iris_core.entitlements import Entitlements, Feature
12
+
11
13
  console = Console()
12
14
 
13
15
 
14
16
  @click.group()
15
17
  def users():
16
- """Manage which users may invoke governed agents (PRO tier RBAC)."""
18
+ """Manage which users may invoke governed agents (Business tier RBAC beyond solo owner)."""
17
19
  pass
18
20
 
19
21
 
@@ -31,7 +33,11 @@ def users_add(email: str, role: str, agent: str, governance_dir: Path):
31
33
  Example:
32
34
  iris users add --email alice@co.com --role developer --agent my-agent
33
35
  """
34
- from iris_core.rbac.registry import add_user
36
+ from iris_core.rbac.registry import add_user, load_users
37
+
38
+ existing = load_users(agent, governance_dir)
39
+ if existing:
40
+ Entitlements().require(Feature.TEAM_RBAC, context="add second+ user to agent")
35
41
 
36
42
  try:
37
43
  entry = add_user(agent, email, role, governance_dir)
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-security-cli
3
- Version: 0.2.17
3
+ Version: 0.2.18
4
4
  Summary: IRIS CLI — declare, compile, preview, enforce, witness, certify, sentinel
5
5
  License: Apache-2.0
6
- Project-URL: Homepage, https://github.com/gimartinb/iris-sdk
7
- Project-URL: Repository, https://github.com/gimartinb/iris-sdk
6
+ Project-URL: Homepage, https://iris-security.github.io/iris-sdk/
7
+ Project-URL: Repository, https://github.com/IRIS-Security/iris-sdk
8
+ Project-URL: Documentation, https://iris-security.github.io/iris-sdk/cli-reference.html
9
+ Project-URL: Issues, https://github.com/IRIS-Security/iris-sdk/issues
8
10
  Classifier: Development Status :: 3 - Alpha
9
11
  Classifier: Intended Audience :: Developers
10
12
  Classifier: Topic :: Security
@@ -15,8 +17,8 @@ Classifier: Programming Language :: Python :: 3.11
15
17
  Classifier: Programming Language :: Python :: 3.12
16
18
  Requires-Python: >=3.10
17
19
  Description-Content-Type: text/markdown
18
- Requires-Dist: iris-security-core>=0.1.17
19
- Requires-Dist: iris-security-sdk>=0.2.17
20
+ Requires-Dist: iris-security-core>=0.1.18
21
+ Requires-Dist: iris-security-sdk>=0.2.18
20
22
  Requires-Dist: click>=8.1
21
23
  Requires-Dist: rich>=13.0
22
24
  Requires-Dist: pyyaml>=6.0
@@ -29,7 +31,7 @@ IRIS CLI — `iris scan`, `iris register`, `iris policy`, `iris compliance`.
29
31
 
30
32
  Command-line tools for AI agent governance and Colorado AI Act compliance.
31
33
 
32
- Part of the [IRIS SDK](https://github.com/gimartinb/iris-sdk).
34
+ Part of the [IRIS SDK](https://github.com/IRIS-Security/iris-sdk).
33
35
 
34
36
  ## Install
35
37
 
@@ -54,6 +54,7 @@ iris_security_cli.egg-info/entry_points.txt
54
54
  iris_security_cli.egg-info/requires.txt
55
55
  iris_security_cli.egg-info/top_level.txt
56
56
  tests/test_audit_log.py
57
+ tests/test_cli_free_tier_boundary.py
57
58
  tests/test_compliance_scan_fail_on.py
58
59
  tests/test_docs_pages_check.py
59
60
  tests/test_entitlements_display.py
@@ -67,4 +68,5 @@ tests/test_list_cmd.py
67
68
  tests/test_mcp_model_governance.py
68
69
  tests/test_policy_diff.py
69
70
  tests/test_quickstart.py
70
- tests/test_vocabulary.py
71
+ tests/test_vocabulary.py
72
+ tests/vault_fixtures.py
@@ -1,5 +1,5 @@
1
- iris-security-core>=0.1.17
2
- iris-security-sdk>=0.2.17
1
+ iris-security-core>=0.1.18
2
+ iris-security-sdk>=0.2.18
3
3
  click>=8.1
4
4
  rich>=13.0
5
5
  pyyaml>=6.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "iris-security-cli"
7
- version = "0.2.17"
7
+ version = "0.2.18"
8
8
  description = "IRIS CLI — declare, compile, preview, enforce, witness, certify, sentinel"
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -20,8 +20,8 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3.12",
21
21
  ]
22
22
  dependencies = [
23
- "iris-security-core>=0.1.17",
24
- "iris-security-sdk>=0.2.17",
23
+ "iris-security-core>=0.1.18",
24
+ "iris-security-sdk>=0.2.18",
25
25
  "click>=8.1",
26
26
  "rich>=13.0",
27
27
  "pyyaml>=6.0",
@@ -31,8 +31,10 @@ dependencies = [
31
31
  scm = ["iris-security-scm[all]>=0.2.0"]
32
32
 
33
33
  [project.urls]
34
- Homepage = "https://github.com/gimartinb/iris-sdk"
35
- Repository = "https://github.com/gimartinb/iris-sdk"
34
+ Homepage = "https://iris-security.github.io/iris-sdk/"
35
+ Repository = "https://github.com/IRIS-Security/iris-sdk"
36
+ Documentation = "https://iris-security.github.io/iris-sdk/cli-reference.html"
37
+ Issues = "https://github.com/IRIS-Security/iris-sdk/issues"
36
38
 
37
39
  [project.scripts]
38
40
  iris = "iris_cli.main:cli"