agentguardp 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentguard/__init__.py +9 -0
- agentguard/cli.py +222 -0
- agentguard/desktop.py +635 -0
- agentguard/gui.py +598 -0
- agentguard/license_gen.py +181 -0
- agentguard/license_public.pem +9 -0
- agentguard/license_verify.py +126 -0
- agentguard/pipeline.py +247 -0
- agentguard/reporter/__init__.py +1 -0
- agentguard/reporter/reporter.py +199 -0
- agentguard/rules/__init__.py +1 -0
- agentguard/rules/python_rules.py +450 -0
- agentguard/scanner/__init__.py +1 -0
- agentguard/scanner/bandit_adapter.py +70 -0
- agentguard/scanner/bandit_rules.py +53 -0
- agentguard/scanner/code_scanner.py +329 -0
- agentguard/scanner/llm_heuristic.py +688 -0
- agentguard/scanner/llm_review.py +257 -0
- agentguard/scanner/ml_filter.py +290 -0
- agentguard/xhls_security/__init__.py +3 -0
- agentguard/xhls_security/binary_sanitizer.py +296 -0
- agentguard/xhls_security/model_guard.py +453 -0
- agentguard/xhls_security/security_hardening.py +347 -0
- agentguard/xhls_security/semantic_gate.py +421 -0
- agentguardp-0.5.0.dist-info/METADATA +235 -0
- agentguardp-0.5.0.dist-info/RECORD +34 -0
- agentguardp-0.5.0.dist-info/WHEEL +5 -0
- agentguardp-0.5.0.dist-info/entry_points.txt +3 -0
- agentguardp-0.5.0.dist-info/licenses/LICENSE +21 -0
- agentguardp-0.5.0.dist-info/top_level.txt +2 -0
- fixer/__init__.py +3 -0
- fixer/code_fixer.py +380 -0
- fixer/fixtures/sample_bad.py +37 -0
- fixer/fixtures/sample_test.py +39 -0
agentguard/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentGuard v0.1 — AI Agent Code Review & Security Compliance Platform
|
|
3
|
+
=====================================================================
|
|
4
|
+
|
|
5
|
+
Powered by XHLS security modules: model_guard + security_hardening + binary_sanitizer.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.5.0"
|
|
9
|
+
__author__ = "XHLS / Xiaohei"
|
agentguard/cli.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentGuard CLI v0.3.0
|
|
3
|
+
==========================
|
|
4
|
+
Command-line interface for AgentGuard 鈥?Scanner + Auto-Fixer.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
agentguard scan ./my-project
|
|
8
|
+
agentguard fix findings.json --mode fix
|
|
9
|
+
agentguard activate AGENTGUARD-xxxx.yyyy
|
|
10
|
+
agentguard status
|
|
11
|
+
agentguard deactivate
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# Fix Unicode emoji output on Windows GBK terminals
|
|
18
|
+
if sys.platform == "win32":
|
|
19
|
+
for stream in ("stdout", "stderr"):
|
|
20
|
+
try:
|
|
21
|
+
s = getattr(sys, stream)
|
|
22
|
+
if s is not None and hasattr(s, "reconfigure"):
|
|
23
|
+
s.reconfigure(encoding="utf-8", errors="replace")
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
from .scanner.code_scanner import CodeScanner
|
|
28
|
+
from .reporter.reporter import terminal_report, json_report, sarif_report, markdown_report
|
|
29
|
+
from .license_verify import (
|
|
30
|
+
activate as license_activate,
|
|
31
|
+
get_active_tier,
|
|
32
|
+
deactivate as license_deactivate,
|
|
33
|
+
get_machine_hash,
|
|
34
|
+
)
|
|
35
|
+
from fixer.code_fixer import run as fixer_run
|
|
36
|
+
from .pipeline import cmd_pipeline
|
|
37
|
+
from .desktop import serve as desktop_serve
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def scan(
|
|
41
|
+
path: str = ".",
|
|
42
|
+
*,
|
|
43
|
+
format: str = "terminal",
|
|
44
|
+
output: str = "",
|
|
45
|
+
verbose: bool = False,
|
|
46
|
+
tier: str = None,
|
|
47
|
+
files: list = None,
|
|
48
|
+
labs: bool = False,
|
|
49
|
+
bandit: bool = False,
|
|
50
|
+
):
|
|
51
|
+
"""Scan a directory or files for security issues."""
|
|
52
|
+
if tier is None:
|
|
53
|
+
tier = get_active_tier()
|
|
54
|
+
|
|
55
|
+
target = Path(path)
|
|
56
|
+
if not target.exists():
|
|
57
|
+
print(f"Error: Path not found: {path}", file=sys.stderr)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
scanner = CodeScanner(tier=tier)
|
|
61
|
+
result = scanner.scan_directory(str(target), files=files)
|
|
62
|
+
|
|
63
|
+
if bandit:
|
|
64
|
+
try:
|
|
65
|
+
from .scanner.bandit_adapter import run_bandit
|
|
66
|
+
bandit_findings = run_bandit(str(target))
|
|
67
|
+
result.findings.extend(bandit_findings)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"[Bandit] Engine not available: {e}", file=sys.stderr)
|
|
70
|
+
|
|
71
|
+
reporters = {
|
|
72
|
+
"terminal": terminal_report,
|
|
73
|
+
"json": json_report,
|
|
74
|
+
"sarif": sarif_report,
|
|
75
|
+
"markdown": markdown_report,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
reporter = reporters.get(format, terminal_report)
|
|
79
|
+
if format == "terminal":
|
|
80
|
+
report_text = reporter(result, verbose=verbose)
|
|
81
|
+
else:
|
|
82
|
+
report_text = reporter(result)
|
|
83
|
+
|
|
84
|
+
if output:
|
|
85
|
+
Path(output).write_text(report_text, encoding="utf-8")
|
|
86
|
+
print(f"Report saved to: {output}")
|
|
87
|
+
else:
|
|
88
|
+
print(report_text)
|
|
89
|
+
|
|
90
|
+
# [Labs] LLM heuristic discovery
|
|
91
|
+
if labs:
|
|
92
|
+
print("\n─── [Labs] LLM Heuristic Discovery ───")
|
|
93
|
+
print("Running LLM heuristic review (this may take a while)...")
|
|
94
|
+
try:
|
|
95
|
+
from .scanner.llm_heuristic import heuristic_scan_files
|
|
96
|
+
file_map = {f: Path(f).read_text(encoding='utf-8', errors='replace') for f in result.findings[:1]}
|
|
97
|
+
# Collect unique scanned files
|
|
98
|
+
scanned_files = list(set(str(f.file) if hasattr(f, 'file') else str(f) for f in result.findings))
|
|
99
|
+
# To scan all python files, not just files with findings:
|
|
100
|
+
py_files = list(Path(path).rglob('*.py')) if Path(path).is_dir() else ([Path(path)] if Path(path).suffix == '.py' else [])
|
|
101
|
+
file_map = {}
|
|
102
|
+
for pf in py_files:
|
|
103
|
+
try:
|
|
104
|
+
file_map[str(pf)] = pf.read_text(encoding='utf-8', errors='replace')
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
# Build existing findings map
|
|
108
|
+
existing_by_file = {}
|
|
109
|
+
for f in result.findings:
|
|
110
|
+
fpath = f.file if hasattr(f, 'file') else str(f)
|
|
111
|
+
existing_by_file.setdefault(fpath, []).append({
|
|
112
|
+
'rule_id': f.rule_id if hasattr(f, 'rule_id') else '?',
|
|
113
|
+
'line': f.line if hasattr(f, 'line') else 0,
|
|
114
|
+
'message': f.message if hasattr(f, 'message') else ''
|
|
115
|
+
})
|
|
116
|
+
heuristic_findings = heuristic_scan_files(file_map, existing_by_file)
|
|
117
|
+
if heuristic_findings:
|
|
118
|
+
from .scanner.llm_heuristic import format_heuristic_results
|
|
119
|
+
print(format_heuristic_results(heuristic_findings))
|
|
120
|
+
else:
|
|
121
|
+
print(" No new risks found by LLM heuristic.")
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f" Labs heuristic unavailable: {e}")
|
|
124
|
+
|
|
125
|
+
if not result.passed:
|
|
126
|
+
sys.exit(1)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def main():
|
|
130
|
+
"""Entry point for console_scripts."""
|
|
131
|
+
import argparse
|
|
132
|
+
|
|
133
|
+
parser = argparse.ArgumentParser(
|
|
134
|
+
prog="agentguard",
|
|
135
|
+
description="AgentGuard 鈥?AI Code Security Scanner + Auto-Fixer",
|
|
136
|
+
)
|
|
137
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
138
|
+
|
|
139
|
+
# ---- scan ----
|
|
140
|
+
scan_parser = subparsers.add_parser("scan", help="Scan code for security issues")
|
|
141
|
+
scan_parser.add_argument("path", default=".", nargs="?", help="Directory or file to scan")
|
|
142
|
+
scan_parser.add_argument("--format", "-f", default="terminal",
|
|
143
|
+
choices=["terminal", "json", "sarif", "markdown"],
|
|
144
|
+
help="Output format (default: terminal)")
|
|
145
|
+
scan_parser.add_argument("--output", "-o", default="", help="Output file path")
|
|
146
|
+
scan_parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
|
147
|
+
scan_parser.add_argument("--tier", default=None, choices=["free", "pro"],
|
|
148
|
+
help="License tier (default: auto-detect from activated license)")
|
|
149
|
+
scan_parser.add_argument("--version", action="version", version="AgentGuard v0.5.0")
|
|
150
|
+
scan_parser.add_argument("--bandit", action="store_true", help="Use Bandit engine (100+ rules) in addition to built-in")
|
|
151
|
+
scan_parser.add_argument("--labs", action="store_true", help="Enable [Labs] LLM heuristic discovery (experimental)")
|
|
152
|
+
|
|
153
|
+
# ---- activate ----
|
|
154
|
+
activate_parser = subparsers.add_parser("activate", help="Activate a license key")
|
|
155
|
+
activate_parser.add_argument("key", help="License key (e.g., AGENTGUARD-xxxx.yyyy)")
|
|
156
|
+
|
|
157
|
+
# ---- status ----
|
|
158
|
+
subparsers.add_parser("status", help="Show license status and machine hash")
|
|
159
|
+
subparsers.add_parser("serve", help="Start desktop web GUI")
|
|
160
|
+
|
|
161
|
+
# ---- deactivate ----
|
|
162
|
+
subparsers.add_parser("deactivate", help="Deactivate license on this machine")
|
|
163
|
+
|
|
164
|
+
# ---- fix ----
|
|
165
|
+
fix_parser = subparsers.add_parser("fix", help="Auto-fix findings from a previous scan")
|
|
166
|
+
fix_parser.add_argument("findings", help="Path to findings JSON file from agentguard scan --format json")
|
|
167
|
+
fix_parser.add_argument("--mode", "-m", default="dry-run",
|
|
168
|
+
choices=["safe", "fix", "dry-run"],
|
|
169
|
+
help="Fix mode: safe, fix, dry-run (default)")
|
|
170
|
+
fix_parser.add_argument("--write", "-w", action="store_true", help="Write fixes to disk")
|
|
171
|
+
|
|
172
|
+
# ---- pipeline (scan + filter + fix) ----
|
|
173
|
+
pipe_parser = subparsers.add_parser("pipeline", aliases=["auto-fix"], help="Full pipeline: scan -> filter -> fix")
|
|
174
|
+
pipe_parser.add_argument("path", default=".", nargs="?", help="Directory or file to scan")
|
|
175
|
+
pipe_parser.add_argument("--mode", "-m", default="dry-run",
|
|
176
|
+
choices=["safe", "fix", "dry-run"],
|
|
177
|
+
help="Fix mode (default: dry-run)")
|
|
178
|
+
pipe_parser.add_argument("--ds", action="store_true", help="Enable DeepSeek secondary review")
|
|
179
|
+
pipe_parser.add_argument("--write", "-w", action="store_true", help="Write fixes to disk")
|
|
180
|
+
pipe_parser.add_argument("--bandit", action="store_true", help="Use Bandit engine (100+ rules) instead of built-in")
|
|
181
|
+
pipe_parser.add_argument("--labs", action="store_true", help="Enable [Labs] LLM heuristic discovery (experimental)")
|
|
182
|
+
|
|
183
|
+
args = parser.parse_args()
|
|
184
|
+
|
|
185
|
+
if args.command == "scan":
|
|
186
|
+
scan(
|
|
187
|
+
path=args.path,
|
|
188
|
+
format=args.format,
|
|
189
|
+
output=args.output,
|
|
190
|
+
verbose=args.verbose,
|
|
191
|
+
tier=args.tier,
|
|
192
|
+
labs=getattr(args, 'labs', False),
|
|
193
|
+
bandit=getattr(args, 'bandit', False),
|
|
194
|
+
)
|
|
195
|
+
elif args.command == "activate":
|
|
196
|
+
license_activate(args.key)
|
|
197
|
+
elif args.command == "status":
|
|
198
|
+
tier = get_active_tier()
|
|
199
|
+
print(f"Tier : {tier}")
|
|
200
|
+
print(f"Machine ID: {get_machine_hash()}")
|
|
201
|
+
print(f"Status : {'Activated' if tier != 'free' else 'Free (no license)'}")
|
|
202
|
+
elif args.command == "deactivate":
|
|
203
|
+
license_deactivate()
|
|
204
|
+
print("License deactivated. Back to free tier.")
|
|
205
|
+
elif args.command == "fix":
|
|
206
|
+
result = fixer_run(args.findings, mode=args.mode, write=args.write)
|
|
207
|
+
print(f"\nFixed: {result['fixed_count']} | Manual: {result['manual_count']} | Files: {result['files_changed']} | Mode: {result['mode']}")
|
|
208
|
+
if result.get('diff'):
|
|
209
|
+
print(result['diff'])
|
|
210
|
+
for r in result.get('results', []):
|
|
211
|
+
print(f" [{r['rule_id']}] L{r['line']}: {r.get('reason', r.get('fixed', ''))}")
|
|
212
|
+
elif args.command in ("pipeline", "auto-fix"):
|
|
213
|
+
cmd_pipeline(args)
|
|
214
|
+
elif args.command == "serve":
|
|
215
|
+
desktop_serve()
|
|
216
|
+
else:
|
|
217
|
+
from .gui import main as gui_main; gui_main()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
main()
|
|
222
|
+
|