iris-security-cli 0.2.14__tar.gz → 0.2.17__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_cli-0.2.14 → iris_security_cli-0.2.17}/PKG-INFO +19 -6
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/README.md +14 -1
- iris_security_cli-0.2.17/iris_cli/compliance_scan_cmd.py +242 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/explain.py +1 -1
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/main.py +41 -7
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/quickstart.py +2 -2
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/scm.py +1 -1
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_security_cli.egg-info/PKG-INFO +19 -6
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_security_cli.egg-info/SOURCES.txt +2 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_security_cli.egg-info/requires.txt +2 -2
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/pyproject.toml +5 -5
- iris_security_cli-0.2.17/tests/test_compliance_scan_fail_on.py +56 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/__init__.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/action_plan.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/assess.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/audit_log.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/cedar_parser.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/certify.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/compiler_config.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/compliance_check_cmd.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/compliance_fix.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/cost.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/declare.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/delegation.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/discover.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/dlp_cmd.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/drift.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/enforce.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/entitlements_cmd.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/evidence.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/framework_suggest.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/framework_test.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/hitl.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/license_cmd.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/list_cmd.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/mcp_server.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/models_cmd.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/onboarding_report.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/org_policy.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/policy_cache.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/policy_commit.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/policy_diff.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/policy_status.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/preview.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/redteam.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/regulatory.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/scan_govern.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/scan_report.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/sentinel.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/status_cmd.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/users.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/vault.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/watch.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_cli/witness.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_security_cli.egg-info/dependency_links.txt +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_security_cli.egg-info/entry_points.txt +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_security_cli.egg-info/top_level.txt +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/setup.cfg +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_audit_log.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_docs_pages_check.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_entitlements_display.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_evidence.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_evidence_export.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_evidence_query.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_framework_suggest.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_framework_test.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_hitl_cli.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_list_cmd.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_mcp_model_governance.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_policy_diff.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_quickstart.py +0 -0
- {iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/tests/test_vocabulary.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iris-security-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.17
|
|
4
4
|
Summary: IRIS CLI — declare, compile, preview, enforce, witness, certify, sentinel
|
|
5
5
|
License: Apache-2.0
|
|
6
|
-
Project-URL: Homepage, https://
|
|
7
|
-
Project-URL: Repository, https://github.com/
|
|
6
|
+
Project-URL: Homepage, https://github.com/gimartinb/iris-sdk
|
|
7
|
+
Project-URL: Repository, https://github.com/gimartinb/iris-sdk
|
|
8
8
|
Classifier: Development Status :: 3 - Alpha
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
10
10
|
Classifier: Topic :: Security
|
|
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: iris-security-core>=0.1.
|
|
19
|
-
Requires-Dist: iris-security-sdk>=0.2.
|
|
18
|
+
Requires-Dist: iris-security-core>=0.1.17
|
|
19
|
+
Requires-Dist: iris-security-sdk>=0.2.17
|
|
20
20
|
Requires-Dist: click>=8.1
|
|
21
21
|
Requires-Dist: rich>=13.0
|
|
22
22
|
Requires-Dist: pyyaml>=6.0
|
|
@@ -29,7 +29,7 @@ IRIS CLI — `iris scan`, `iris register`, `iris policy`, `iris compliance`.
|
|
|
29
29
|
|
|
30
30
|
Command-line tools for AI agent governance and Colorado AI Act compliance.
|
|
31
31
|
|
|
32
|
-
Part of the [IRIS SDK](https://github.com/
|
|
32
|
+
Part of the [IRIS SDK](https://github.com/gimartinb/iris-sdk).
|
|
33
33
|
|
|
34
34
|
## Install
|
|
35
35
|
|
|
@@ -45,3 +45,16 @@ iris register --name my-agent --owner you@company.com --team my-team --complianc
|
|
|
45
45
|
iris scan
|
|
46
46
|
iris compliance check --framework colorado-ai-act
|
|
47
47
|
```
|
|
48
|
+
|
|
49
|
+
## GitHub Action
|
|
50
|
+
|
|
51
|
+
Add AI governance to CI with the composite Action in this repo (offline by default):
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
- uses: IRIS-Security/iris-cli@v1
|
|
55
|
+
with:
|
|
56
|
+
command: compliance scan
|
|
57
|
+
fail-on: blocker
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Optional `--push` to IRIS Cloud when `api-key` is set. See [GitHub Action docs](https://iris-security.github.io/iris-sdk/github-action.html).
|
|
@@ -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/
|
|
7
|
+
Part of the [IRIS SDK](https://github.com/gimartinb/iris-sdk).
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -20,3 +20,16 @@ iris register --name my-agent --owner you@company.com --team my-team --complianc
|
|
|
20
20
|
iris scan
|
|
21
21
|
iris compliance check --framework colorado-ai-act
|
|
22
22
|
```
|
|
23
|
+
|
|
24
|
+
## GitHub Action
|
|
25
|
+
|
|
26
|
+
Add AI governance to CI with the composite Action in this repo (offline by default):
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
- uses: IRIS-Security/iris-cli@v1
|
|
30
|
+
with:
|
|
31
|
+
command: compliance scan
|
|
32
|
+
fail-on: blocker
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Optional `--push` to IRIS Cloud when `api-key` is set. See [GitHub Action docs](https://iris-security.github.io/iris-sdk/github-action.html).
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""iris compliance scan — offline workload detection + local obligation eval."""
|
|
2
|
+
|
|
3
|
+
# Copyright 2024-2025 Gilbert Martin / IRIS Security, Inc.
|
|
4
|
+
# All Rights Reserved.
|
|
5
|
+
# Author: Gilbert Martin <gilbert@iris-security.io>
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
|
|
18
|
+
from iris.scan import detect_workload
|
|
19
|
+
from iris_core.compliance.workload_eval import (
|
|
20
|
+
applicable_frameworks,
|
|
21
|
+
evaluate_workload_profile,
|
|
22
|
+
top_recommended_actions,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
console = Console()
|
|
26
|
+
|
|
27
|
+
_SEVERITY_RANK = {"blocker": 0, "required": 1, "recommended": 2}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _exit_on_fail_threshold(obligations: list[dict], fail_on: str) -> None:
|
|
31
|
+
if fail_on == "none":
|
|
32
|
+
return
|
|
33
|
+
threshold = _SEVERITY_RANK[fail_on]
|
|
34
|
+
blocking = [
|
|
35
|
+
obligation
|
|
36
|
+
for obligation in obligations
|
|
37
|
+
if _SEVERITY_RANK.get(obligation.get("severity", "recommended"), 9) <= threshold
|
|
38
|
+
]
|
|
39
|
+
if blocking:
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _resolve_profile(
|
|
44
|
+
*,
|
|
45
|
+
scan_source: str | None,
|
|
46
|
+
path: str,
|
|
47
|
+
litellm_config: str | None,
|
|
48
|
+
litellm_proxy: str | None,
|
|
49
|
+
lookback_days: int,
|
|
50
|
+
) -> dict:
|
|
51
|
+
if scan_source is None:
|
|
52
|
+
return detect_workload(path)
|
|
53
|
+
|
|
54
|
+
if scan_source == "langfuse":
|
|
55
|
+
try:
|
|
56
|
+
from iris_langfuse import profile_from_langfuse
|
|
57
|
+
except ImportError as exc:
|
|
58
|
+
raise click.ClickException(
|
|
59
|
+
'iris-langfuse is not installed. Run: pip install "iris-langfuse[live]"'
|
|
60
|
+
) from exc
|
|
61
|
+
try:
|
|
62
|
+
return profile_from_langfuse(lookback_days=lookback_days)
|
|
63
|
+
except ImportError as exc:
|
|
64
|
+
raise click.ClickException(
|
|
65
|
+
'Langfuse SDK missing. Run: pip install "iris-langfuse[live]"'
|
|
66
|
+
) from exc
|
|
67
|
+
except ValueError as exc:
|
|
68
|
+
raise click.ClickException(str(exc)) from exc
|
|
69
|
+
|
|
70
|
+
if scan_source == "litellm":
|
|
71
|
+
try:
|
|
72
|
+
from iris_litellm import profile_from_litellm_config, profile_from_litellm_proxy
|
|
73
|
+
except ImportError as exc:
|
|
74
|
+
raise click.ClickException(
|
|
75
|
+
'iris-litellm is not installed. Run: pip install "iris-litellm[live]"'
|
|
76
|
+
) from exc
|
|
77
|
+
if litellm_config:
|
|
78
|
+
return profile_from_litellm_config(litellm_config)
|
|
79
|
+
if litellm_proxy:
|
|
80
|
+
return profile_from_litellm_proxy(litellm_proxy)
|
|
81
|
+
raise click.ClickException(
|
|
82
|
+
"LiteLLM source requires --config <litellm.config.yaml> or --proxy <base_url>"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
raise click.ClickException(f"Unknown scan source: {scan_source}")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _scan_subtitle(scan_source: str | None) -> str:
|
|
89
|
+
if scan_source == "langfuse":
|
|
90
|
+
return "Langfuse trace metadata — no prompt content read."
|
|
91
|
+
if scan_source == "litellm":
|
|
92
|
+
return "LiteLLM config/proxy — models and providers inferred."
|
|
93
|
+
return "Offline detection — no network calls required."
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _print_profile(profile: dict) -> None:
|
|
97
|
+
table = Table(title="What you're building", show_header=True, header_style="bold")
|
|
98
|
+
table.add_column("Attribute")
|
|
99
|
+
table.add_column("Detected values")
|
|
100
|
+
for key in (
|
|
101
|
+
"providers",
|
|
102
|
+
"frameworks",
|
|
103
|
+
"models",
|
|
104
|
+
"data_categories",
|
|
105
|
+
"deployment_regions",
|
|
106
|
+
"agent_count",
|
|
107
|
+
"autonomy_level",
|
|
108
|
+
"customer_facing",
|
|
109
|
+
):
|
|
110
|
+
value = profile.get(key, [])
|
|
111
|
+
if isinstance(value, bool):
|
|
112
|
+
display = "yes" if value else "no"
|
|
113
|
+
elif isinstance(value, list):
|
|
114
|
+
display = ", ".join(value) if value else "—"
|
|
115
|
+
else:
|
|
116
|
+
display = str(value)
|
|
117
|
+
table.add_row(key.replace("_", " ").title(), display)
|
|
118
|
+
console.print(table)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _print_frameworks(profile: dict) -> None:
|
|
122
|
+
table = Table(title="Applicable frameworks", show_header=True, header_style="bold")
|
|
123
|
+
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))
|
|
127
|
+
console.print(table)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _print_actions(obligations: list[dict]) -> None:
|
|
131
|
+
actions = top_recommended_actions(obligations, limit=5)
|
|
132
|
+
if not actions:
|
|
133
|
+
console.print("[dim]No obligations triggered for detected profile.[/dim]")
|
|
134
|
+
return
|
|
135
|
+
table = Table(title="Top recommended actions", show_header=True, header_style="bold")
|
|
136
|
+
table.add_column("#", style="dim")
|
|
137
|
+
table.add_column("Severity")
|
|
138
|
+
table.add_column("Framework")
|
|
139
|
+
table.add_column("Action")
|
|
140
|
+
for idx, action in enumerate(actions, 1):
|
|
141
|
+
sev = action.get("severity", "recommended")
|
|
142
|
+
color = {"blocker": "red", "required": "yellow"}.get(sev, "cyan")
|
|
143
|
+
table.add_row(
|
|
144
|
+
str(idx),
|
|
145
|
+
f"[{color}]{sev}[/{color}]",
|
|
146
|
+
action.get("framework_key", ""),
|
|
147
|
+
(action.get("recommended_action") or "")[:80],
|
|
148
|
+
)
|
|
149
|
+
console.print(table)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _push_profile(profile: dict) -> None:
|
|
153
|
+
api_key = os.environ.get("IRIS_API_KEY") or os.environ.get("IRIS_CLOUD_API_KEY")
|
|
154
|
+
base_url = os.environ.get("IRIS_API_URL", "http://localhost:8000")
|
|
155
|
+
if not api_key:
|
|
156
|
+
console.print("[yellow]Set IRIS_API_KEY to push profile to cloud.[/yellow]")
|
|
157
|
+
return
|
|
158
|
+
import httpx
|
|
159
|
+
|
|
160
|
+
response = httpx.post(
|
|
161
|
+
f"{base_url.rstrip('/')}/intelligence/profile/scan",
|
|
162
|
+
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
|
163
|
+
json=profile,
|
|
164
|
+
timeout=30,
|
|
165
|
+
)
|
|
166
|
+
if response.status_code >= 400:
|
|
167
|
+
console.print(f"[red]Push failed ({response.status_code}): {response.text}[/red]")
|
|
168
|
+
return
|
|
169
|
+
console.print("[green]Profile pushed to IRIS Cloud.[/green]")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@click.command("scan")
|
|
173
|
+
@click.option("--path", default=".", help="Project path to scan (default local code scan)")
|
|
174
|
+
@click.option(
|
|
175
|
+
"--from",
|
|
176
|
+
"scan_source",
|
|
177
|
+
type=click.Choice(["langfuse", "litellm"], case_sensitive=False),
|
|
178
|
+
default=None,
|
|
179
|
+
help="Observability source instead of local code scan",
|
|
180
|
+
)
|
|
181
|
+
@click.option("--config", "litellm_config", type=click.Path(exists=True), help="LiteLLM config.yaml")
|
|
182
|
+
@click.option("--proxy", "litellm_proxy", help="LiteLLM proxy base URL")
|
|
183
|
+
@click.option("--lookback-days", default=30, show_default=True, help="Langfuse lookback window")
|
|
184
|
+
@click.option("--push", is_flag=True, help="POST profile to cloud when IRIS_API_KEY is set")
|
|
185
|
+
@click.option(
|
|
186
|
+
"--fail-on",
|
|
187
|
+
default="none",
|
|
188
|
+
type=click.Choice(["blocker", "required", "none"]),
|
|
189
|
+
show_default=True,
|
|
190
|
+
help="Exit 1 when obligations meet or exceed this severity (CI integration)",
|
|
191
|
+
)
|
|
192
|
+
@click.option("--json", "as_json", is_flag=True, help="Output raw JSON")
|
|
193
|
+
def compliance_scan_cmd(
|
|
194
|
+
path: str,
|
|
195
|
+
scan_source: str | None,
|
|
196
|
+
litellm_config: str | None,
|
|
197
|
+
litellm_proxy: str | None,
|
|
198
|
+
lookback_days: int,
|
|
199
|
+
push: bool,
|
|
200
|
+
fail_on: str,
|
|
201
|
+
as_json: bool,
|
|
202
|
+
) -> None:
|
|
203
|
+
"""Detect workload attributes and evaluate local regulatory obligations (offline)."""
|
|
204
|
+
root = Path(path).resolve()
|
|
205
|
+
if scan_source is None and not root.exists():
|
|
206
|
+
raise click.ClickException(f"Path not found: {root}")
|
|
207
|
+
|
|
208
|
+
profile = _resolve_profile(
|
|
209
|
+
scan_source=scan_source,
|
|
210
|
+
path=str(root),
|
|
211
|
+
litellm_config=litellm_config,
|
|
212
|
+
litellm_proxy=litellm_proxy,
|
|
213
|
+
lookback_days=lookback_days,
|
|
214
|
+
)
|
|
215
|
+
obligations = evaluate_workload_profile(profile)
|
|
216
|
+
|
|
217
|
+
if as_json:
|
|
218
|
+
import json
|
|
219
|
+
|
|
220
|
+
click.echo(json.dumps({"profile": profile, "obligations": obligations}, indent=2))
|
|
221
|
+
if push:
|
|
222
|
+
_push_profile(profile)
|
|
223
|
+
_exit_on_fail_threshold(obligations, fail_on)
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
console.print(
|
|
227
|
+
Panel(
|
|
228
|
+
"[bold]IRIS Compliance Intelligence Scan[/bold]\n"
|
|
229
|
+
+ _scan_subtitle(scan_source),
|
|
230
|
+
border_style="cyan",
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
_print_profile(profile)
|
|
234
|
+
_print_frameworks(profile)
|
|
235
|
+
_print_actions(obligations)
|
|
236
|
+
console.print(
|
|
237
|
+
"\n[dim]Continuous monitoring, evidence mapping, and team workflows: "
|
|
238
|
+
"iris cloud connect -> https://iris-security.io[/dim]"
|
|
239
|
+
)
|
|
240
|
+
if push:
|
|
241
|
+
_push_profile(profile)
|
|
242
|
+
_exit_on_fail_threshold(obligations, fail_on)
|
|
@@ -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/
|
|
33
|
+
github.com/gimartinb/iris-sdk/issues"""
|
|
34
34
|
|
|
35
35
|
TECHNICAL_EXPLANATION = """\
|
|
36
36
|
Proxy pattern (identical attribute forwarding):
|
|
@@ -17,6 +17,7 @@ Every iris scan or iris certify is a weekly active developer event.
|
|
|
17
17
|
import click
|
|
18
18
|
import sys
|
|
19
19
|
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
20
21
|
from rich.console import Console
|
|
21
22
|
from rich.panel import Panel
|
|
22
23
|
|
|
@@ -25,9 +26,31 @@ from iris_core.cli_timing.instrument import timed_cli_command
|
|
|
25
26
|
console = Console()
|
|
26
27
|
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
class IrisCLI(click.Group):
|
|
30
|
+
"""Click group that records daily aggregated CLI usage after each command."""
|
|
31
|
+
|
|
32
|
+
def invoke(self, ctx: click.Context) -> Any:
|
|
33
|
+
result = super().invoke(ctx)
|
|
34
|
+
if not ctx.resilient_parsing:
|
|
35
|
+
command = self._resolved_command(ctx)
|
|
36
|
+
if command:
|
|
37
|
+
from iris._telemetry import maybe_record_cli_usage
|
|
38
|
+
|
|
39
|
+
maybe_record_cli_usage(command)
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def _resolved_command(ctx: click.Context) -> str | None:
|
|
44
|
+
if ctx.invoked_subcommand is None:
|
|
45
|
+
return None
|
|
46
|
+
if ctx.command_path:
|
|
47
|
+
return " ".join(ctx.command_path)
|
|
48
|
+
return ctx.invoked_subcommand
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@click.group(cls=IrisCLI)
|
|
29
52
|
@click.version_option(
|
|
30
|
-
version="0.2.
|
|
53
|
+
version="0.2.17",
|
|
31
54
|
prog_name="iris",
|
|
32
55
|
message="%(prog)s %(version)s · AARM Core conformant (R1–R6) · "
|
|
33
56
|
"AIUC-1 Q1 2026 aligned · https://iris-security.io",
|
|
@@ -462,12 +485,20 @@ framework.add_command(framework_suggest, name="suggest")
|
|
|
462
485
|
from iris_cli.assess import compliance_assess
|
|
463
486
|
compliance.add_command(compliance_assess)
|
|
464
487
|
|
|
465
|
-
# Wire in evidence commands
|
|
466
|
-
|
|
467
|
-
|
|
488
|
+
# Wire in evidence commands (optional — requires iris-security-core>=0.1.17 exporters)
|
|
489
|
+
try:
|
|
490
|
+
from iris_cli.evidence import evidence
|
|
468
491
|
|
|
469
|
-
|
|
470
|
-
|
|
492
|
+
cli.add_command(evidence)
|
|
493
|
+
except ModuleNotFoundError:
|
|
494
|
+
pass
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
from iris_cli.vault import vault
|
|
498
|
+
|
|
499
|
+
cli.add_command(vault)
|
|
500
|
+
except ModuleNotFoundError:
|
|
501
|
+
pass
|
|
471
502
|
|
|
472
503
|
from iris_cli.framework_test import test as framework_test_cmd_legacy
|
|
473
504
|
# framework_test kept for internal imports; certify is the primary command
|
|
@@ -560,6 +591,9 @@ def ping():
|
|
|
560
591
|
from iris_cli.compliance_check_cmd import compliance_check_cmd
|
|
561
592
|
compliance.add_command(compliance_check_cmd, name="check")
|
|
562
593
|
|
|
594
|
+
from iris_cli.compliance_scan_cmd import compliance_scan_cmd
|
|
595
|
+
compliance.add_command(compliance_scan_cmd, name="scan")
|
|
596
|
+
|
|
563
597
|
cli.add_command(compliance)
|
|
564
598
|
cli.add_command(framework)
|
|
565
599
|
|
|
@@ -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/
|
|
170
|
+
"github.com/gimartinb/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/
|
|
366
|
+
" github.com/gimartinb/iris-sdk/blob/main/QUICKSTART.md\n\n"
|
|
367
367
|
"Questions? gilbert.martin@gmail.com",
|
|
368
368
|
title="Quickstart complete",
|
|
369
369
|
style="green",
|
|
@@ -330,7 +330,7 @@ Step 1: Go to github.com/organizations/{org_name}/settings/apps
|
|
|
330
330
|
Step 2: Click "New GitHub App"
|
|
331
331
|
Step 3: Fill in:
|
|
332
332
|
Name: IRIS Governance
|
|
333
|
-
Homepage URL: https://github.com/
|
|
333
|
+
Homepage URL: https://github.com/gimartinb/iris-sdk
|
|
334
334
|
Webhook: leave unchecked for now
|
|
335
335
|
Step 4: Set permissions (read-only):
|
|
336
336
|
Repository permissions:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iris-security-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.17
|
|
4
4
|
Summary: IRIS CLI — declare, compile, preview, enforce, witness, certify, sentinel
|
|
5
5
|
License: Apache-2.0
|
|
6
|
-
Project-URL: Homepage, https://
|
|
7
|
-
Project-URL: Repository, https://github.com/
|
|
6
|
+
Project-URL: Homepage, https://github.com/gimartinb/iris-sdk
|
|
7
|
+
Project-URL: Repository, https://github.com/gimartinb/iris-sdk
|
|
8
8
|
Classifier: Development Status :: 3 - Alpha
|
|
9
9
|
Classifier: Intended Audience :: Developers
|
|
10
10
|
Classifier: Topic :: Security
|
|
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
|
-
Requires-Dist: iris-security-core>=0.1.
|
|
19
|
-
Requires-Dist: iris-security-sdk>=0.2.
|
|
18
|
+
Requires-Dist: iris-security-core>=0.1.17
|
|
19
|
+
Requires-Dist: iris-security-sdk>=0.2.17
|
|
20
20
|
Requires-Dist: click>=8.1
|
|
21
21
|
Requires-Dist: rich>=13.0
|
|
22
22
|
Requires-Dist: pyyaml>=6.0
|
|
@@ -29,7 +29,7 @@ IRIS CLI — `iris scan`, `iris register`, `iris policy`, `iris compliance`.
|
|
|
29
29
|
|
|
30
30
|
Command-line tools for AI agent governance and Colorado AI Act compliance.
|
|
31
31
|
|
|
32
|
-
Part of the [IRIS SDK](https://github.com/
|
|
32
|
+
Part of the [IRIS SDK](https://github.com/gimartinb/iris-sdk).
|
|
33
33
|
|
|
34
34
|
## Install
|
|
35
35
|
|
|
@@ -45,3 +45,16 @@ iris register --name my-agent --owner you@company.com --team my-team --complianc
|
|
|
45
45
|
iris scan
|
|
46
46
|
iris compliance check --framework colorado-ai-act
|
|
47
47
|
```
|
|
48
|
+
|
|
49
|
+
## GitHub Action
|
|
50
|
+
|
|
51
|
+
Add AI governance to CI with the composite Action in this repo (offline by default):
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
- uses: IRIS-Security/iris-cli@v1
|
|
55
|
+
with:
|
|
56
|
+
command: compliance scan
|
|
57
|
+
fail-on: blocker
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Optional `--push` to IRIS Cloud when `api-key` is set. See [GitHub Action docs](https://iris-security.github.io/iris-sdk/github-action.html).
|
{iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_security_cli.egg-info/SOURCES.txt
RENAMED
|
@@ -9,6 +9,7 @@ iris_cli/certify.py
|
|
|
9
9
|
iris_cli/compiler_config.py
|
|
10
10
|
iris_cli/compliance_check_cmd.py
|
|
11
11
|
iris_cli/compliance_fix.py
|
|
12
|
+
iris_cli/compliance_scan_cmd.py
|
|
12
13
|
iris_cli/cost.py
|
|
13
14
|
iris_cli/declare.py
|
|
14
15
|
iris_cli/delegation.py
|
|
@@ -53,6 +54,7 @@ iris_security_cli.egg-info/entry_points.txt
|
|
|
53
54
|
iris_security_cli.egg-info/requires.txt
|
|
54
55
|
iris_security_cli.egg-info/top_level.txt
|
|
55
56
|
tests/test_audit_log.py
|
|
57
|
+
tests/test_compliance_scan_fail_on.py
|
|
56
58
|
tests/test_docs_pages_check.py
|
|
57
59
|
tests/test_entitlements_display.py
|
|
58
60
|
tests/test_evidence.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "iris-security-cli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.17"
|
|
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.
|
|
24
|
-
"iris-security-sdk>=0.2.
|
|
23
|
+
"iris-security-core>=0.1.17",
|
|
24
|
+
"iris-security-sdk>=0.2.17",
|
|
25
25
|
"click>=8.1",
|
|
26
26
|
"rich>=13.0",
|
|
27
27
|
"pyyaml>=6.0",
|
|
@@ -31,8 +31,8 @@ dependencies = [
|
|
|
31
31
|
scm = ["iris-security-scm[all]>=0.2.0"]
|
|
32
32
|
|
|
33
33
|
[project.urls]
|
|
34
|
-
Homepage = "https://
|
|
35
|
-
Repository = "https://github.com/
|
|
34
|
+
Homepage = "https://github.com/gimartinb/iris-sdk"
|
|
35
|
+
Repository = "https://github.com/gimartinb/iris-sdk"
|
|
36
36
|
|
|
37
37
|
[project.scripts]
|
|
38
38
|
iris = "iris_cli.main:cli"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Tests for iris compliance scan --fail-on CI thresholds."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from click.testing import CliRunner
|
|
6
|
+
|
|
7
|
+
from iris_cli.main import cli
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_compliance_scan_fail_on_blocker_exits_nonzero(monkeypatch):
|
|
11
|
+
runner = CliRunner()
|
|
12
|
+
profile = {
|
|
13
|
+
"providers": ["openai"],
|
|
14
|
+
"frameworks": ["langchain"],
|
|
15
|
+
"models": ["gpt-4"],
|
|
16
|
+
"data_categories": ["pii"],
|
|
17
|
+
"deployment_regions": ["eu"],
|
|
18
|
+
"agent_count": 5,
|
|
19
|
+
"autonomy_level": "autonomous",
|
|
20
|
+
"customer_facing": True,
|
|
21
|
+
}
|
|
22
|
+
obligations = [
|
|
23
|
+
{"severity": "blocker", "framework_key": "eu_ai_act", "recommended_action": "test"},
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
monkeypatch.setattr(
|
|
27
|
+
"iris_cli.compliance_scan_cmd.detect_workload",
|
|
28
|
+
lambda path: profile,
|
|
29
|
+
)
|
|
30
|
+
monkeypatch.setattr(
|
|
31
|
+
"iris_cli.compliance_scan_cmd.evaluate_workload_profile",
|
|
32
|
+
lambda profile: obligations,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
result = runner.invoke(cli, ["compliance", "scan", "--json", "--fail-on", "blocker"])
|
|
36
|
+
assert result.exit_code == 1
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_compliance_scan_fail_on_none_always_zero(monkeypatch):
|
|
40
|
+
runner = CliRunner()
|
|
41
|
+
profile = {"providers": [], "frameworks": [], "models": [], "agent_count": 0}
|
|
42
|
+
obligations = [
|
|
43
|
+
{"severity": "blocker", "framework_key": "eu_ai_act", "recommended_action": "test"},
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
monkeypatch.setattr(
|
|
47
|
+
"iris_cli.compliance_scan_cmd.detect_workload",
|
|
48
|
+
lambda path: profile,
|
|
49
|
+
)
|
|
50
|
+
monkeypatch.setattr(
|
|
51
|
+
"iris_cli.compliance_scan_cmd.evaluate_workload_profile",
|
|
52
|
+
lambda profile: obligations,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
result = runner.invoke(cli, ["compliance", "scan", "--json", "--fail-on", "none"])
|
|
56
|
+
assert result.exit_code == 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_security_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{iris_security_cli-0.2.14 → iris_security_cli-0.2.17}/iris_security_cli.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|