dfx-agentguard 0.1.0__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 (30) hide show
  1. dfx_agentguard-0.1.0/PKG-INFO +236 -0
  2. dfx_agentguard-0.1.0/README.md +195 -0
  3. dfx_agentguard-0.1.0/agentguard/__init__.py +3 -0
  4. dfx_agentguard-0.1.0/agentguard/cli.py +67 -0
  5. dfx_agentguard-0.1.0/agentguard/mcp_server.py +205 -0
  6. dfx_agentguard-0.1.0/agentguard/models.py +95 -0
  7. dfx_agentguard-0.1.0/agentguard/reporter.py +161 -0
  8. dfx_agentguard-0.1.0/agentguard/rules/__init__.py +23 -0
  9. dfx_agentguard-0.1.0/agentguard/rules/agent_loop.py +125 -0
  10. dfx_agentguard-0.1.0/agentguard/rules/context_manipulation.py +72 -0
  11. dfx_agentguard-0.1.0/agentguard/rules/credential_leak.py +109 -0
  12. dfx_agentguard-0.1.0/agentguard/rules/data_exfiltration.py +134 -0
  13. dfx_agentguard-0.1.0/agentguard/rules/excessive_agency.py +126 -0
  14. dfx_agentguard-0.1.0/agentguard/rules/prompt_injection.py +60 -0
  15. dfx_agentguard-0.1.0/agentguard/rules/supply_chain.py +124 -0
  16. dfx_agentguard-0.1.0/agentguard/rules/tool_abuse.py +111 -0
  17. dfx_agentguard-0.1.0/agentguard/rules/trust_boundary.py +112 -0
  18. dfx_agentguard-0.1.0/agentguard/rules/unsafe_eval.py +48 -0
  19. dfx_agentguard-0.1.0/agentguard/scanner.py +95 -0
  20. dfx_agentguard-0.1.0/dfx_agentguard.egg-info/PKG-INFO +236 -0
  21. dfx_agentguard-0.1.0/dfx_agentguard.egg-info/SOURCES.txt +28 -0
  22. dfx_agentguard-0.1.0/dfx_agentguard.egg-info/dependency_links.txt +1 -0
  23. dfx_agentguard-0.1.0/dfx_agentguard.egg-info/entry_points.txt +2 -0
  24. dfx_agentguard-0.1.0/dfx_agentguard.egg-info/requires.txt +11 -0
  25. dfx_agentguard-0.1.0/dfx_agentguard.egg-info/top_level.txt +2 -0
  26. dfx_agentguard-0.1.0/license +21 -0
  27. dfx_agentguard-0.1.0/pyproject.toml +51 -0
  28. dfx_agentguard-0.1.0/setup.cfg +4 -0
  29. dfx_agentguard-0.1.0/setup.py +32 -0
  30. dfx_agentguard-0.1.0/tests/test_scanner.py +135 -0
@@ -0,0 +1,236 @@
1
+ Metadata-Version: 2.4
2
+ Name: dfx-agentguard
3
+ Version: 0.1.0
4
+ Summary: Autonomous security scanner for AI agents — detects prompt injection, tool abuse, data exfiltration, and OWASP ASI Top 10 vulnerabilities.
5
+ Home-page: https://github.com/dockfixlabs/agentguard
6
+ Author: Dockfix Labs
7
+ Author-email: Dockfix Labs <security@dockfixlabs.dev>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/dockfixlabs/agentguard
10
+ Project-URL: Documentation, https://github.com/dockfixlabs/agentguard#readme
11
+ Project-URL: Issues, https://github.com/dockfixlabs/agentguard/issues
12
+ Project-URL: Security Policy, https://github.com/dockfixlabs/agentguard/blob/main/SECURITY.md
13
+ Keywords: ai-security,agent-security,owasp-asi,prompt-injection,mcp,static-analysis,llm-security
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Information Technology
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Security
24
+ Classifier: Topic :: Software Development :: Quality Assurance
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: license
28
+ Requires-Dist: click>=8.1
29
+ Requires-Dist: rich>=13.0
30
+ Requires-Dist: pydantic>=2.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7.0; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Requires-Dist: ruff; extra == "dev"
35
+ Provides-Extra: mcp
36
+ Requires-Dist: mcp>=0.9; extra == "mcp"
37
+ Dynamic: author
38
+ Dynamic: home-page
39
+ Dynamic: license-file
40
+ Dynamic: requires-python
41
+
42
+ # 🛡️ AgentGuard
43
+
44
+ > **Autonomous security scanner for AI agents.** Detects prompt injection, tool abuse, data exfiltration, and OWASP ASI Top 10 vulnerabilities in agent code.
45
+
46
+ [![Python 3.10+](https://img.shields.io/badge/Python-3.10+-3776AB?style=flat-square&logo=python&logoColor=white)](https://python.org)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE)
48
+ [![CI](https://img.shields.io/badge/CI-GitHub%20Actions-2088FF?style=flat-square&logo=github-actions&logoColor=white)](https://github.com/dockfixlabs/agentguard/actions)
49
+ [![OWASP ASI](https://img.shields.io/badge/OWASP-ASI%20Top%2010-orange?style=flat-square)](https://owasp.org/www-project-agentic-security-initiative/)
50
+
51
+ ---
52
+
53
+ ## Why AgentGuard?
54
+
55
+ AI agents are being deployed at scale — in coding tools, customer support, trading bots, and autonomous systems. **Nobody is scanning their code for security vulnerabilities.**
56
+
57
+ Existing tools (Bandit, Semgrep, CodeQL) scan for traditional vulnerabilities. AgentGuard scans for **agent-specific** attack vectors:
58
+
59
+ - 📥 **Prompt Injection** — untrusted input reaching LLM prompts
60
+ - 🔧 **Tool Abuse** — agents with unrestricted shell/exec access
61
+ - 📤 **Data Exfiltration** — agents leaking data to external URLs
62
+ - 🔑 **Credential Exposure** — hardcoded API keys and wallet seeds
63
+ - ⚡ **Unsafe Eval** — `eval()`, `exec()`, `subprocess(shell=True)` with user input
64
+ - 🧠 **Context Manipulation** — unbounded context window attacks
65
+ - 🏰 **Trust Boundary Violations** — agents running as root, accessing host filesystem
66
+
67
+ ## Quick Start
68
+
69
+ ```bash
70
+ pip install agentguard
71
+
72
+ # Scan a directory
73
+ agentguard .
74
+
75
+ # JSON output for CI/CD
76
+ agentguard src/ --format json
77
+
78
+ # SARIF for GitHub Code Scanning
79
+ agentguard . --format sarif > results.sarif
80
+
81
+ # Only show HIGH and above
82
+ agentguard . --min-severity HIGH
83
+ ```
84
+
85
+ ## CLI Usage
86
+
87
+ ```
88
+ agentguard [OPTIONS] [TARGET]
89
+
90
+ Arguments:
91
+ TARGET Directory or file to scan (default: current directory)
92
+
93
+ Options:
94
+ --format [text|json|sarif] Output format (default: text)
95
+ --exit-code / --no-exit-code Exit non-zero if findings found (default: on)
96
+ --min-severity [CRITICAL|HIGH|MEDIUM|LOW|INFO] Minimum severity to report
97
+ --help Show help
98
+ ```
99
+
100
+ ## OWASP ASI Top 10 Coverage
101
+
102
+ | ID | Vulnerability | Status |
103
+ |----|--------------|--------|
104
+ | ASI01 | Prompt Injection | ✅ |
105
+ | ASI02 | Tool Abuse / Unintended Tool Use | ✅ |
106
+ | ASI03 | Data Exfiltration / Sensitive Data Leakage | ✅ |
107
+ | ASI04 | Unauthorized Actions / Excessive Agency | ✅ |
108
+ | ASI05 | Supply Chain / Untrusted Components | ✅ |
109
+ | ASI06 | Insecure Output Handling | ✅ |
110
+ | ASI07 | Credential / Secret Exposure | ✅ |
111
+ | ASI08 | Context Window Manipulation | ✅ |
112
+ | ASI09 | Agent Loop Exploitation | ✅ |
113
+ | ASI10 | Trust Boundary Violation | ✅ |
114
+
115
+ ## CI/CD Integration
116
+
117
+ ### GitHub Actions
118
+
119
+ ```yaml
120
+ name: Security Scan
121
+ on: [push, pull_request]
122
+
123
+ jobs:
124
+ agentguard:
125
+ runs-on: ubuntu-latest
126
+ steps:
127
+ - uses: actions/checkout@v4
128
+ - uses: actions/setup-python@v5
129
+ with:
130
+ python-version: '3.12'
131
+ - run: pip install agentguard
132
+ - run: agentguard . --format sarif > results.sarif
133
+ - uses: github/codeql-action/upload-sarif@v3
134
+ with:
135
+ sarif_file: results.sarif
136
+ ```
137
+
138
+ ### Pre-commit Hook
139
+
140
+ ```yaml
141
+ repos:
142
+ - repo: https://github.com/dockfixlabs/agentguard
143
+ rev: v0.1.0
144
+ hooks:
145
+ - id: agentguard
146
+ args: ["--min-severity", "HIGH"]
147
+ ```
148
+
149
+ ## Programmatic Usage
150
+
151
+ ```python
152
+ from agentguard.scanner import scan_directory
153
+ from agentguard.reporter import json_report
154
+
155
+ result = scan_directory("src/")
156
+
157
+ print(f"Found {len(result.findings)} issues")
158
+ print(f"Critical: {result.critical_count}")
159
+ print(f"High: {result.high_count}")
160
+
161
+ for finding in result.findings:
162
+ print(f" [{finding.severity}] {finding.rule_name} at {finding.file}:{finding.line}")
163
+ ```
164
+
165
+ ## Detection Rules
166
+
167
+ ### ASI01 — Prompt Injection
168
+ Detects untrusted user input being concatenated into LLM prompts via f-strings, `.format()`, or string concatenation.
169
+
170
+ ### ASI02 — Tool Abuse
171
+ Flags agents with access to `exec()`, `subprocess`, `os.system()`, shell tools, unrestricted tool registration, and missing rate limits.
172
+
173
+ ### ASI03 — Data Exfiltration
174
+ Detects outbound HTTP requests to external URLs, webhook configurations, DNS exfiltration patterns, and secret+network correlation.
175
+
176
+ ### ASI06 — Unsafe Eval
177
+ Flags `eval()`, `exec()`, `compile()` with user input, `pickle.load()`, `yaml.load()` without SafeLoader, `subprocess(shell=True)`.
178
+
179
+ ### ASI07 — Credential Exposure
180
+ Detects hardcoded API keys (sk-, ghp_, AKIA), private keys, connection strings with passwords, and crypto wallet seeds.
181
+
182
+ ### ASI08 — Context Manipulation
183
+ Flags missing token limits, unbounded context accumulation, and large files loaded directly into LLM context.
184
+
185
+ ### ASI10 — Trust Boundary Violation
186
+ Detects agents running as root, host filesystem access, self-modifying code, and direct database access with user input.
187
+
188
+ ## MCP Server Mode
189
+
190
+ Scan agent code directly from Claude Code, Cursor, or any MCP-compatible client:
191
+
192
+ ```json
193
+ // ~/.claude/claude_code_config.json
194
+ {
195
+ "mcpServers": {
196
+ "agentguard": {
197
+ "command": "python3",
198
+ "args": ["-m", "agentguard.mcp_server"]
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ Then ask Claude: *"Scan my agent code for security vulnerabilities"*
205
+
206
+ ### MCP Tools
207
+ - `scan_agent_code` — Scan a directory/file for vulnerabilities
208
+ - `list_rules` — List all detection rules and OWASP mapping
209
+ - `get_finding_details` — Get remediation guidance for a specific rule
210
+
211
+ ## Roadmap
212
+
213
+ - [x] OWASP ASI Top 10 — all 10 categories covered
214
+ - [x] MCP server mode — scan from Claude Code/Cursor
215
+ - [x] SARIF output — GitHub Code Scanning integration
216
+ - [ ] PyPI publication
217
+ - [ ] Semantic analysis with LLM-assisted code review
218
+ - [ ] Language support: Rust, Go, Java
219
+ - [ ] VS Code extension
220
+ - [ ] GitHub App for automated PR reviews
221
+
222
+ ## Contributing
223
+
224
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and feature requests welcome.
225
+
226
+ ## Security
227
+
228
+ See [SECURITY.md](SECURITY.md). Report vulnerabilities privately — do not open public issues.
229
+
230
+ ## License
231
+
232
+ MIT — see [LICENSE](LICENSE).
233
+
234
+ ---
235
+
236
+ Built by [Dockfix Labs](https://github.com/dockfixlabs). Built for the AI agent era.
@@ -0,0 +1,195 @@
1
+ # 🛡️ AgentGuard
2
+
3
+ > **Autonomous security scanner for AI agents.** Detects prompt injection, tool abuse, data exfiltration, and OWASP ASI Top 10 vulnerabilities in agent code.
4
+
5
+ [![Python 3.10+](https://img.shields.io/badge/Python-3.10+-3776AB?style=flat-square&logo=python&logoColor=white)](https://python.org)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE)
7
+ [![CI](https://img.shields.io/badge/CI-GitHub%20Actions-2088FF?style=flat-square&logo=github-actions&logoColor=white)](https://github.com/dockfixlabs/agentguard/actions)
8
+ [![OWASP ASI](https://img.shields.io/badge/OWASP-ASI%20Top%2010-orange?style=flat-square)](https://owasp.org/www-project-agentic-security-initiative/)
9
+
10
+ ---
11
+
12
+ ## Why AgentGuard?
13
+
14
+ AI agents are being deployed at scale — in coding tools, customer support, trading bots, and autonomous systems. **Nobody is scanning their code for security vulnerabilities.**
15
+
16
+ Existing tools (Bandit, Semgrep, CodeQL) scan for traditional vulnerabilities. AgentGuard scans for **agent-specific** attack vectors:
17
+
18
+ - 📥 **Prompt Injection** — untrusted input reaching LLM prompts
19
+ - 🔧 **Tool Abuse** — agents with unrestricted shell/exec access
20
+ - 📤 **Data Exfiltration** — agents leaking data to external URLs
21
+ - 🔑 **Credential Exposure** — hardcoded API keys and wallet seeds
22
+ - ⚡ **Unsafe Eval** — `eval()`, `exec()`, `subprocess(shell=True)` with user input
23
+ - 🧠 **Context Manipulation** — unbounded context window attacks
24
+ - 🏰 **Trust Boundary Violations** — agents running as root, accessing host filesystem
25
+
26
+ ## Quick Start
27
+
28
+ ```bash
29
+ pip install agentguard
30
+
31
+ # Scan a directory
32
+ agentguard .
33
+
34
+ # JSON output for CI/CD
35
+ agentguard src/ --format json
36
+
37
+ # SARIF for GitHub Code Scanning
38
+ agentguard . --format sarif > results.sarif
39
+
40
+ # Only show HIGH and above
41
+ agentguard . --min-severity HIGH
42
+ ```
43
+
44
+ ## CLI Usage
45
+
46
+ ```
47
+ agentguard [OPTIONS] [TARGET]
48
+
49
+ Arguments:
50
+ TARGET Directory or file to scan (default: current directory)
51
+
52
+ Options:
53
+ --format [text|json|sarif] Output format (default: text)
54
+ --exit-code / --no-exit-code Exit non-zero if findings found (default: on)
55
+ --min-severity [CRITICAL|HIGH|MEDIUM|LOW|INFO] Minimum severity to report
56
+ --help Show help
57
+ ```
58
+
59
+ ## OWASP ASI Top 10 Coverage
60
+
61
+ | ID | Vulnerability | Status |
62
+ |----|--------------|--------|
63
+ | ASI01 | Prompt Injection | ✅ |
64
+ | ASI02 | Tool Abuse / Unintended Tool Use | ✅ |
65
+ | ASI03 | Data Exfiltration / Sensitive Data Leakage | ✅ |
66
+ | ASI04 | Unauthorized Actions / Excessive Agency | ✅ |
67
+ | ASI05 | Supply Chain / Untrusted Components | ✅ |
68
+ | ASI06 | Insecure Output Handling | ✅ |
69
+ | ASI07 | Credential / Secret Exposure | ✅ |
70
+ | ASI08 | Context Window Manipulation | ✅ |
71
+ | ASI09 | Agent Loop Exploitation | ✅ |
72
+ | ASI10 | Trust Boundary Violation | ✅ |
73
+
74
+ ## CI/CD Integration
75
+
76
+ ### GitHub Actions
77
+
78
+ ```yaml
79
+ name: Security Scan
80
+ on: [push, pull_request]
81
+
82
+ jobs:
83
+ agentguard:
84
+ runs-on: ubuntu-latest
85
+ steps:
86
+ - uses: actions/checkout@v4
87
+ - uses: actions/setup-python@v5
88
+ with:
89
+ python-version: '3.12'
90
+ - run: pip install agentguard
91
+ - run: agentguard . --format sarif > results.sarif
92
+ - uses: github/codeql-action/upload-sarif@v3
93
+ with:
94
+ sarif_file: results.sarif
95
+ ```
96
+
97
+ ### Pre-commit Hook
98
+
99
+ ```yaml
100
+ repos:
101
+ - repo: https://github.com/dockfixlabs/agentguard
102
+ rev: v0.1.0
103
+ hooks:
104
+ - id: agentguard
105
+ args: ["--min-severity", "HIGH"]
106
+ ```
107
+
108
+ ## Programmatic Usage
109
+
110
+ ```python
111
+ from agentguard.scanner import scan_directory
112
+ from agentguard.reporter import json_report
113
+
114
+ result = scan_directory("src/")
115
+
116
+ print(f"Found {len(result.findings)} issues")
117
+ print(f"Critical: {result.critical_count}")
118
+ print(f"High: {result.high_count}")
119
+
120
+ for finding in result.findings:
121
+ print(f" [{finding.severity}] {finding.rule_name} at {finding.file}:{finding.line}")
122
+ ```
123
+
124
+ ## Detection Rules
125
+
126
+ ### ASI01 — Prompt Injection
127
+ Detects untrusted user input being concatenated into LLM prompts via f-strings, `.format()`, or string concatenation.
128
+
129
+ ### ASI02 — Tool Abuse
130
+ Flags agents with access to `exec()`, `subprocess`, `os.system()`, shell tools, unrestricted tool registration, and missing rate limits.
131
+
132
+ ### ASI03 — Data Exfiltration
133
+ Detects outbound HTTP requests to external URLs, webhook configurations, DNS exfiltration patterns, and secret+network correlation.
134
+
135
+ ### ASI06 — Unsafe Eval
136
+ Flags `eval()`, `exec()`, `compile()` with user input, `pickle.load()`, `yaml.load()` without SafeLoader, `subprocess(shell=True)`.
137
+
138
+ ### ASI07 — Credential Exposure
139
+ Detects hardcoded API keys (sk-, ghp_, AKIA), private keys, connection strings with passwords, and crypto wallet seeds.
140
+
141
+ ### ASI08 — Context Manipulation
142
+ Flags missing token limits, unbounded context accumulation, and large files loaded directly into LLM context.
143
+
144
+ ### ASI10 — Trust Boundary Violation
145
+ Detects agents running as root, host filesystem access, self-modifying code, and direct database access with user input.
146
+
147
+ ## MCP Server Mode
148
+
149
+ Scan agent code directly from Claude Code, Cursor, or any MCP-compatible client:
150
+
151
+ ```json
152
+ // ~/.claude/claude_code_config.json
153
+ {
154
+ "mcpServers": {
155
+ "agentguard": {
156
+ "command": "python3",
157
+ "args": ["-m", "agentguard.mcp_server"]
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ Then ask Claude: *"Scan my agent code for security vulnerabilities"*
164
+
165
+ ### MCP Tools
166
+ - `scan_agent_code` — Scan a directory/file for vulnerabilities
167
+ - `list_rules` — List all detection rules and OWASP mapping
168
+ - `get_finding_details` — Get remediation guidance for a specific rule
169
+
170
+ ## Roadmap
171
+
172
+ - [x] OWASP ASI Top 10 — all 10 categories covered
173
+ - [x] MCP server mode — scan from Claude Code/Cursor
174
+ - [x] SARIF output — GitHub Code Scanning integration
175
+ - [ ] PyPI publication
176
+ - [ ] Semantic analysis with LLM-assisted code review
177
+ - [ ] Language support: Rust, Go, Java
178
+ - [ ] VS Code extension
179
+ - [ ] GitHub App for automated PR reviews
180
+
181
+ ## Contributing
182
+
183
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and feature requests welcome.
184
+
185
+ ## Security
186
+
187
+ See [SECURITY.md](SECURITY.md). Report vulnerabilities privately — do not open public issues.
188
+
189
+ ## License
190
+
191
+ MIT — see [LICENSE](LICENSE).
192
+
193
+ ---
194
+
195
+ Built by [Dockfix Labs](https://github.com/dockfixlabs). Built for the AI agent era.
@@ -0,0 +1,3 @@
1
+ """AgentGuard — Autonomous security scanner for AI agents."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,67 @@
1
+ """CLI entry point for AgentGuard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+ from rich.console import Console
10
+
11
+ from agentguard.scanner import scan_directory
12
+ from agentguard.reporter import print_report, json_report, sarif_report
13
+
14
+
15
+ @click.command()
16
+ @click.argument("target", default=".", type=click.Path(exists=True))
17
+ @click.option(
18
+ "--format", "output_format",
19
+ type=click.Choice(["text", "json", "sarif"], case_sensitive=False),
20
+ default="text",
21
+ help="Output format (default: text)",
22
+ )
23
+ @click.option(
24
+ "--exit-code/--no-exit-code",
25
+ default=True,
26
+ help="Exit with non-zero code if findings found (default: True)",
27
+ )
28
+ @click.option(
29
+ "--min-severity",
30
+ type=click.Choice(["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"], case_sensitive=False),
31
+ default="INFO",
32
+ help="Minimum severity to report (default: INFO = all)",
33
+ )
34
+ def main(target: str, output_format: str, exit_code: bool, min_severity: str) -> None:
35
+ """AgentGuard — Scan AI agent code for security vulnerabilities.
36
+
37
+ TARGET: Directory or file to scan (default: current directory)
38
+
39
+ Examples:
40
+
41
+ agentguard . # Scan current directory
42
+ agentguard src/ --format json # JSON output
43
+ agentguard . --format sarif # SARIF for CI/CD
44
+ agentguard . --min-severity HIGH # Only HIGH+ findings
45
+ """
46
+ console = Console()
47
+ result = scan_directory(target)
48
+
49
+ # Filter by severity
50
+ severity_order = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]
51
+ min_idx = severity_order.index(min_severity.upper())
52
+ allowed = severity_order[:min_idx + 1]
53
+ result.findings = [f for f in result.findings if f.severity.value in allowed]
54
+
55
+ if output_format.lower() == "json":
56
+ print(json_report(result))
57
+ elif output_format.lower() == "sarif":
58
+ print(sarif_report(result))
59
+ else:
60
+ print_report(result, console)
61
+
62
+ if exit_code and not result.clean:
63
+ sys.exit(1)
64
+
65
+
66
+ if __name__ == "__main__":
67
+ main()