aiptx 2.0.7__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.
- aipt_v2/__init__.py +110 -0
- aipt_v2/__main__.py +24 -0
- aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
- aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
- aipt_v2/agents/__init__.py +46 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/exploit_agent.py +688 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +957 -0
- aipt_v2/browser/__init__.py +31 -0
- aipt_v2/browser/automation.py +458 -0
- aipt_v2/browser/crawler.py +453 -0
- aipt_v2/cli.py +2933 -0
- aipt_v2/compliance/__init__.py +71 -0
- aipt_v2/compliance/compliance_report.py +449 -0
- aipt_v2/compliance/framework_mapper.py +424 -0
- aipt_v2/compliance/nist_mapping.py +345 -0
- aipt_v2/compliance/owasp_mapping.py +330 -0
- aipt_v2/compliance/pci_mapping.py +297 -0
- aipt_v2/config.py +341 -0
- aipt_v2/core/__init__.py +43 -0
- aipt_v2/core/agent.py +630 -0
- aipt_v2/core/llm.py +395 -0
- aipt_v2/core/memory.py +305 -0
- aipt_v2/core/ptt.py +329 -0
- aipt_v2/database/__init__.py +14 -0
- aipt_v2/database/models.py +232 -0
- aipt_v2/database/repository.py +384 -0
- aipt_v2/docker/__init__.py +23 -0
- aipt_v2/docker/builder.py +260 -0
- aipt_v2/docker/manager.py +222 -0
- aipt_v2/docker/sandbox.py +371 -0
- aipt_v2/evasion/__init__.py +58 -0
- aipt_v2/evasion/request_obfuscator.py +272 -0
- aipt_v2/evasion/tls_fingerprint.py +285 -0
- aipt_v2/evasion/ua_rotator.py +301 -0
- aipt_v2/evasion/waf_bypass.py +439 -0
- aipt_v2/execution/__init__.py +23 -0
- aipt_v2/execution/executor.py +302 -0
- aipt_v2/execution/parser.py +544 -0
- aipt_v2/execution/terminal.py +337 -0
- aipt_v2/health.py +437 -0
- aipt_v2/intelligence/__init__.py +194 -0
- aipt_v2/intelligence/adaptation.py +474 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/correlation.py +536 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -0
- aipt_v2/intelligence/knowledge_graph.py +590 -0
- aipt_v2/intelligence/learning.py +626 -0
- aipt_v2/intelligence/llm_analyzer.py +502 -0
- aipt_v2/intelligence/llm_tool_selector.py +518 -0
- aipt_v2/intelligence/payload_generator.py +562 -0
- aipt_v2/intelligence/rag.py +239 -0
- aipt_v2/intelligence/scope.py +442 -0
- aipt_v2/intelligence/searchers/__init__.py +5 -0
- aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
- aipt_v2/intelligence/searchers/github_searcher.py +467 -0
- aipt_v2/intelligence/searchers/google_searcher.py +281 -0
- aipt_v2/intelligence/tools.json +443 -0
- aipt_v2/intelligence/triage.py +670 -0
- aipt_v2/interactive_shell.py +559 -0
- aipt_v2/interface/__init__.py +5 -0
- aipt_v2/interface/cli.py +230 -0
- aipt_v2/interface/main.py +501 -0
- aipt_v2/interface/tui.py +1276 -0
- aipt_v2/interface/utils.py +583 -0
- aipt_v2/llm/__init__.py +39 -0
- aipt_v2/llm/config.py +26 -0
- aipt_v2/llm/llm.py +514 -0
- aipt_v2/llm/memory.py +214 -0
- aipt_v2/llm/request_queue.py +89 -0
- aipt_v2/llm/utils.py +89 -0
- aipt_v2/local_tool_installer.py +1467 -0
- aipt_v2/models/__init__.py +15 -0
- aipt_v2/models/findings.py +295 -0
- aipt_v2/models/phase_result.py +224 -0
- aipt_v2/models/scan_config.py +207 -0
- aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
- aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
- aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
- aipt_v2/monitoring/prometheus.yml +60 -0
- aipt_v2/orchestration/__init__.py +52 -0
- aipt_v2/orchestration/pipeline.py +398 -0
- aipt_v2/orchestration/progress.py +300 -0
- aipt_v2/orchestration/scheduler.py +296 -0
- aipt_v2/orchestrator.py +2427 -0
- aipt_v2/payloads/__init__.py +27 -0
- aipt_v2/payloads/cmdi.py +150 -0
- aipt_v2/payloads/sqli.py +263 -0
- aipt_v2/payloads/ssrf.py +204 -0
- aipt_v2/payloads/templates.py +222 -0
- aipt_v2/payloads/traversal.py +166 -0
- aipt_v2/payloads/xss.py +204 -0
- aipt_v2/prompts/__init__.py +60 -0
- aipt_v2/proxy/__init__.py +29 -0
- aipt_v2/proxy/history.py +352 -0
- aipt_v2/proxy/interceptor.py +452 -0
- aipt_v2/recon/__init__.py +44 -0
- aipt_v2/recon/dns.py +241 -0
- aipt_v2/recon/osint.py +367 -0
- aipt_v2/recon/subdomain.py +372 -0
- aipt_v2/recon/tech_detect.py +311 -0
- aipt_v2/reports/__init__.py +17 -0
- aipt_v2/reports/generator.py +313 -0
- aipt_v2/reports/html_report.py +378 -0
- aipt_v2/runtime/__init__.py +53 -0
- aipt_v2/runtime/base.py +30 -0
- aipt_v2/runtime/docker.py +401 -0
- aipt_v2/runtime/local.py +346 -0
- aipt_v2/runtime/tool_server.py +205 -0
- aipt_v2/runtime/vps.py +830 -0
- aipt_v2/scanners/__init__.py +28 -0
- aipt_v2/scanners/base.py +273 -0
- aipt_v2/scanners/nikto.py +244 -0
- aipt_v2/scanners/nmap.py +402 -0
- aipt_v2/scanners/nuclei.py +273 -0
- aipt_v2/scanners/web.py +454 -0
- aipt_v2/scripts/security_audit.py +366 -0
- aipt_v2/setup_wizard.py +941 -0
- aipt_v2/skills/__init__.py +80 -0
- aipt_v2/skills/agents/__init__.py +14 -0
- aipt_v2/skills/agents/api_tester.py +706 -0
- aipt_v2/skills/agents/base.py +477 -0
- aipt_v2/skills/agents/code_review.py +459 -0
- aipt_v2/skills/agents/security_agent.py +336 -0
- aipt_v2/skills/agents/web_pentest.py +818 -0
- aipt_v2/skills/prompts/__init__.py +647 -0
- aipt_v2/system_detector.py +539 -0
- aipt_v2/telemetry/__init__.py +7 -0
- aipt_v2/telemetry/tracer.py +347 -0
- aipt_v2/terminal/__init__.py +28 -0
- aipt_v2/terminal/executor.py +400 -0
- aipt_v2/terminal/sandbox.py +350 -0
- aipt_v2/tools/__init__.py +44 -0
- aipt_v2/tools/active_directory/__init__.py +78 -0
- aipt_v2/tools/active_directory/ad_config.py +238 -0
- aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
- aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
- aipt_v2/tools/active_directory/ldap_enum.py +533 -0
- aipt_v2/tools/active_directory/smb_attacks.py +505 -0
- aipt_v2/tools/agents_graph/__init__.py +19 -0
- aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
- aipt_v2/tools/api_security/__init__.py +76 -0
- aipt_v2/tools/api_security/api_discovery.py +608 -0
- aipt_v2/tools/api_security/graphql_scanner.py +622 -0
- aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
- aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
- aipt_v2/tools/browser/__init__.py +5 -0
- aipt_v2/tools/browser/browser_actions.py +238 -0
- aipt_v2/tools/browser/browser_instance.py +535 -0
- aipt_v2/tools/browser/tab_manager.py +344 -0
- aipt_v2/tools/cloud/__init__.py +70 -0
- aipt_v2/tools/cloud/cloud_config.py +273 -0
- aipt_v2/tools/cloud/cloud_scanner.py +639 -0
- aipt_v2/tools/cloud/prowler_tool.py +571 -0
- aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
- aipt_v2/tools/executor.py +307 -0
- aipt_v2/tools/parser.py +408 -0
- aipt_v2/tools/proxy/__init__.py +5 -0
- aipt_v2/tools/proxy/proxy_actions.py +103 -0
- aipt_v2/tools/proxy/proxy_manager.py +789 -0
- aipt_v2/tools/registry.py +196 -0
- aipt_v2/tools/scanners/__init__.py +343 -0
- aipt_v2/tools/scanners/acunetix_tool.py +712 -0
- aipt_v2/tools/scanners/burp_tool.py +631 -0
- aipt_v2/tools/scanners/config.py +156 -0
- aipt_v2/tools/scanners/nessus_tool.py +588 -0
- aipt_v2/tools/scanners/zap_tool.py +612 -0
- aipt_v2/tools/terminal/__init__.py +5 -0
- aipt_v2/tools/terminal/terminal_actions.py +37 -0
- aipt_v2/tools/terminal/terminal_manager.py +153 -0
- aipt_v2/tools/terminal/terminal_session.py +449 -0
- aipt_v2/tools/tool_processing.py +108 -0
- aipt_v2/utils/__init__.py +17 -0
- aipt_v2/utils/logging.py +202 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aipt_v2/verify_install.py +793 -0
- aiptx-2.0.7.dist-info/METADATA +345 -0
- aiptx-2.0.7.dist-info/RECORD +187 -0
- aiptx-2.0.7.dist-info/WHEEL +5 -0
- aiptx-2.0.7.dist-info/entry_points.txt +7 -0
- aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.7.dist-info/top_level.txt +1 -0
aipt_v2/cli.py
ADDED
|
@@ -0,0 +1,2933 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPTX Command Line Interface
|
|
3
|
+
============================
|
|
4
|
+
|
|
5
|
+
Entry point for the AIPTX command-line tool.
|
|
6
|
+
Zero-click installation: pipx install aiptx
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
aiptx setup # Run setup wizard (first-time)
|
|
10
|
+
aiptx scan example.com # Run security scan
|
|
11
|
+
aiptx scan example.com --full # Comprehensive scan
|
|
12
|
+
aiptx api # Start REST API
|
|
13
|
+
aiptx status # Check configuration
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import asyncio
|
|
18
|
+
import sys
|
|
19
|
+
import os
|
|
20
|
+
import warnings
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
# Suppress noisy warnings for cleaner user experience
|
|
24
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
25
|
+
warnings.filterwarnings("ignore", message=".*urllib3.*OpenSSL.*")
|
|
26
|
+
warnings.filterwarnings("ignore", message=".*NotOpenSSLWarning.*")
|
|
27
|
+
|
|
28
|
+
# Set default log level to WARNING before any imports that might log
|
|
29
|
+
os.environ.setdefault("AIPT_LOG_LEVEL", "WARNING")
|
|
30
|
+
|
|
31
|
+
# Handle imports for both installed package and local development
|
|
32
|
+
try:
|
|
33
|
+
from . import __version__
|
|
34
|
+
from .config import get_config, validate_config_for_features, reload_config
|
|
35
|
+
from .utils.logging import setup_logging, logger
|
|
36
|
+
from .setup_wizard import is_configured, prompt_first_run_setup, run_setup_wizard
|
|
37
|
+
except ImportError:
|
|
38
|
+
# Local development fallback
|
|
39
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
40
|
+
from __init__ import __version__
|
|
41
|
+
from config import get_config, validate_config_for_features, reload_config
|
|
42
|
+
from utils.logging import setup_logging, logger
|
|
43
|
+
from setup_wizard import is_configured, prompt_first_run_setup, run_setup_wizard
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main():
|
|
47
|
+
"""Main CLI entry point."""
|
|
48
|
+
# Handle keyboard interrupts gracefully at the top level
|
|
49
|
+
import signal
|
|
50
|
+
|
|
51
|
+
def signal_handler(signum, frame):
|
|
52
|
+
"""Handle interrupt signals gracefully."""
|
|
53
|
+
from rich.console import Console
|
|
54
|
+
Console().print("\n[yellow]Operation cancelled.[/yellow]")
|
|
55
|
+
sys.exit(130) # Standard exit code for Ctrl+C
|
|
56
|
+
|
|
57
|
+
# Register signal handlers for graceful shutdown
|
|
58
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
59
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
60
|
+
|
|
61
|
+
parser = argparse.ArgumentParser(
|
|
62
|
+
prog="aiptx",
|
|
63
|
+
description="AIPTX - AI-Powered Penetration Testing Framework",
|
|
64
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
65
|
+
epilog="""
|
|
66
|
+
Examples:
|
|
67
|
+
aiptx scan example.com Run basic scan
|
|
68
|
+
aiptx scan example.com --full Run comprehensive scan
|
|
69
|
+
aiptx scan example.com --ai AI-guided scanning
|
|
70
|
+
aiptx api Start REST API server
|
|
71
|
+
aiptx status Check configuration status
|
|
72
|
+
aiptx version Show version information
|
|
73
|
+
|
|
74
|
+
First-time setup:
|
|
75
|
+
aiptx setup Interactive configuration wizard
|
|
76
|
+
|
|
77
|
+
Installation:
|
|
78
|
+
pipx install aiptx Zero-click install
|
|
79
|
+
pip install aiptx[full] Install with all features
|
|
80
|
+
""",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
parser.add_argument(
|
|
84
|
+
"--version", "-V",
|
|
85
|
+
action="version",
|
|
86
|
+
version=f"AIPTX v{__version__}",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"--verbose", "-v",
|
|
91
|
+
action="count",
|
|
92
|
+
default=0,
|
|
93
|
+
help="Increase verbosity (use -vv for debug)",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
parser.add_argument(
|
|
97
|
+
"--json",
|
|
98
|
+
action="store_true",
|
|
99
|
+
help="Output in JSON format",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
103
|
+
|
|
104
|
+
# Scan command
|
|
105
|
+
scan_parser = subparsers.add_parser("scan", help="Run security scan")
|
|
106
|
+
scan_parser.add_argument("target", help="Target URL or domain")
|
|
107
|
+
scan_parser.add_argument("--client", "-c", help="Client name")
|
|
108
|
+
scan_parser.add_argument("--output", "-o", help="Output directory")
|
|
109
|
+
scan_parser.add_argument(
|
|
110
|
+
"--mode", "-m",
|
|
111
|
+
choices=["quick", "standard", "full", "ai"],
|
|
112
|
+
default="standard",
|
|
113
|
+
help="Scan mode (default: standard)",
|
|
114
|
+
)
|
|
115
|
+
scan_parser.add_argument("--full", action="store_true", help="Run full comprehensive scan")
|
|
116
|
+
scan_parser.add_argument("--ai", action="store_true", help="Enable AI-guided scanning")
|
|
117
|
+
scan_parser.add_argument("--use-vps", action="store_true", help="Use VPS for tool execution")
|
|
118
|
+
scan_parser.add_argument("--use-acunetix", action="store_true", help="Include Acunetix scan")
|
|
119
|
+
scan_parser.add_argument("--use-burp", action="store_true", help="Include Burp Suite scan")
|
|
120
|
+
scan_parser.add_argument("--skip-recon", action="store_true", help="Skip reconnaissance phase")
|
|
121
|
+
scan_parser.add_argument("--quiet", "-q", action="store_true", help="Quiet mode - minimal output")
|
|
122
|
+
scan_parser.add_argument("--no-stream", action="store_true", help="Don't stream command output (show progress only)")
|
|
123
|
+
scan_parser.add_argument("--check", action="store_true", help="Run pre-flight checks to validate config/connections before scan")
|
|
124
|
+
|
|
125
|
+
# API command
|
|
126
|
+
api_parser = subparsers.add_parser("api", help="Start REST API server")
|
|
127
|
+
# Security: Default to localhost to prevent accidental network exposure
|
|
128
|
+
api_parser.add_argument("--host", default="127.0.0.1", help="API host (default: 127.0.0.1, use 0.0.0.0 for network access)")
|
|
129
|
+
api_parser.add_argument("--port", "-p", type=int, default=8000, help="API port (default: 8000)")
|
|
130
|
+
api_parser.add_argument("--reload", action="store_true", help="Enable auto-reload for development")
|
|
131
|
+
|
|
132
|
+
# Status command
|
|
133
|
+
subparsers.add_parser("status", help="Check configuration and dependencies")
|
|
134
|
+
|
|
135
|
+
# Test command - validate all configurations
|
|
136
|
+
test_parser = subparsers.add_parser("test", help="Test and validate all configurations")
|
|
137
|
+
test_parser.add_argument("--llm", action="store_true", help="Test LLM API key only")
|
|
138
|
+
test_parser.add_argument("--vps", action="store_true", help="Test VPS connection only")
|
|
139
|
+
test_parser.add_argument("--scanners", action="store_true", help="Test scanner integrations only")
|
|
140
|
+
test_parser.add_argument("--tools", action="store_true", help="Test local tool availability")
|
|
141
|
+
test_parser.add_argument("--all", "-a", action="store_true", help="Test everything (default)")
|
|
142
|
+
|
|
143
|
+
# Version command
|
|
144
|
+
subparsers.add_parser("version", help="Show detailed version information")
|
|
145
|
+
|
|
146
|
+
# Setup command
|
|
147
|
+
setup_parser = subparsers.add_parser("setup", help="Run interactive setup wizard")
|
|
148
|
+
setup_parser.add_argument(
|
|
149
|
+
"--force", "-f",
|
|
150
|
+
action="store_true",
|
|
151
|
+
help="Force reconfiguration even if already configured"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# VPS command with subcommands
|
|
155
|
+
vps_parser = subparsers.add_parser("vps", help="VPS remote execution management")
|
|
156
|
+
vps_subparsers = vps_parser.add_subparsers(dest="vps_command", help="VPS commands")
|
|
157
|
+
|
|
158
|
+
# vps setup - Install tools on VPS
|
|
159
|
+
vps_setup = vps_subparsers.add_parser("setup", help="Install security tools on VPS")
|
|
160
|
+
vps_setup.add_argument(
|
|
161
|
+
"--categories", "-c",
|
|
162
|
+
nargs="+",
|
|
163
|
+
choices=["recon", "scan", "exploit", "post_exploit", "api", "network"],
|
|
164
|
+
help="Tool categories to install (default: all)"
|
|
165
|
+
)
|
|
166
|
+
vps_setup.add_argument(
|
|
167
|
+
"--tools", "-t",
|
|
168
|
+
nargs="+",
|
|
169
|
+
help="Specific tools to install"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# vps status - Check VPS connection and tools
|
|
173
|
+
vps_subparsers.add_parser("status", help="Check VPS connection and installed tools")
|
|
174
|
+
|
|
175
|
+
# vps scan - Run scan from VPS
|
|
176
|
+
vps_scan = vps_subparsers.add_parser("scan", help="Run security scan from VPS")
|
|
177
|
+
vps_scan.add_argument("target", help="Target URL or domain")
|
|
178
|
+
vps_scan.add_argument(
|
|
179
|
+
"--mode", "-m",
|
|
180
|
+
choices=["quick", "standard", "full"],
|
|
181
|
+
default="standard",
|
|
182
|
+
help="Scan mode"
|
|
183
|
+
)
|
|
184
|
+
vps_scan.add_argument(
|
|
185
|
+
"--tools", "-t",
|
|
186
|
+
nargs="+",
|
|
187
|
+
help="Specific tools to run"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# vps script - Generate setup script
|
|
191
|
+
vps_script = vps_subparsers.add_parser("script", help="Generate VPS setup script")
|
|
192
|
+
vps_script.add_argument(
|
|
193
|
+
"--output", "-o",
|
|
194
|
+
help="Output file (default: stdout)"
|
|
195
|
+
)
|
|
196
|
+
vps_script.add_argument(
|
|
197
|
+
"--categories", "-c",
|
|
198
|
+
nargs="+",
|
|
199
|
+
help="Tool categories to include"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Verify command - Installation verification
|
|
203
|
+
verify_parser = subparsers.add_parser("verify", help="Verify installation and configuration")
|
|
204
|
+
verify_parser.add_argument(
|
|
205
|
+
"--quick", "-q",
|
|
206
|
+
action="store_true",
|
|
207
|
+
help="Run quick checks only"
|
|
208
|
+
)
|
|
209
|
+
verify_parser.add_argument(
|
|
210
|
+
"--fix",
|
|
211
|
+
action="store_true",
|
|
212
|
+
help="Attempt to auto-fix issues"
|
|
213
|
+
)
|
|
214
|
+
verify_parser.add_argument(
|
|
215
|
+
"--report", "-r",
|
|
216
|
+
help="Save markdown report to file"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Shell command - Interactive shell
|
|
220
|
+
shell_parser = subparsers.add_parser("shell", help="Start interactive security shell")
|
|
221
|
+
shell_parser.add_argument(
|
|
222
|
+
"--log", "-l",
|
|
223
|
+
help="Log session to file"
|
|
224
|
+
)
|
|
225
|
+
shell_parser.add_argument(
|
|
226
|
+
"--dir", "-d",
|
|
227
|
+
help="Working directory"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Tools command with subcommands
|
|
231
|
+
tools_parser = subparsers.add_parser("tools", help="Manage local security tools")
|
|
232
|
+
tools_subparsers = tools_parser.add_subparsers(dest="tools_command", help="Tools commands")
|
|
233
|
+
|
|
234
|
+
# tools install - Install security tools
|
|
235
|
+
tools_install = tools_subparsers.add_parser("install", help="Install security tools on local system")
|
|
236
|
+
tools_install.add_argument(
|
|
237
|
+
"--categories", "-c",
|
|
238
|
+
nargs="+",
|
|
239
|
+
choices=[
|
|
240
|
+
"recon", "scan", "exploit", "post_exploit", "api", "network",
|
|
241
|
+
"prerequisite", "active_directory", "cloud", "container",
|
|
242
|
+
"osint", "wireless", "web", "secrets", "mobile"
|
|
243
|
+
],
|
|
244
|
+
help="Tool categories to install (default: core tools)"
|
|
245
|
+
)
|
|
246
|
+
tools_install.add_argument(
|
|
247
|
+
"--tools", "-t",
|
|
248
|
+
nargs="+",
|
|
249
|
+
help="Specific tools to install"
|
|
250
|
+
)
|
|
251
|
+
tools_install.add_argument(
|
|
252
|
+
"--all", "-a",
|
|
253
|
+
action="store_true",
|
|
254
|
+
help="Install all available tools"
|
|
255
|
+
)
|
|
256
|
+
tools_install.add_argument(
|
|
257
|
+
"--core",
|
|
258
|
+
action="store_true",
|
|
259
|
+
help="Install only core essential tools (default)"
|
|
260
|
+
)
|
|
261
|
+
tools_install.add_argument(
|
|
262
|
+
"--no-sudo",
|
|
263
|
+
action="store_true",
|
|
264
|
+
help="Don't use sudo for installation"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# tools list - List available/installed tools
|
|
268
|
+
tools_list = tools_subparsers.add_parser("list", help="List available and installed tools")
|
|
269
|
+
tools_list.add_argument(
|
|
270
|
+
"--category", "-c",
|
|
271
|
+
choices=[
|
|
272
|
+
"recon", "scan", "exploit", "post_exploit", "api", "network",
|
|
273
|
+
"prerequisite", "active_directory", "cloud", "container",
|
|
274
|
+
"osint", "wireless", "web", "secrets", "mobile", "all"
|
|
275
|
+
],
|
|
276
|
+
default="all",
|
|
277
|
+
help="Filter by category"
|
|
278
|
+
)
|
|
279
|
+
tools_list.add_argument(
|
|
280
|
+
"--installed-only",
|
|
281
|
+
action="store_true",
|
|
282
|
+
help="Show only installed tools"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# tools check - Check tool availability
|
|
286
|
+
tools_subparsers.add_parser("check", help="Check which tools are installed")
|
|
287
|
+
|
|
288
|
+
# AI Skills command with subcommands
|
|
289
|
+
ai_parser = subparsers.add_parser("ai", help="AI-powered security testing (code review, API testing, web pentesting)")
|
|
290
|
+
ai_subparsers = ai_parser.add_subparsers(dest="ai_command", help="AI testing commands")
|
|
291
|
+
|
|
292
|
+
# ai code-review - AI source code security review
|
|
293
|
+
ai_code = ai_subparsers.add_parser("code-review", help="AI-powered source code security review")
|
|
294
|
+
ai_code.add_argument("target", help="Path to code directory to review")
|
|
295
|
+
ai_code.add_argument(
|
|
296
|
+
"--focus", "-f",
|
|
297
|
+
nargs="+",
|
|
298
|
+
choices=["sqli", "xss", "auth", "crypto", "secrets", "injection"],
|
|
299
|
+
help="Focus areas for review"
|
|
300
|
+
)
|
|
301
|
+
ai_code.add_argument(
|
|
302
|
+
"--model", "-m",
|
|
303
|
+
default="claude-sonnet-4-20250514",
|
|
304
|
+
help="LLM model to use (default: claude-sonnet-4-20250514)"
|
|
305
|
+
)
|
|
306
|
+
ai_code.add_argument(
|
|
307
|
+
"--max-steps",
|
|
308
|
+
type=int,
|
|
309
|
+
default=100,
|
|
310
|
+
help="Maximum agent steps (default: 100)"
|
|
311
|
+
)
|
|
312
|
+
ai_code.add_argument(
|
|
313
|
+
"--quick", "-q",
|
|
314
|
+
action="store_true",
|
|
315
|
+
help="Quick scan focusing on high-priority patterns"
|
|
316
|
+
)
|
|
317
|
+
ai_code.add_argument(
|
|
318
|
+
"--output", "-o",
|
|
319
|
+
help="Output file for results (JSON)"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# ai api-test - AI API security testing
|
|
323
|
+
ai_api = ai_subparsers.add_parser("api-test", help="AI-powered REST API security testing")
|
|
324
|
+
ai_api.add_argument("target", help="Base URL of the API to test")
|
|
325
|
+
ai_api.add_argument(
|
|
326
|
+
"--openapi", "-s",
|
|
327
|
+
help="Path or URL to OpenAPI/Swagger spec"
|
|
328
|
+
)
|
|
329
|
+
ai_api.add_argument(
|
|
330
|
+
"--auth-token", "-t",
|
|
331
|
+
help="Bearer token for API authentication"
|
|
332
|
+
)
|
|
333
|
+
ai_api.add_argument(
|
|
334
|
+
"--model", "-m",
|
|
335
|
+
default="claude-sonnet-4-20250514",
|
|
336
|
+
help="LLM model to use"
|
|
337
|
+
)
|
|
338
|
+
ai_api.add_argument(
|
|
339
|
+
"--max-steps",
|
|
340
|
+
type=int,
|
|
341
|
+
default=100,
|
|
342
|
+
help="Maximum agent steps"
|
|
343
|
+
)
|
|
344
|
+
ai_api.add_argument(
|
|
345
|
+
"--output", "-o",
|
|
346
|
+
help="Output file for results (JSON)"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# ai web-pentest - AI web penetration testing
|
|
350
|
+
ai_web = ai_subparsers.add_parser("web-pentest", help="AI-powered web application penetration testing")
|
|
351
|
+
ai_web.add_argument("target", help="Target URL to test")
|
|
352
|
+
ai_web.add_argument(
|
|
353
|
+
"--auth-token", "-t",
|
|
354
|
+
help="Bearer token for authentication"
|
|
355
|
+
)
|
|
356
|
+
ai_web.add_argument(
|
|
357
|
+
"--cookie", "-c",
|
|
358
|
+
action="append",
|
|
359
|
+
help="Cookies for authenticated testing (key=value)"
|
|
360
|
+
)
|
|
361
|
+
ai_web.add_argument(
|
|
362
|
+
"--model", "-m",
|
|
363
|
+
default="claude-sonnet-4-20250514",
|
|
364
|
+
help="LLM model to use"
|
|
365
|
+
)
|
|
366
|
+
ai_web.add_argument(
|
|
367
|
+
"--max-steps",
|
|
368
|
+
type=int,
|
|
369
|
+
default=100,
|
|
370
|
+
help="Maximum agent steps"
|
|
371
|
+
)
|
|
372
|
+
ai_web.add_argument(
|
|
373
|
+
"--quick", "-q",
|
|
374
|
+
action="store_true",
|
|
375
|
+
help="Quick scan focusing on critical vulnerabilities"
|
|
376
|
+
)
|
|
377
|
+
ai_web.add_argument(
|
|
378
|
+
"--output", "-o",
|
|
379
|
+
help="Output file for results (JSON)"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# ai full - Full AI-driven security assessment
|
|
383
|
+
ai_full = ai_subparsers.add_parser("full", help="Full AI-driven security assessment")
|
|
384
|
+
ai_full.add_argument("target", help="Target URL or code path")
|
|
385
|
+
ai_full.add_argument(
|
|
386
|
+
"--types", "-t",
|
|
387
|
+
nargs="+",
|
|
388
|
+
choices=["web", "api", "code"],
|
|
389
|
+
default=["web"],
|
|
390
|
+
help="Types of testing to perform"
|
|
391
|
+
)
|
|
392
|
+
ai_full.add_argument(
|
|
393
|
+
"--model", "-m",
|
|
394
|
+
default="claude-sonnet-4-20250514",
|
|
395
|
+
help="LLM model to use"
|
|
396
|
+
)
|
|
397
|
+
ai_full.add_argument(
|
|
398
|
+
"--output", "-o",
|
|
399
|
+
help="Output file for results (JSON)"
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
args = parser.parse_args()
|
|
403
|
+
|
|
404
|
+
# Setup logging based on verbosity
|
|
405
|
+
log_level = "DEBUG" if args.verbose >= 2 else "INFO" if args.verbose == 1 else "WARNING"
|
|
406
|
+
setup_logging(level=log_level, json_format=args.json)
|
|
407
|
+
|
|
408
|
+
# Handle commands - wrap in try/except for graceful interrupt handling
|
|
409
|
+
try:
|
|
410
|
+
if args.command == "setup":
|
|
411
|
+
return run_setup(args)
|
|
412
|
+
elif args.command == "scan":
|
|
413
|
+
return run_scan(args)
|
|
414
|
+
elif args.command == "api":
|
|
415
|
+
return run_api(args)
|
|
416
|
+
elif args.command == "status":
|
|
417
|
+
return show_status(args)
|
|
418
|
+
elif args.command == "test":
|
|
419
|
+
return run_config_test(args)
|
|
420
|
+
elif args.command == "version":
|
|
421
|
+
return show_version()
|
|
422
|
+
elif args.command == "vps":
|
|
423
|
+
return run_vps_command(args)
|
|
424
|
+
elif args.command == "tools":
|
|
425
|
+
return run_tools_command(args)
|
|
426
|
+
elif args.command == "shell":
|
|
427
|
+
return run_shell(args)
|
|
428
|
+
elif args.command == "verify":
|
|
429
|
+
return run_verify(args)
|
|
430
|
+
elif args.command == "ai":
|
|
431
|
+
return run_ai_command(args)
|
|
432
|
+
else:
|
|
433
|
+
# No command given - start interactive mode
|
|
434
|
+
return run_interactive_mode()
|
|
435
|
+
except KeyboardInterrupt:
|
|
436
|
+
# Gracefully handle Ctrl+C
|
|
437
|
+
from rich.console import Console
|
|
438
|
+
Console().print("\n[yellow]Operation cancelled.[/yellow]")
|
|
439
|
+
return 130
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def show_first_run_help():
|
|
443
|
+
"""Show helpful guidance for first-time users."""
|
|
444
|
+
from rich.console import Console
|
|
445
|
+
from rich.panel import Panel
|
|
446
|
+
|
|
447
|
+
console = Console()
|
|
448
|
+
|
|
449
|
+
console.print()
|
|
450
|
+
console.print(Panel(
|
|
451
|
+
"[bold cyan]Welcome to AIPTX![/bold cyan]\n\n"
|
|
452
|
+
"[bold yellow]First-time setup required[/bold yellow]\n\n"
|
|
453
|
+
"AIPTX needs an LLM API key to power AI-guided security testing.\n\n"
|
|
454
|
+
"[bold]Quick Start:[/bold]\n"
|
|
455
|
+
" 1. Run [bold green]aiptx setup[/bold green] to configure interactively\n"
|
|
456
|
+
" 2. Or set environment variable:\n"
|
|
457
|
+
" [dim]export ANTHROPIC_API_KEY=your-key-here[/dim]\n\n"
|
|
458
|
+
"[bold]Then run:[/bold]\n"
|
|
459
|
+
" [bold green]aiptx scan example.com[/bold green]",
|
|
460
|
+
title="🚀 AIPTX - AI-Powered Penetration Testing",
|
|
461
|
+
border_style="cyan",
|
|
462
|
+
padding=(1, 2),
|
|
463
|
+
))
|
|
464
|
+
console.print()
|
|
465
|
+
|
|
466
|
+
return 0
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def run_interactive_mode():
|
|
470
|
+
"""Run AIPTX in interactive shell mode."""
|
|
471
|
+
from rich.console import Console
|
|
472
|
+
from rich.panel import Panel
|
|
473
|
+
from rich.prompt import Prompt
|
|
474
|
+
from rich.table import Table
|
|
475
|
+
from rich import box
|
|
476
|
+
|
|
477
|
+
console = Console()
|
|
478
|
+
|
|
479
|
+
# ASCII Art Logo
|
|
480
|
+
logo = """
|
|
481
|
+
[bold cyan] █████╗ ██╗██████╗ ████████╗██╗ ██╗[/bold cyan]
|
|
482
|
+
[bold cyan] ██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗██╔╝[/bold cyan]
|
|
483
|
+
[bold blue] ███████║██║██████╔╝ ██║ ╚███╔╝ [/bold blue]
|
|
484
|
+
[bold blue] ██╔══██║██║██╔═══╝ ██║ ██╔██╗ [/bold blue]
|
|
485
|
+
[bold magenta] ██║ ██║██║██║ ██║ ██╔╝ ██╗[/bold magenta]
|
|
486
|
+
[bold magenta] ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝[/bold magenta]
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
# Show welcome banner
|
|
490
|
+
console.print()
|
|
491
|
+
console.print(logo)
|
|
492
|
+
console.print("[bold white] AI-Powered Penetration Testing Framework[/bold white]")
|
|
493
|
+
console.print(f"[dim] v{__version__} • [link=https://aiptx.io]aiptx.io[/link][/dim]")
|
|
494
|
+
console.print()
|
|
495
|
+
|
|
496
|
+
# Stylish separator
|
|
497
|
+
console.print("[dim cyan]" + "━" * 60 + "[/dim cyan]")
|
|
498
|
+
console.print()
|
|
499
|
+
|
|
500
|
+
# Quick commands in a nice table format
|
|
501
|
+
commands_table = Table(
|
|
502
|
+
show_header=False,
|
|
503
|
+
box=box.SIMPLE,
|
|
504
|
+
padding=(0, 2),
|
|
505
|
+
collapse_padding=True,
|
|
506
|
+
)
|
|
507
|
+
commands_table.add_column("Command", style="bold green", width=18)
|
|
508
|
+
commands_table.add_column("Description", style="white")
|
|
509
|
+
|
|
510
|
+
commands_table.add_row("scan <target>", "🔍 Run security scan")
|
|
511
|
+
commands_table.add_row("scan <target> --ai", "🤖 AI-guided intelligent scanning")
|
|
512
|
+
commands_table.add_row("scan <target> --full", "🎯 Comprehensive assessment")
|
|
513
|
+
commands_table.add_row("ai <command>", "🧠 AI security testing")
|
|
514
|
+
commands_table.add_row("vps <command>", "☁️ Remote VPS execution")
|
|
515
|
+
commands_table.add_row("setup", "⚙️ Configure AIPTX")
|
|
516
|
+
commands_table.add_row("status", "📊 Show configuration")
|
|
517
|
+
commands_table.add_row("help", "❓ Show all commands")
|
|
518
|
+
commands_table.add_row("exit", "👋 Exit AIPTX")
|
|
519
|
+
|
|
520
|
+
console.print(commands_table)
|
|
521
|
+
console.print()
|
|
522
|
+
console.print("[dim cyan]" + "━" * 60 + "[/dim cyan]")
|
|
523
|
+
console.print()
|
|
524
|
+
console.print("[dim]💡 Tip: Type [bold green]scan example.com --ai[/bold green] to start AI-guided scanning[/dim]")
|
|
525
|
+
console.print("[dim]🌐 Docs: [link=https://aiptx.io/docs]https://aiptx.io/docs[/link][/dim]")
|
|
526
|
+
console.print()
|
|
527
|
+
|
|
528
|
+
# Check configuration status
|
|
529
|
+
if not is_configured():
|
|
530
|
+
console.print(Panel(
|
|
531
|
+
"[yellow]⚠️ AIPTX is not configured yet![/yellow]\n\n"
|
|
532
|
+
"Run [bold green]setup[/bold green] to configure your API keys and scanners.\n"
|
|
533
|
+
"Or set environment variable: [dim]export ANTHROPIC_API_KEY=your-key[/dim]",
|
|
534
|
+
border_style="yellow",
|
|
535
|
+
title="[bold yellow]Setup Required[/bold yellow]",
|
|
536
|
+
title_align="left",
|
|
537
|
+
))
|
|
538
|
+
console.print()
|
|
539
|
+
|
|
540
|
+
# Interactive loop
|
|
541
|
+
while True:
|
|
542
|
+
try:
|
|
543
|
+
# Flush stdin to avoid stale input from previous commands
|
|
544
|
+
import sys
|
|
545
|
+
import platform
|
|
546
|
+
if sys.stdin.isatty():
|
|
547
|
+
# Clear any buffered input (platform-specific)
|
|
548
|
+
if platform.system() == "Windows":
|
|
549
|
+
# Windows: use msvcrt for non-blocking input check
|
|
550
|
+
import msvcrt
|
|
551
|
+
while msvcrt.kbhit():
|
|
552
|
+
msvcrt.getch()
|
|
553
|
+
else:
|
|
554
|
+
# Unix/Linux/macOS: use select
|
|
555
|
+
import select
|
|
556
|
+
while select.select([sys.stdin], [], [], 0)[0]:
|
|
557
|
+
sys.stdin.read(1)
|
|
558
|
+
|
|
559
|
+
# Get user input with stylish prompt
|
|
560
|
+
user_input = Prompt.ask("[bold cyan]aiptx[/bold cyan] [dim]→[/dim]", default="").strip()
|
|
561
|
+
|
|
562
|
+
if not user_input:
|
|
563
|
+
continue
|
|
564
|
+
|
|
565
|
+
# Parse the input
|
|
566
|
+
parts = user_input.split()
|
|
567
|
+
cmd = parts[0].lower()
|
|
568
|
+
args_list = parts[1:] if len(parts) > 1 else []
|
|
569
|
+
|
|
570
|
+
# Handle commands
|
|
571
|
+
if cmd in ("exit", "quit", "q"):
|
|
572
|
+
console.print()
|
|
573
|
+
console.print("[bold cyan]Thanks for using AIPTX![/bold cyan] 🚀")
|
|
574
|
+
console.print("[dim]Visit [link=https://aiptx.io]aiptx.io[/link] for docs & updates[/dim]")
|
|
575
|
+
console.print()
|
|
576
|
+
break
|
|
577
|
+
|
|
578
|
+
elif cmd == "help":
|
|
579
|
+
show_interactive_help(console)
|
|
580
|
+
|
|
581
|
+
elif cmd == "clear":
|
|
582
|
+
console.clear()
|
|
583
|
+
|
|
584
|
+
elif cmd == "setup":
|
|
585
|
+
run_setup_wrapper()
|
|
586
|
+
|
|
587
|
+
elif cmd == "status":
|
|
588
|
+
show_status_wrapper()
|
|
589
|
+
|
|
590
|
+
elif cmd == "test":
|
|
591
|
+
run_test_wrapper(parts[1:] if len(parts) > 1 else None)
|
|
592
|
+
|
|
593
|
+
elif cmd == "version":
|
|
594
|
+
console.print(f"[cyan]AIPTX v{__version__}[/cyan]")
|
|
595
|
+
|
|
596
|
+
elif cmd == "scan":
|
|
597
|
+
if not args_list:
|
|
598
|
+
console.print("[red]Usage:[/red] scan <target> [--mode quick|standard|full]")
|
|
599
|
+
else:
|
|
600
|
+
run_scan_wrapper(args_list)
|
|
601
|
+
|
|
602
|
+
elif cmd == "vps":
|
|
603
|
+
run_vps_wrapper(args_list)
|
|
604
|
+
|
|
605
|
+
elif cmd == "ai":
|
|
606
|
+
run_ai_wrapper(args_list)
|
|
607
|
+
|
|
608
|
+
else:
|
|
609
|
+
console.print(f"[red]Unknown command:[/red] {cmd}")
|
|
610
|
+
console.print("[dim]Type 'help' for available commands[/dim]")
|
|
611
|
+
|
|
612
|
+
except KeyboardInterrupt:
|
|
613
|
+
console.print("\n[dim]Press Ctrl+C again to exit, or type 'exit'[/dim]")
|
|
614
|
+
try:
|
|
615
|
+
# Give user a chance to continue
|
|
616
|
+
continue
|
|
617
|
+
except KeyboardInterrupt:
|
|
618
|
+
console.print("\n[dim]Goodbye![/dim]")
|
|
619
|
+
break
|
|
620
|
+
except EOFError:
|
|
621
|
+
# Handle Ctrl+D
|
|
622
|
+
console.print("\n[dim]Goodbye![/dim]")
|
|
623
|
+
break
|
|
624
|
+
|
|
625
|
+
return 0
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def show_interactive_help(console):
|
|
629
|
+
"""Show help for interactive mode."""
|
|
630
|
+
from rich.table import Table
|
|
631
|
+
from rich import box
|
|
632
|
+
|
|
633
|
+
table = Table(title="AIPTX Commands", box=box.ROUNDED)
|
|
634
|
+
table.add_column("Command", style="green")
|
|
635
|
+
table.add_column("Description")
|
|
636
|
+
table.add_column("Example", style="dim")
|
|
637
|
+
|
|
638
|
+
table.add_row("scan <target>", "Run security scan", "scan example.com --check --full")
|
|
639
|
+
table.add_row("ai code-review <path>", "AI code security review", "ai code-review ./src")
|
|
640
|
+
table.add_row("ai api-test <url>", "AI API security testing", "ai api-test https://api.example.com")
|
|
641
|
+
table.add_row("ai web-pentest <url>", "AI web penetration test", "ai web-pentest https://example.com")
|
|
642
|
+
table.add_row("vps setup", "Install tools on VPS", "vps setup")
|
|
643
|
+
table.add_row("vps status", "Check VPS status", "vps status")
|
|
644
|
+
table.add_row("vps scan <target>", "Run scan from VPS", "vps scan example.com")
|
|
645
|
+
table.add_row("setup", "Configure AIPTX", "setup")
|
|
646
|
+
table.add_row("status", "Show configuration", "status")
|
|
647
|
+
table.add_row("test", "Validate all configs", "test")
|
|
648
|
+
table.add_row("clear", "Clear screen", "clear")
|
|
649
|
+
table.add_row("exit", "Exit AIPTX", "exit")
|
|
650
|
+
|
|
651
|
+
console.print(table)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def run_setup_wrapper():
|
|
655
|
+
"""Wrapper to run setup from interactive mode."""
|
|
656
|
+
from rich.console import Console
|
|
657
|
+
console = Console()
|
|
658
|
+
try:
|
|
659
|
+
success = run_setup_wizard(force=True)
|
|
660
|
+
# Reload configuration after successful setup
|
|
661
|
+
if success:
|
|
662
|
+
reload_config()
|
|
663
|
+
except Exception as e:
|
|
664
|
+
console.print(f"[red]Setup error:[/red] {e}")
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def show_status_wrapper():
|
|
668
|
+
"""Wrapper to show status from interactive mode."""
|
|
669
|
+
import argparse
|
|
670
|
+
args = argparse.Namespace(verbose=0, json=False)
|
|
671
|
+
show_status(args)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def run_test_wrapper(args_list=None):
|
|
675
|
+
"""Wrapper to run config test from interactive mode."""
|
|
676
|
+
import argparse
|
|
677
|
+
from rich.console import Console
|
|
678
|
+
console = Console()
|
|
679
|
+
|
|
680
|
+
# Parse test arguments
|
|
681
|
+
test_llm = False
|
|
682
|
+
test_vps = False
|
|
683
|
+
test_scanners = False
|
|
684
|
+
test_tools = False
|
|
685
|
+
test_all = True
|
|
686
|
+
|
|
687
|
+
if args_list:
|
|
688
|
+
for arg in args_list:
|
|
689
|
+
if arg == "--llm":
|
|
690
|
+
test_llm = True
|
|
691
|
+
test_all = False
|
|
692
|
+
elif arg == "--vps":
|
|
693
|
+
test_vps = True
|
|
694
|
+
test_all = False
|
|
695
|
+
elif arg == "--scanners":
|
|
696
|
+
test_scanners = True
|
|
697
|
+
test_all = False
|
|
698
|
+
elif arg == "--tools":
|
|
699
|
+
test_tools = True
|
|
700
|
+
test_all = False
|
|
701
|
+
|
|
702
|
+
args = argparse.Namespace(
|
|
703
|
+
llm=test_llm,
|
|
704
|
+
vps=test_vps,
|
|
705
|
+
scanners=test_scanners,
|
|
706
|
+
tools=test_tools,
|
|
707
|
+
all=test_all,
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
try:
|
|
711
|
+
run_config_test(args)
|
|
712
|
+
except Exception as e:
|
|
713
|
+
console.print(f"[red]Test error:[/red] {e}")
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def run_scan_wrapper(args_list):
|
|
717
|
+
"""Wrapper to run scan from interactive mode."""
|
|
718
|
+
import argparse
|
|
719
|
+
from rich.console import Console
|
|
720
|
+
console = Console()
|
|
721
|
+
|
|
722
|
+
# Parse scan arguments
|
|
723
|
+
target = args_list[0]
|
|
724
|
+
mode = "standard"
|
|
725
|
+
full = False
|
|
726
|
+
ai = False
|
|
727
|
+
check = False
|
|
728
|
+
use_vps = False
|
|
729
|
+
|
|
730
|
+
for i, arg in enumerate(args_list[1:]):
|
|
731
|
+
if arg == "--full":
|
|
732
|
+
full = True
|
|
733
|
+
elif arg == "--ai":
|
|
734
|
+
ai = True
|
|
735
|
+
elif arg == "--check":
|
|
736
|
+
check = True
|
|
737
|
+
elif arg == "--use-vps":
|
|
738
|
+
use_vps = True
|
|
739
|
+
elif arg in ("--mode", "-m") and i + 2 < len(args_list):
|
|
740
|
+
mode = args_list[i + 2]
|
|
741
|
+
|
|
742
|
+
args = argparse.Namespace(
|
|
743
|
+
target=target,
|
|
744
|
+
client=None,
|
|
745
|
+
output=None,
|
|
746
|
+
mode=mode,
|
|
747
|
+
full=full,
|
|
748
|
+
ai=ai,
|
|
749
|
+
use_vps=use_vps,
|
|
750
|
+
use_acunetix=False,
|
|
751
|
+
use_burp=False,
|
|
752
|
+
skip_recon=False,
|
|
753
|
+
verbose=0,
|
|
754
|
+
check=check,
|
|
755
|
+
quiet=False,
|
|
756
|
+
no_stream=False,
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
try:
|
|
760
|
+
run_scan(args)
|
|
761
|
+
except Exception as e:
|
|
762
|
+
console.print(f"[red]Scan error:[/red] {e}")
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def run_vps_wrapper(args_list):
|
|
766
|
+
"""Wrapper to run VPS commands from interactive mode."""
|
|
767
|
+
import argparse
|
|
768
|
+
from rich.console import Console
|
|
769
|
+
console = Console()
|
|
770
|
+
|
|
771
|
+
if not args_list:
|
|
772
|
+
console.print("[yellow]VPS Commands:[/yellow]")
|
|
773
|
+
console.print(" vps setup - Install security tools")
|
|
774
|
+
console.print(" vps status - Check VPS status")
|
|
775
|
+
console.print(" vps scan - Run scan from VPS")
|
|
776
|
+
return
|
|
777
|
+
|
|
778
|
+
vps_cmd = args_list[0]
|
|
779
|
+
args = argparse.Namespace(
|
|
780
|
+
vps_command=vps_cmd,
|
|
781
|
+
categories=None,
|
|
782
|
+
tools=None,
|
|
783
|
+
target=args_list[1] if len(args_list) > 1 else None,
|
|
784
|
+
mode="standard",
|
|
785
|
+
output=None,
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
try:
|
|
789
|
+
run_vps_command(args)
|
|
790
|
+
except Exception as e:
|
|
791
|
+
console.print(f"[red]VPS error:[/red] {e}")
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def run_ai_wrapper(args_list):
|
|
795
|
+
"""Wrapper to run AI commands from interactive mode."""
|
|
796
|
+
import argparse
|
|
797
|
+
from rich.console import Console
|
|
798
|
+
console = Console()
|
|
799
|
+
|
|
800
|
+
if not args_list:
|
|
801
|
+
console.print("[yellow]AI Commands:[/yellow]")
|
|
802
|
+
console.print(" ai code-review <path> - AI code security review")
|
|
803
|
+
console.print(" ai api-test <url> - AI API testing")
|
|
804
|
+
console.print(" ai web-pentest <url> - AI web pentesting")
|
|
805
|
+
console.print(" ai full <target> - Full AI assessment")
|
|
806
|
+
return
|
|
807
|
+
|
|
808
|
+
ai_cmd = args_list[0]
|
|
809
|
+
target = args_list[1] if len(args_list) > 1 else None
|
|
810
|
+
|
|
811
|
+
if not target and ai_cmd != "help":
|
|
812
|
+
console.print(f"[red]Usage:[/red] ai {ai_cmd} <target>")
|
|
813
|
+
return
|
|
814
|
+
|
|
815
|
+
args = argparse.Namespace(
|
|
816
|
+
ai_command=ai_cmd,
|
|
817
|
+
target=target,
|
|
818
|
+
focus=None,
|
|
819
|
+
model="claude-sonnet-4-20250514",
|
|
820
|
+
max_steps=100,
|
|
821
|
+
quick="--quick" in args_list or "-q" in args_list,
|
|
822
|
+
output=None,
|
|
823
|
+
openapi=None,
|
|
824
|
+
auth_token=None,
|
|
825
|
+
cookie=None,
|
|
826
|
+
types=["web"],
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
try:
|
|
830
|
+
run_ai_command(args)
|
|
831
|
+
except Exception as e:
|
|
832
|
+
console.print(f"[red]AI error:[/red] {e}")
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def run_setup(args):
|
|
836
|
+
"""Run the interactive setup wizard."""
|
|
837
|
+
force = getattr(args, 'force', False)
|
|
838
|
+
success = run_setup_wizard(force=force)
|
|
839
|
+
|
|
840
|
+
# Reload configuration after successful setup so it's immediately available
|
|
841
|
+
if success:
|
|
842
|
+
reload_config()
|
|
843
|
+
|
|
844
|
+
return 0 if success else 1
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def run_scan(args):
|
|
848
|
+
"""Run security scan."""
|
|
849
|
+
from rich.console import Console
|
|
850
|
+
from rich.panel import Panel
|
|
851
|
+
|
|
852
|
+
console = Console()
|
|
853
|
+
|
|
854
|
+
try:
|
|
855
|
+
from .orchestrator import Orchestrator, OrchestratorConfig
|
|
856
|
+
except ImportError as e:
|
|
857
|
+
# Provide helpful error message for missing dependencies
|
|
858
|
+
error_msg = str(e)
|
|
859
|
+
console.print()
|
|
860
|
+
console.print(Panel(
|
|
861
|
+
"[bold red]Missing Dependencies[/bold red]\n\n"
|
|
862
|
+
f"[dim]Import error: {error_msg}[/dim]\n\n"
|
|
863
|
+
"The scan module requires additional dependencies.\n\n"
|
|
864
|
+
"[bold]To fix, install the full package:[/bold]\n"
|
|
865
|
+
" [bold green]pip install aiptx[full][/bold green]\n\n"
|
|
866
|
+
"[bold]Or install specific dependencies:[/bold]\n"
|
|
867
|
+
" [dim]pip install sentence-transformers torch langchain-core[/dim]",
|
|
868
|
+
title="⚠️ Scan Module Not Available",
|
|
869
|
+
border_style="yellow",
|
|
870
|
+
padding=(1, 2),
|
|
871
|
+
))
|
|
872
|
+
console.print()
|
|
873
|
+
return 1
|
|
874
|
+
|
|
875
|
+
# Check if configured - prompt for setup if not
|
|
876
|
+
if not is_configured():
|
|
877
|
+
# Interactive setup for first-time users
|
|
878
|
+
if not prompt_first_run_setup():
|
|
879
|
+
return 1 # User declined setup or setup failed
|
|
880
|
+
|
|
881
|
+
# Validate configuration for requested features
|
|
882
|
+
features = ["llm"]
|
|
883
|
+
if args.use_acunetix:
|
|
884
|
+
features.append("acunetix")
|
|
885
|
+
if args.use_burp:
|
|
886
|
+
features.append("burp")
|
|
887
|
+
if args.use_vps:
|
|
888
|
+
features.append("vps")
|
|
889
|
+
|
|
890
|
+
errors = validate_config_for_features(features)
|
|
891
|
+
if errors:
|
|
892
|
+
console.print()
|
|
893
|
+
console.print(Panel(
|
|
894
|
+
"[bold red]Configuration Error[/bold red]\n\n"
|
|
895
|
+
"The following issues need to be resolved:\n\n" +
|
|
896
|
+
"\n".join(f" [yellow]•[/yellow] {error}" for error in errors) +
|
|
897
|
+
"\n\n[bold]To fix:[/bold]\n"
|
|
898
|
+
" Run [bold green]aiptx setup[/bold green] to configure interactively\n\n"
|
|
899
|
+
"[bold]Or set environment variables:[/bold]\n"
|
|
900
|
+
" [dim]export ANTHROPIC_API_KEY=your-key-here[/dim]",
|
|
901
|
+
title="⚠️ Setup Required",
|
|
902
|
+
border_style="yellow",
|
|
903
|
+
padding=(1, 2),
|
|
904
|
+
))
|
|
905
|
+
console.print()
|
|
906
|
+
return 1
|
|
907
|
+
|
|
908
|
+
# Run pre-flight checks if requested
|
|
909
|
+
if getattr(args, 'check', False):
|
|
910
|
+
ai_mode = args.ai or args.mode == "ai"
|
|
911
|
+
checks_passed = run_preflight_check(
|
|
912
|
+
console=console,
|
|
913
|
+
use_vps=args.use_vps,
|
|
914
|
+
use_acunetix=args.use_acunetix,
|
|
915
|
+
use_burp=args.use_burp,
|
|
916
|
+
ai_mode=ai_mode,
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
if not checks_passed:
|
|
920
|
+
console.print("[yellow]Scan aborted due to failed pre-flight checks.[/yellow]")
|
|
921
|
+
console.print("[dim]Fix the issues above and try again, or run without --check to skip validation.[/dim]")
|
|
922
|
+
return 1
|
|
923
|
+
|
|
924
|
+
console.print("[dim]Pre-flight checks passed. Starting scan...[/dim]")
|
|
925
|
+
console.print()
|
|
926
|
+
|
|
927
|
+
# Create config
|
|
928
|
+
# Verbose mode is default (True), quiet mode disables it
|
|
929
|
+
verbose = not getattr(args, 'quiet', False)
|
|
930
|
+
# Show command output is default (True), --no-stream disables it
|
|
931
|
+
show_command_output = not getattr(args, 'no_stream', False)
|
|
932
|
+
|
|
933
|
+
config = OrchestratorConfig(
|
|
934
|
+
target=args.target,
|
|
935
|
+
output_dir=Path(args.output) if args.output else Path("./results"),
|
|
936
|
+
skip_recon=args.skip_recon,
|
|
937
|
+
use_acunetix=args.use_acunetix,
|
|
938
|
+
use_burp=args.use_burp,
|
|
939
|
+
verbose=verbose,
|
|
940
|
+
show_command_output=show_command_output,
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
# Determine mode
|
|
944
|
+
if args.ai or args.mode == "ai":
|
|
945
|
+
mode = "ai"
|
|
946
|
+
elif args.full or args.mode == "full":
|
|
947
|
+
mode = "full"
|
|
948
|
+
elif args.mode == "quick":
|
|
949
|
+
mode = "quick"
|
|
950
|
+
else:
|
|
951
|
+
mode = "standard"
|
|
952
|
+
|
|
953
|
+
# Show scan starting message
|
|
954
|
+
console.print()
|
|
955
|
+
console.print(f"[bold cyan]Starting {mode} scan on[/bold cyan] [bold]{args.target}[/bold]")
|
|
956
|
+
console.print()
|
|
957
|
+
|
|
958
|
+
# Run orchestrator
|
|
959
|
+
orchestrator = Orchestrator(args.target, config)
|
|
960
|
+
|
|
961
|
+
try:
|
|
962
|
+
# Use custom event loop handling to avoid cleanup warnings
|
|
963
|
+
loop = asyncio.new_event_loop()
|
|
964
|
+
asyncio.set_event_loop(loop)
|
|
965
|
+
try:
|
|
966
|
+
loop.run_until_complete(orchestrator.run())
|
|
967
|
+
finally:
|
|
968
|
+
# Clean up pending tasks before closing the loop
|
|
969
|
+
try:
|
|
970
|
+
pending = asyncio.all_tasks(loop)
|
|
971
|
+
for task in pending:
|
|
972
|
+
task.cancel()
|
|
973
|
+
# Give tasks a chance to respond to cancellation
|
|
974
|
+
if pending:
|
|
975
|
+
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
|
|
976
|
+
except Exception:
|
|
977
|
+
pass # Ignore cleanup errors
|
|
978
|
+
loop.close()
|
|
979
|
+
|
|
980
|
+
console.print()
|
|
981
|
+
console.print("[bold green]✓ Scan completed successfully[/bold green]")
|
|
982
|
+
return 0
|
|
983
|
+
except KeyboardInterrupt:
|
|
984
|
+
console.print()
|
|
985
|
+
console.print("[yellow]Scan interrupted by user[/yellow]")
|
|
986
|
+
return 130
|
|
987
|
+
except Exception as e:
|
|
988
|
+
console.print()
|
|
989
|
+
console.print(f"[bold red]✗ Scan failed:[/bold red] {e}")
|
|
990
|
+
if args.verbose:
|
|
991
|
+
import traceback
|
|
992
|
+
console.print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
993
|
+
return 1
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
def run_api(args):
|
|
997
|
+
"""Start REST API server."""
|
|
998
|
+
import uvicorn
|
|
999
|
+
|
|
1000
|
+
logger.info(f"Starting API server on {args.host}:{args.port}")
|
|
1001
|
+
|
|
1002
|
+
# Try package import first, then local
|
|
1003
|
+
try:
|
|
1004
|
+
uvicorn.run(
|
|
1005
|
+
"app:app",
|
|
1006
|
+
host=args.host,
|
|
1007
|
+
port=args.port,
|
|
1008
|
+
reload=args.reload,
|
|
1009
|
+
log_level="info",
|
|
1010
|
+
)
|
|
1011
|
+
except Exception:
|
|
1012
|
+
# Fallback for installed package
|
|
1013
|
+
uvicorn.run(
|
|
1014
|
+
"aiptx.app:app",
|
|
1015
|
+
host=args.host,
|
|
1016
|
+
port=args.port,
|
|
1017
|
+
reload=args.reload,
|
|
1018
|
+
log_level="info",
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
return 0
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
def show_status(args):
|
|
1025
|
+
"""Show configuration status."""
|
|
1026
|
+
from rich.console import Console
|
|
1027
|
+
from rich.table import Table
|
|
1028
|
+
|
|
1029
|
+
console = Console()
|
|
1030
|
+
config = get_config()
|
|
1031
|
+
|
|
1032
|
+
console.print("\n[bold cyan]AIPT v2 Configuration Status[/bold cyan]\n")
|
|
1033
|
+
|
|
1034
|
+
# LLM Status
|
|
1035
|
+
table = Table(title="LLM Configuration")
|
|
1036
|
+
table.add_column("Setting", style="cyan")
|
|
1037
|
+
table.add_column("Value", style="green")
|
|
1038
|
+
table.add_column("Status", style="yellow")
|
|
1039
|
+
|
|
1040
|
+
table.add_row("Provider", config.llm.provider, "✓" if config.llm.provider else "✗")
|
|
1041
|
+
table.add_row("Model", config.llm.model, "✓" if config.llm.model else "✗")
|
|
1042
|
+
table.add_row("API Key", "****" if config.llm.api_key else "Not set", "✓" if config.llm.api_key else "✗")
|
|
1043
|
+
|
|
1044
|
+
console.print(table)
|
|
1045
|
+
|
|
1046
|
+
# Scanner Status
|
|
1047
|
+
table = Table(title="Scanner Configuration")
|
|
1048
|
+
table.add_column("Scanner", style="cyan")
|
|
1049
|
+
table.add_column("URL", style="green")
|
|
1050
|
+
table.add_column("API Key", style="yellow")
|
|
1051
|
+
|
|
1052
|
+
table.add_row(
|
|
1053
|
+
"Acunetix",
|
|
1054
|
+
config.scanners.acunetix_url or "Not configured",
|
|
1055
|
+
"✓" if config.scanners.acunetix_api_key else "✗",
|
|
1056
|
+
)
|
|
1057
|
+
table.add_row(
|
|
1058
|
+
"Burp Suite",
|
|
1059
|
+
config.scanners.burp_url or "Not configured",
|
|
1060
|
+
"✓" if config.scanners.burp_api_key else "✗",
|
|
1061
|
+
)
|
|
1062
|
+
table.add_row(
|
|
1063
|
+
"Nessus",
|
|
1064
|
+
config.scanners.nessus_url or "Not configured",
|
|
1065
|
+
"✓" if config.scanners.nessus_access_key else "✗",
|
|
1066
|
+
)
|
|
1067
|
+
table.add_row(
|
|
1068
|
+
"OWASP ZAP",
|
|
1069
|
+
config.scanners.zap_url or "Not configured",
|
|
1070
|
+
"✓" if config.scanners.zap_api_key else "✗",
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
console.print(table)
|
|
1074
|
+
|
|
1075
|
+
# VPS Status
|
|
1076
|
+
table = Table(title="VPS Configuration")
|
|
1077
|
+
table.add_column("Setting", style="cyan")
|
|
1078
|
+
table.add_column("Value", style="green")
|
|
1079
|
+
|
|
1080
|
+
table.add_row("Host", config.vps.host or "Not configured")
|
|
1081
|
+
table.add_row("User", config.vps.user)
|
|
1082
|
+
table.add_row("SSH Key", config.vps.key_path or "Not configured")
|
|
1083
|
+
|
|
1084
|
+
console.print(table)
|
|
1085
|
+
|
|
1086
|
+
# Check for issues
|
|
1087
|
+
console.print("\n[bold]Configuration Validation:[/bold]")
|
|
1088
|
+
|
|
1089
|
+
all_features = ["llm", "acunetix", "burp", "nessus", "vps"]
|
|
1090
|
+
for feature in all_features:
|
|
1091
|
+
errors = validate_config_for_features([feature])
|
|
1092
|
+
if errors:
|
|
1093
|
+
console.print(f" [yellow]⚠[/yellow] {feature}: {errors[0]}")
|
|
1094
|
+
else:
|
|
1095
|
+
console.print(f" [green]✓[/green] {feature}: Ready")
|
|
1096
|
+
|
|
1097
|
+
return 0
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
def run_config_test(args):
|
|
1101
|
+
"""
|
|
1102
|
+
Test and validate all configurations by making real connections.
|
|
1103
|
+
|
|
1104
|
+
Unlike 'status' which just shows config values, 'test' actually
|
|
1105
|
+
validates that services are reachable and credentials work.
|
|
1106
|
+
"""
|
|
1107
|
+
import asyncio
|
|
1108
|
+
import shutil
|
|
1109
|
+
import time
|
|
1110
|
+
from rich.console import Console
|
|
1111
|
+
from rich.panel import Panel
|
|
1112
|
+
from rich.table import Table
|
|
1113
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
1114
|
+
from rich import box
|
|
1115
|
+
|
|
1116
|
+
console = Console()
|
|
1117
|
+
config = get_config()
|
|
1118
|
+
|
|
1119
|
+
console.print()
|
|
1120
|
+
console.print(Panel(
|
|
1121
|
+
"[bold]AIPTX Configuration Validator[/bold]\n\n"
|
|
1122
|
+
"Testing all configured services and credentials...",
|
|
1123
|
+
title="🔍 Self-Test",
|
|
1124
|
+
border_style="cyan"
|
|
1125
|
+
))
|
|
1126
|
+
console.print()
|
|
1127
|
+
|
|
1128
|
+
results = {}
|
|
1129
|
+
test_all = getattr(args, 'all', False) or not any([
|
|
1130
|
+
getattr(args, 'llm', False),
|
|
1131
|
+
getattr(args, 'vps', False),
|
|
1132
|
+
getattr(args, 'scanners', False),
|
|
1133
|
+
getattr(args, 'tools', False),
|
|
1134
|
+
])
|
|
1135
|
+
|
|
1136
|
+
# ======================== LLM Test ========================
|
|
1137
|
+
if test_all or getattr(args, 'llm', False):
|
|
1138
|
+
console.print("[bold cyan]━━━ LLM API Test ━━━[/bold cyan]")
|
|
1139
|
+
|
|
1140
|
+
if not config.llm.api_key:
|
|
1141
|
+
console.print(" [red]✗[/red] No API key configured")
|
|
1142
|
+
console.print(" [dim]Run 'aiptx setup' to configure[/dim]")
|
|
1143
|
+
results['llm'] = False
|
|
1144
|
+
else:
|
|
1145
|
+
with console.status("[yellow]Testing LLM API connection...[/yellow]"):
|
|
1146
|
+
try:
|
|
1147
|
+
import litellm
|
|
1148
|
+
|
|
1149
|
+
# Determine model string based on provider
|
|
1150
|
+
provider = config.llm.provider.lower()
|
|
1151
|
+
model = config.llm.model
|
|
1152
|
+
|
|
1153
|
+
if provider == "anthropic":
|
|
1154
|
+
model_str = f"anthropic/{model}" if not model.startswith("anthropic/") else model
|
|
1155
|
+
litellm.api_key = config.llm.api_key
|
|
1156
|
+
elif provider == "openai":
|
|
1157
|
+
model_str = f"openai/{model}" if not model.startswith("openai/") else model
|
|
1158
|
+
litellm.api_key = config.llm.api_key
|
|
1159
|
+
elif provider == "deepseek":
|
|
1160
|
+
model_str = f"deepseek/{model}" if not model.startswith("deepseek/") else model
|
|
1161
|
+
litellm.api_key = config.llm.api_key
|
|
1162
|
+
else:
|
|
1163
|
+
model_str = model
|
|
1164
|
+
|
|
1165
|
+
start = time.time()
|
|
1166
|
+
response = litellm.completion(
|
|
1167
|
+
model=model_str,
|
|
1168
|
+
messages=[{"role": "user", "content": "Reply with only: OK"}],
|
|
1169
|
+
max_tokens=10,
|
|
1170
|
+
timeout=30,
|
|
1171
|
+
)
|
|
1172
|
+
elapsed = time.time() - start
|
|
1173
|
+
|
|
1174
|
+
console.print(f" [green]✓[/green] LLM API connection successful")
|
|
1175
|
+
console.print(f" [dim]Provider: {provider}[/dim]")
|
|
1176
|
+
console.print(f" [dim]Model: {model}[/dim]")
|
|
1177
|
+
console.print(f" [dim]Response time: {elapsed:.2f}s[/dim]")
|
|
1178
|
+
results['llm'] = True
|
|
1179
|
+
|
|
1180
|
+
except ImportError:
|
|
1181
|
+
console.print(" [yellow]⚠[/yellow] litellm not installed")
|
|
1182
|
+
console.print(" [dim]Install with: pip install litellm[/dim]")
|
|
1183
|
+
results['llm'] = None
|
|
1184
|
+
except Exception as e:
|
|
1185
|
+
console.print(f" [red]✗[/red] LLM API test failed")
|
|
1186
|
+
console.print(f" [dim]Error: {str(e)[:100]}[/dim]")
|
|
1187
|
+
results['llm'] = False
|
|
1188
|
+
|
|
1189
|
+
console.print()
|
|
1190
|
+
|
|
1191
|
+
# ======================== VPS Test ========================
|
|
1192
|
+
if test_all or getattr(args, 'vps', False):
|
|
1193
|
+
console.print("[bold cyan]━━━ VPS Connection Test ━━━[/bold cyan]")
|
|
1194
|
+
|
|
1195
|
+
if not config.vps.host:
|
|
1196
|
+
console.print(" [yellow]○[/yellow] VPS not configured (optional)")
|
|
1197
|
+
results['vps'] = None
|
|
1198
|
+
elif not config.vps.key_path:
|
|
1199
|
+
console.print(" [red]✗[/red] SSH key path not configured")
|
|
1200
|
+
results['vps'] = False
|
|
1201
|
+
else:
|
|
1202
|
+
with console.status("[yellow]Testing SSH connection to VPS...[/yellow]"):
|
|
1203
|
+
try:
|
|
1204
|
+
from pathlib import Path
|
|
1205
|
+
|
|
1206
|
+
key_path = Path(config.vps.key_path).expanduser()
|
|
1207
|
+
if not key_path.exists():
|
|
1208
|
+
console.print(f" [red]✗[/red] SSH key not found: {key_path}")
|
|
1209
|
+
results['vps'] = False
|
|
1210
|
+
else:
|
|
1211
|
+
# Test SSH connection using asyncssh
|
|
1212
|
+
async def test_ssh():
|
|
1213
|
+
import asyncssh
|
|
1214
|
+
start = time.time()
|
|
1215
|
+
conn = await asyncssh.connect(
|
|
1216
|
+
config.vps.host,
|
|
1217
|
+
port=config.vps.port,
|
|
1218
|
+
username=config.vps.user,
|
|
1219
|
+
client_keys=[str(key_path)],
|
|
1220
|
+
known_hosts=None,
|
|
1221
|
+
)
|
|
1222
|
+
# Run a simple command to verify
|
|
1223
|
+
result = await conn.run("echo 'AIPTX_TEST_OK' && uname -a", check=True)
|
|
1224
|
+
await conn.close()
|
|
1225
|
+
elapsed = time.time() - start
|
|
1226
|
+
return result.stdout.strip(), elapsed
|
|
1227
|
+
|
|
1228
|
+
output, elapsed = asyncio.run(test_ssh())
|
|
1229
|
+
|
|
1230
|
+
if "AIPTX_TEST_OK" in output:
|
|
1231
|
+
uname = output.replace("AIPTX_TEST_OK", "").strip()
|
|
1232
|
+
console.print(f" [green]✓[/green] VPS connection successful")
|
|
1233
|
+
console.print(f" [dim]Host: {config.vps.user}@{config.vps.host}:{config.vps.port}[/dim]")
|
|
1234
|
+
console.print(f" [dim]System: {uname[:60]}...[/dim]" if len(uname) > 60 else f" [dim]System: {uname}[/dim]")
|
|
1235
|
+
console.print(f" [dim]Response time: {elapsed:.2f}s[/dim]")
|
|
1236
|
+
results['vps'] = True
|
|
1237
|
+
else:
|
|
1238
|
+
console.print(f" [red]✗[/red] VPS connection failed - unexpected response")
|
|
1239
|
+
results['vps'] = False
|
|
1240
|
+
|
|
1241
|
+
except ImportError:
|
|
1242
|
+
console.print(" [yellow]⚠[/yellow] asyncssh not installed")
|
|
1243
|
+
console.print(" [dim]Install with: pip install asyncssh[/dim]")
|
|
1244
|
+
results['vps'] = None
|
|
1245
|
+
except Exception as e:
|
|
1246
|
+
console.print(f" [red]✗[/red] VPS connection failed")
|
|
1247
|
+
console.print(f" [dim]Error: {str(e)[:100]}[/dim]")
|
|
1248
|
+
results['vps'] = False
|
|
1249
|
+
|
|
1250
|
+
console.print()
|
|
1251
|
+
|
|
1252
|
+
# ======================== Scanner Tests ========================
|
|
1253
|
+
if test_all or getattr(args, 'scanners', False):
|
|
1254
|
+
console.print("[bold cyan]━━━ Scanner Integration Tests ━━━[/bold cyan]")
|
|
1255
|
+
|
|
1256
|
+
scanners_tested = 0
|
|
1257
|
+
|
|
1258
|
+
# Acunetix
|
|
1259
|
+
if config.scanners.acunetix_url:
|
|
1260
|
+
scanners_tested += 1
|
|
1261
|
+
with console.status("[yellow]Testing Acunetix connection...[/yellow]"):
|
|
1262
|
+
try:
|
|
1263
|
+
import httpx
|
|
1264
|
+
response = httpx.get(
|
|
1265
|
+
f"{config.scanners.acunetix_url}/api/v1/me",
|
|
1266
|
+
headers={"X-Auth": config.scanners.acunetix_api_key},
|
|
1267
|
+
verify=False,
|
|
1268
|
+
timeout=10,
|
|
1269
|
+
)
|
|
1270
|
+
if response.status_code == 200:
|
|
1271
|
+
console.print(f" [green]✓[/green] Acunetix connected")
|
|
1272
|
+
console.print(f" [dim]URL: {config.scanners.acunetix_url}[/dim]")
|
|
1273
|
+
results['acunetix'] = True
|
|
1274
|
+
else:
|
|
1275
|
+
console.print(f" [red]✗[/red] Acunetix auth failed (HTTP {response.status_code})")
|
|
1276
|
+
results['acunetix'] = False
|
|
1277
|
+
except Exception as e:
|
|
1278
|
+
console.print(f" [red]✗[/red] Acunetix connection failed: {str(e)[:50]}")
|
|
1279
|
+
results['acunetix'] = False
|
|
1280
|
+
|
|
1281
|
+
# Burp Suite
|
|
1282
|
+
if config.scanners.burp_url:
|
|
1283
|
+
scanners_tested += 1
|
|
1284
|
+
with console.status("[yellow]Testing Burp Suite connection...[/yellow]"):
|
|
1285
|
+
try:
|
|
1286
|
+
import httpx
|
|
1287
|
+
response = httpx.get(
|
|
1288
|
+
f"{config.scanners.burp_url}/api-internal/versions",
|
|
1289
|
+
headers={"Authorization": f"Bearer {config.scanners.burp_api_key}"},
|
|
1290
|
+
verify=False,
|
|
1291
|
+
timeout=10,
|
|
1292
|
+
)
|
|
1293
|
+
if response.status_code == 200:
|
|
1294
|
+
console.print(f" [green]✓[/green] Burp Suite connected")
|
|
1295
|
+
console.print(f" [dim]URL: {config.scanners.burp_url}[/dim]")
|
|
1296
|
+
results['burp'] = True
|
|
1297
|
+
else:
|
|
1298
|
+
console.print(f" [red]✗[/red] Burp Suite auth failed (HTTP {response.status_code})")
|
|
1299
|
+
results['burp'] = False
|
|
1300
|
+
except Exception as e:
|
|
1301
|
+
console.print(f" [red]✗[/red] Burp Suite connection failed: {str(e)[:50]}")
|
|
1302
|
+
results['burp'] = False
|
|
1303
|
+
|
|
1304
|
+
# Nessus
|
|
1305
|
+
if config.scanners.nessus_url:
|
|
1306
|
+
scanners_tested += 1
|
|
1307
|
+
with console.status("[yellow]Testing Nessus connection...[/yellow]"):
|
|
1308
|
+
try:
|
|
1309
|
+
import httpx
|
|
1310
|
+
response = httpx.get(
|
|
1311
|
+
f"{config.scanners.nessus_url}/server/status",
|
|
1312
|
+
headers={
|
|
1313
|
+
"X-ApiKeys": f"accessKey={config.scanners.nessus_access_key};secretKey={config.scanners.nessus_secret_key}"
|
|
1314
|
+
},
|
|
1315
|
+
verify=False,
|
|
1316
|
+
timeout=10,
|
|
1317
|
+
)
|
|
1318
|
+
if response.status_code == 200:
|
|
1319
|
+
console.print(f" [green]✓[/green] Nessus connected")
|
|
1320
|
+
console.print(f" [dim]URL: {config.scanners.nessus_url}[/dim]")
|
|
1321
|
+
results['nessus'] = True
|
|
1322
|
+
else:
|
|
1323
|
+
console.print(f" [red]✗[/red] Nessus auth failed (HTTP {response.status_code})")
|
|
1324
|
+
results['nessus'] = False
|
|
1325
|
+
except Exception as e:
|
|
1326
|
+
console.print(f" [red]✗[/red] Nessus connection failed: {str(e)[:50]}")
|
|
1327
|
+
results['nessus'] = False
|
|
1328
|
+
|
|
1329
|
+
# ZAP
|
|
1330
|
+
if config.scanners.zap_url:
|
|
1331
|
+
scanners_tested += 1
|
|
1332
|
+
with console.status("[yellow]Testing OWASP ZAP connection...[/yellow]"):
|
|
1333
|
+
try:
|
|
1334
|
+
import httpx
|
|
1335
|
+
url = f"{config.scanners.zap_url}/JSON/core/view/version/"
|
|
1336
|
+
if config.scanners.zap_api_key:
|
|
1337
|
+
url += f"?apikey={config.scanners.zap_api_key}"
|
|
1338
|
+
response = httpx.get(url, timeout=10)
|
|
1339
|
+
if response.status_code == 200:
|
|
1340
|
+
version = response.json().get("version", "unknown")
|
|
1341
|
+
console.print(f" [green]✓[/green] OWASP ZAP connected (v{version})")
|
|
1342
|
+
console.print(f" [dim]URL: {config.scanners.zap_url}[/dim]")
|
|
1343
|
+
results['zap'] = True
|
|
1344
|
+
else:
|
|
1345
|
+
console.print(f" [red]✗[/red] ZAP connection failed (HTTP {response.status_code})")
|
|
1346
|
+
results['zap'] = False
|
|
1347
|
+
except Exception as e:
|
|
1348
|
+
console.print(f" [red]✗[/red] ZAP connection failed: {str(e)[:50]}")
|
|
1349
|
+
results['zap'] = False
|
|
1350
|
+
|
|
1351
|
+
if scanners_tested == 0:
|
|
1352
|
+
console.print(" [yellow]○[/yellow] No scanners configured (optional)")
|
|
1353
|
+
|
|
1354
|
+
console.print()
|
|
1355
|
+
|
|
1356
|
+
# ======================== Local Tools Test ========================
|
|
1357
|
+
if test_all or getattr(args, 'tools', False):
|
|
1358
|
+
console.print("[bold cyan]━━━ Local Security Tools ━━━[/bold cyan]")
|
|
1359
|
+
|
|
1360
|
+
tools = {
|
|
1361
|
+
"nmap": "nmap --version",
|
|
1362
|
+
"subfinder": "subfinder -version",
|
|
1363
|
+
"httpx": "httpx -version",
|
|
1364
|
+
"nuclei": "nuclei -version",
|
|
1365
|
+
"ffuf": "ffuf -V",
|
|
1366
|
+
"gobuster": "gobuster version",
|
|
1367
|
+
"nikto": "nikto -Version",
|
|
1368
|
+
"sqlmap": "sqlmap --version",
|
|
1369
|
+
"wpscan": "wpscan --version",
|
|
1370
|
+
"amass": "amass -version",
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
found_tools = []
|
|
1374
|
+
missing_tools = []
|
|
1375
|
+
|
|
1376
|
+
for tool, check_cmd in tools.items():
|
|
1377
|
+
if shutil.which(tool):
|
|
1378
|
+
found_tools.append(tool)
|
|
1379
|
+
else:
|
|
1380
|
+
missing_tools.append(tool)
|
|
1381
|
+
|
|
1382
|
+
if found_tools:
|
|
1383
|
+
console.print(f" [green]✓[/green] Available: {', '.join(found_tools)}")
|
|
1384
|
+
|
|
1385
|
+
if missing_tools:
|
|
1386
|
+
console.print(f" [yellow]○[/yellow] Not found: {', '.join(missing_tools)}")
|
|
1387
|
+
console.print(" [dim]Install missing tools or use --use-vps to run on VPS[/dim]")
|
|
1388
|
+
|
|
1389
|
+
results['tools'] = len(found_tools)
|
|
1390
|
+
console.print()
|
|
1391
|
+
|
|
1392
|
+
# ======================== Summary ========================
|
|
1393
|
+
console.print("[bold cyan]━━━ Test Summary ━━━[/bold cyan]")
|
|
1394
|
+
|
|
1395
|
+
table = Table(box=box.ROUNDED)
|
|
1396
|
+
table.add_column("Component", style="cyan")
|
|
1397
|
+
table.add_column("Status", justify="center")
|
|
1398
|
+
table.add_column("Details")
|
|
1399
|
+
|
|
1400
|
+
for component, status in results.items():
|
|
1401
|
+
if status is True:
|
|
1402
|
+
status_str = "[green]✓ PASS[/green]"
|
|
1403
|
+
details = "Working correctly"
|
|
1404
|
+
elif status is False:
|
|
1405
|
+
status_str = "[red]✗ FAIL[/red]"
|
|
1406
|
+
details = "Check configuration"
|
|
1407
|
+
elif status is None:
|
|
1408
|
+
status_str = "[yellow]○ SKIP[/yellow]"
|
|
1409
|
+
details = "Not configured"
|
|
1410
|
+
elif isinstance(status, int):
|
|
1411
|
+
status_str = f"[green]✓ {status}[/green]"
|
|
1412
|
+
details = f"{status} tools available"
|
|
1413
|
+
else:
|
|
1414
|
+
status_str = "[dim]?[/dim]"
|
|
1415
|
+
details = "Unknown"
|
|
1416
|
+
|
|
1417
|
+
table.add_row(component.upper(), status_str, details)
|
|
1418
|
+
|
|
1419
|
+
console.print(table)
|
|
1420
|
+
|
|
1421
|
+
# Overall result
|
|
1422
|
+
failures = sum(1 for v in results.values() if v is False)
|
|
1423
|
+
if failures == 0:
|
|
1424
|
+
console.print("\n[bold green]✓ All tests passed![/bold green]")
|
|
1425
|
+
return 0
|
|
1426
|
+
else:
|
|
1427
|
+
console.print(f"\n[bold yellow]⚠ {failures} test(s) failed. Run 'aiptx setup' to fix.[/bold yellow]")
|
|
1428
|
+
return 1
|
|
1429
|
+
|
|
1430
|
+
|
|
1431
|
+
def run_preflight_check(console, use_vps=False, use_acunetix=False, use_burp=False, ai_mode=False):
|
|
1432
|
+
"""
|
|
1433
|
+
Run pre-flight checks before starting a scan.
|
|
1434
|
+
|
|
1435
|
+
Validates that all required components are configured and reachable.
|
|
1436
|
+
Returns True if all checks pass, False otherwise.
|
|
1437
|
+
|
|
1438
|
+
Args:
|
|
1439
|
+
console: Rich console for output
|
|
1440
|
+
use_vps: Whether VPS will be used for the scan
|
|
1441
|
+
use_acunetix: Whether Acunetix scanner will be used
|
|
1442
|
+
use_burp: Whether Burp Suite will be used
|
|
1443
|
+
ai_mode: Whether AI-guided scanning is enabled
|
|
1444
|
+
|
|
1445
|
+
Returns:
|
|
1446
|
+
bool: True if all checks pass, False if any fail
|
|
1447
|
+
"""
|
|
1448
|
+
import asyncio
|
|
1449
|
+
import shutil
|
|
1450
|
+
import time
|
|
1451
|
+
from rich.panel import Panel
|
|
1452
|
+
from rich.table import Table
|
|
1453
|
+
from rich import box
|
|
1454
|
+
|
|
1455
|
+
config = get_config()
|
|
1456
|
+
results = {}
|
|
1457
|
+
all_passed = True
|
|
1458
|
+
|
|
1459
|
+
console.print()
|
|
1460
|
+
console.print(Panel(
|
|
1461
|
+
"[bold]Pre-flight Configuration Check[/bold]\n\n"
|
|
1462
|
+
"Validating all required services before scan...",
|
|
1463
|
+
title="✈️ Pre-flight Check",
|
|
1464
|
+
border_style="cyan"
|
|
1465
|
+
))
|
|
1466
|
+
console.print()
|
|
1467
|
+
|
|
1468
|
+
# ======================== LLM Check (always required) ========================
|
|
1469
|
+
console.print("[bold cyan]━━━ LLM API ━━━[/bold cyan]")
|
|
1470
|
+
|
|
1471
|
+
if not config.llm.api_key:
|
|
1472
|
+
console.print(" [red]✗[/red] No API key configured")
|
|
1473
|
+
console.print(" [dim]Run 'aiptx setup' to configure[/dim]")
|
|
1474
|
+
results['llm'] = False
|
|
1475
|
+
all_passed = False
|
|
1476
|
+
else:
|
|
1477
|
+
with console.status("[yellow]Testing LLM API...[/yellow]"):
|
|
1478
|
+
try:
|
|
1479
|
+
import litellm
|
|
1480
|
+
|
|
1481
|
+
provider = config.llm.provider.lower()
|
|
1482
|
+
model = config.llm.model
|
|
1483
|
+
|
|
1484
|
+
if provider == "anthropic":
|
|
1485
|
+
model_str = f"anthropic/{model}" if not model.startswith("anthropic/") else model
|
|
1486
|
+
elif provider == "openai":
|
|
1487
|
+
model_str = f"openai/{model}" if not model.startswith("openai/") else model
|
|
1488
|
+
elif provider == "deepseek":
|
|
1489
|
+
model_str = f"deepseek/{model}" if not model.startswith("deepseek/") else model
|
|
1490
|
+
else:
|
|
1491
|
+
model_str = model
|
|
1492
|
+
|
|
1493
|
+
start = time.time()
|
|
1494
|
+
response = litellm.completion(
|
|
1495
|
+
model=model_str,
|
|
1496
|
+
messages=[{"role": "user", "content": "Reply with only: OK"}],
|
|
1497
|
+
max_tokens=10,
|
|
1498
|
+
timeout=30,
|
|
1499
|
+
)
|
|
1500
|
+
elapsed = time.time() - start
|
|
1501
|
+
|
|
1502
|
+
console.print(f" [green]✓[/green] LLM ready ({provider}/{model}) - {elapsed:.1f}s")
|
|
1503
|
+
results['llm'] = True
|
|
1504
|
+
|
|
1505
|
+
except ImportError:
|
|
1506
|
+
console.print(" [yellow]⚠[/yellow] litellm not installed")
|
|
1507
|
+
results['llm'] = None
|
|
1508
|
+
except Exception as e:
|
|
1509
|
+
console.print(f" [red]✗[/red] LLM connection failed: {str(e)[:60]}")
|
|
1510
|
+
results['llm'] = False
|
|
1511
|
+
all_passed = False
|
|
1512
|
+
|
|
1513
|
+
console.print()
|
|
1514
|
+
|
|
1515
|
+
# ======================== VPS Check (if requested) ========================
|
|
1516
|
+
if use_vps:
|
|
1517
|
+
console.print("[bold cyan]━━━ VPS Connection ━━━[/bold cyan]")
|
|
1518
|
+
|
|
1519
|
+
if not config.vps.host:
|
|
1520
|
+
console.print(" [red]✗[/red] VPS not configured")
|
|
1521
|
+
console.print(" [dim]Run 'aiptx setup' to configure VPS[/dim]")
|
|
1522
|
+
results['vps'] = False
|
|
1523
|
+
all_passed = False
|
|
1524
|
+
elif not config.vps.key_path:
|
|
1525
|
+
console.print(" [red]✗[/red] SSH key path not configured")
|
|
1526
|
+
results['vps'] = False
|
|
1527
|
+
all_passed = False
|
|
1528
|
+
else:
|
|
1529
|
+
with console.status("[yellow]Testing SSH connection...[/yellow]"):
|
|
1530
|
+
try:
|
|
1531
|
+
from pathlib import Path
|
|
1532
|
+
import asyncssh
|
|
1533
|
+
|
|
1534
|
+
key_path = Path(config.vps.key_path).expanduser()
|
|
1535
|
+
if not key_path.exists():
|
|
1536
|
+
console.print(f" [red]✗[/red] SSH key not found: {key_path}")
|
|
1537
|
+
results['vps'] = False
|
|
1538
|
+
all_passed = False
|
|
1539
|
+
else:
|
|
1540
|
+
async def test_ssh():
|
|
1541
|
+
start = time.time()
|
|
1542
|
+
conn = await asyncssh.connect(
|
|
1543
|
+
config.vps.host,
|
|
1544
|
+
port=config.vps.port,
|
|
1545
|
+
username=config.vps.user,
|
|
1546
|
+
client_keys=[str(key_path)],
|
|
1547
|
+
known_hosts=None,
|
|
1548
|
+
)
|
|
1549
|
+
await conn.close()
|
|
1550
|
+
return time.time() - start
|
|
1551
|
+
|
|
1552
|
+
elapsed = asyncio.run(test_ssh())
|
|
1553
|
+
console.print(f" [green]✓[/green] VPS connected ({config.vps.user}@{config.vps.host}) - {elapsed:.1f}s")
|
|
1554
|
+
results['vps'] = True
|
|
1555
|
+
|
|
1556
|
+
except ImportError:
|
|
1557
|
+
console.print(" [yellow]⚠[/yellow] asyncssh not installed")
|
|
1558
|
+
console.print(" [dim]Install with: pip install asyncssh[/dim]")
|
|
1559
|
+
results['vps'] = None
|
|
1560
|
+
except Exception as e:
|
|
1561
|
+
console.print(f" [red]✗[/red] VPS connection failed: {str(e)[:60]}")
|
|
1562
|
+
results['vps'] = False
|
|
1563
|
+
all_passed = False
|
|
1564
|
+
|
|
1565
|
+
console.print()
|
|
1566
|
+
|
|
1567
|
+
# ======================== Scanner Checks (if requested) ========================
|
|
1568
|
+
if use_acunetix or use_burp:
|
|
1569
|
+
console.print("[bold cyan]━━━ Scanner Integrations ━━━[/bold cyan]")
|
|
1570
|
+
|
|
1571
|
+
# Acunetix
|
|
1572
|
+
if use_acunetix:
|
|
1573
|
+
if not config.scanners.acunetix_url:
|
|
1574
|
+
console.print(" [red]✗[/red] Acunetix URL not configured")
|
|
1575
|
+
results['acunetix'] = False
|
|
1576
|
+
all_passed = False
|
|
1577
|
+
else:
|
|
1578
|
+
with console.status("[yellow]Testing Acunetix...[/yellow]"):
|
|
1579
|
+
try:
|
|
1580
|
+
import httpx
|
|
1581
|
+
response = httpx.get(
|
|
1582
|
+
f"{config.scanners.acunetix_url}/api/v1/me",
|
|
1583
|
+
headers={"X-Auth": config.scanners.acunetix_api_key or ""},
|
|
1584
|
+
verify=False,
|
|
1585
|
+
timeout=10,
|
|
1586
|
+
)
|
|
1587
|
+
if response.status_code == 200:
|
|
1588
|
+
console.print(f" [green]✓[/green] Acunetix connected")
|
|
1589
|
+
results['acunetix'] = True
|
|
1590
|
+
else:
|
|
1591
|
+
console.print(f" [red]✗[/red] Acunetix auth failed (HTTP {response.status_code})")
|
|
1592
|
+
results['acunetix'] = False
|
|
1593
|
+
all_passed = False
|
|
1594
|
+
except Exception as e:
|
|
1595
|
+
console.print(f" [red]✗[/red] Acunetix failed: {str(e)[:50]}")
|
|
1596
|
+
results['acunetix'] = False
|
|
1597
|
+
all_passed = False
|
|
1598
|
+
|
|
1599
|
+
# Burp Suite
|
|
1600
|
+
if use_burp:
|
|
1601
|
+
if not config.scanners.burp_url:
|
|
1602
|
+
console.print(" [red]✗[/red] Burp Suite URL not configured")
|
|
1603
|
+
results['burp'] = False
|
|
1604
|
+
all_passed = False
|
|
1605
|
+
else:
|
|
1606
|
+
with console.status("[yellow]Testing Burp Suite...[/yellow]"):
|
|
1607
|
+
try:
|
|
1608
|
+
import httpx
|
|
1609
|
+
response = httpx.get(
|
|
1610
|
+
f"{config.scanners.burp_url}/api-internal/versions",
|
|
1611
|
+
headers={"Authorization": f"Bearer {config.scanners.burp_api_key or ''}"},
|
|
1612
|
+
verify=False,
|
|
1613
|
+
timeout=10,
|
|
1614
|
+
)
|
|
1615
|
+
if response.status_code == 200:
|
|
1616
|
+
console.print(f" [green]✓[/green] Burp Suite connected")
|
|
1617
|
+
results['burp'] = True
|
|
1618
|
+
else:
|
|
1619
|
+
console.print(f" [red]✗[/red] Burp Suite auth failed (HTTP {response.status_code})")
|
|
1620
|
+
results['burp'] = False
|
|
1621
|
+
all_passed = False
|
|
1622
|
+
except Exception as e:
|
|
1623
|
+
console.print(f" [red]✗[/red] Burp Suite failed: {str(e)[:50]}")
|
|
1624
|
+
results['burp'] = False
|
|
1625
|
+
all_passed = False
|
|
1626
|
+
|
|
1627
|
+
console.print()
|
|
1628
|
+
|
|
1629
|
+
# ======================== Local Tools Check ========================
|
|
1630
|
+
console.print("[bold cyan]━━━ Local Security Tools ━━━[/bold cyan]")
|
|
1631
|
+
|
|
1632
|
+
essential_tools = ["nmap", "httpx", "nuclei"]
|
|
1633
|
+
optional_tools = ["subfinder", "ffuf", "nikto"]
|
|
1634
|
+
|
|
1635
|
+
found_essential = []
|
|
1636
|
+
missing_essential = []
|
|
1637
|
+
|
|
1638
|
+
for tool in essential_tools:
|
|
1639
|
+
if shutil.which(tool):
|
|
1640
|
+
found_essential.append(tool)
|
|
1641
|
+
else:
|
|
1642
|
+
missing_essential.append(tool)
|
|
1643
|
+
|
|
1644
|
+
found_optional = [t for t in optional_tools if shutil.which(t)]
|
|
1645
|
+
|
|
1646
|
+
if found_essential:
|
|
1647
|
+
console.print(f" [green]✓[/green] Essential: {', '.join(found_essential)}")
|
|
1648
|
+
if missing_essential:
|
|
1649
|
+
console.print(f" [yellow]⚠[/yellow] Missing essential: {', '.join(missing_essential)}")
|
|
1650
|
+
if not use_vps:
|
|
1651
|
+
console.print(" [dim]Consider using --use-vps or install locally[/dim]")
|
|
1652
|
+
if found_optional:
|
|
1653
|
+
console.print(f" [dim]○[/dim] Optional available: {', '.join(found_optional)}")
|
|
1654
|
+
|
|
1655
|
+
results['tools'] = len(missing_essential) == 0 or use_vps
|
|
1656
|
+
|
|
1657
|
+
console.print()
|
|
1658
|
+
|
|
1659
|
+
# ======================== Summary ========================
|
|
1660
|
+
console.print("[bold cyan]━━━ Pre-flight Summary ━━━[/bold cyan]")
|
|
1661
|
+
|
|
1662
|
+
table = Table(box=box.ROUNDED, show_header=False)
|
|
1663
|
+
table.add_column("Component", style="cyan")
|
|
1664
|
+
table.add_column("Status", justify="center")
|
|
1665
|
+
|
|
1666
|
+
for component, status in results.items():
|
|
1667
|
+
if status is True:
|
|
1668
|
+
status_str = "[green]✓ READY[/green]"
|
|
1669
|
+
elif status is False:
|
|
1670
|
+
status_str = "[red]✗ FAILED[/red]"
|
|
1671
|
+
elif status is None:
|
|
1672
|
+
status_str = "[yellow]○ SKIPPED[/yellow]"
|
|
1673
|
+
else:
|
|
1674
|
+
status_str = "[dim]?[/dim]"
|
|
1675
|
+
table.add_row(component.upper(), status_str)
|
|
1676
|
+
|
|
1677
|
+
console.print(table)
|
|
1678
|
+
console.print()
|
|
1679
|
+
|
|
1680
|
+
if all_passed:
|
|
1681
|
+
console.print("[bold green]✓ All pre-flight checks passed! Ready to scan.[/bold green]")
|
|
1682
|
+
else:
|
|
1683
|
+
console.print("[bold red]✗ Some checks failed. Fix issues above before scanning.[/bold red]")
|
|
1684
|
+
console.print("[dim]Run 'aiptx setup' to configure missing components.[/dim]")
|
|
1685
|
+
|
|
1686
|
+
console.print()
|
|
1687
|
+
|
|
1688
|
+
return all_passed
|
|
1689
|
+
|
|
1690
|
+
|
|
1691
|
+
def show_version():
|
|
1692
|
+
"""Show detailed version information."""
|
|
1693
|
+
from rich.console import Console
|
|
1694
|
+
from rich.panel import Panel
|
|
1695
|
+
|
|
1696
|
+
console = Console()
|
|
1697
|
+
|
|
1698
|
+
info = f"""
|
|
1699
|
+
[bold cyan]AIPT v2 - AI-Powered Penetration Testing Framework[/bold cyan]
|
|
1700
|
+
Version: {__version__}
|
|
1701
|
+
|
|
1702
|
+
[bold]Components:[/bold]
|
|
1703
|
+
• LLM Integration (litellm)
|
|
1704
|
+
• Scanner Integration (Acunetix, Burp, Nessus, ZAP)
|
|
1705
|
+
• VPS Execution Support
|
|
1706
|
+
• AI-Guided Scanning
|
|
1707
|
+
• Professional Report Generation
|
|
1708
|
+
|
|
1709
|
+
[bold]Documentation:[/bold]
|
|
1710
|
+
https://github.com/aipt/aipt-v2
|
|
1711
|
+
|
|
1712
|
+
[bold]Author:[/bold]
|
|
1713
|
+
Satyam Rastogi
|
|
1714
|
+
"""
|
|
1715
|
+
|
|
1716
|
+
console.print(Panel(info, title="Version Information", border_style="cyan"))
|
|
1717
|
+
|
|
1718
|
+
return 0
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
def run_verify(args):
|
|
1722
|
+
"""Verify AIPTX installation."""
|
|
1723
|
+
import asyncio
|
|
1724
|
+
|
|
1725
|
+
from aipt_v2.verify_install import verify_installation
|
|
1726
|
+
|
|
1727
|
+
quick = getattr(args, 'quick', False)
|
|
1728
|
+
auto_fix = getattr(args, 'fix', False)
|
|
1729
|
+
report_file = getattr(args, 'report', None)
|
|
1730
|
+
|
|
1731
|
+
return asyncio.run(verify_installation(
|
|
1732
|
+
quick=quick,
|
|
1733
|
+
auto_fix=auto_fix,
|
|
1734
|
+
report_file=report_file,
|
|
1735
|
+
))
|
|
1736
|
+
|
|
1737
|
+
|
|
1738
|
+
def run_shell(args):
|
|
1739
|
+
"""Start the interactive security shell."""
|
|
1740
|
+
import asyncio
|
|
1741
|
+
|
|
1742
|
+
from aipt_v2.interactive_shell import start_interactive_shell
|
|
1743
|
+
|
|
1744
|
+
log_file = getattr(args, 'log', None)
|
|
1745
|
+
working_dir = getattr(args, 'dir', None)
|
|
1746
|
+
|
|
1747
|
+
return asyncio.run(start_interactive_shell(
|
|
1748
|
+
log_file=log_file,
|
|
1749
|
+
working_dir=working_dir,
|
|
1750
|
+
))
|
|
1751
|
+
|
|
1752
|
+
|
|
1753
|
+
def run_tools_command(args):
|
|
1754
|
+
"""Handle tools subcommands for local tool management."""
|
|
1755
|
+
import asyncio
|
|
1756
|
+
from rich.console import Console
|
|
1757
|
+
from rich.panel import Panel
|
|
1758
|
+
from rich.table import Table
|
|
1759
|
+
from rich import box
|
|
1760
|
+
|
|
1761
|
+
console = Console()
|
|
1762
|
+
|
|
1763
|
+
tools_cmd = getattr(args, 'tools_command', None)
|
|
1764
|
+
|
|
1765
|
+
if tools_cmd == "install":
|
|
1766
|
+
return run_tools_install(args, console)
|
|
1767
|
+
elif tools_cmd == "list":
|
|
1768
|
+
return run_tools_list(args, console)
|
|
1769
|
+
elif tools_cmd == "check":
|
|
1770
|
+
return run_tools_check(args, console)
|
|
1771
|
+
else:
|
|
1772
|
+
console.print(Panel(
|
|
1773
|
+
"[bold cyan]AIPTX Local Tools Management[/bold cyan]\n\n"
|
|
1774
|
+
"[bold]aiptx tools install[/bold] - Install security tools locally\n"
|
|
1775
|
+
"[bold]aiptx tools list[/bold] - List available/installed tools\n"
|
|
1776
|
+
"[bold]aiptx tools check[/bold] - Check installed tool status\n\n"
|
|
1777
|
+
"[dim]Examples:[/dim]\n"
|
|
1778
|
+
" aiptx tools install --core # Install core tools\n"
|
|
1779
|
+
" aiptx tools install -c recon scan # Install by category\n"
|
|
1780
|
+
" aiptx tools install -t nmap nuclei # Install specific tools",
|
|
1781
|
+
title="🔧 Local Security Tools",
|
|
1782
|
+
border_style="cyan",
|
|
1783
|
+
))
|
|
1784
|
+
return 0
|
|
1785
|
+
|
|
1786
|
+
|
|
1787
|
+
def run_tools_install(args, console):
|
|
1788
|
+
"""Install security tools on local system."""
|
|
1789
|
+
import asyncio
|
|
1790
|
+
from rich.panel import Panel
|
|
1791
|
+
|
|
1792
|
+
console.print()
|
|
1793
|
+
console.print(Panel(
|
|
1794
|
+
"[bold cyan]Local Tool Installation[/bold cyan]\n\n"
|
|
1795
|
+
"Installing security tools on your local system.\n"
|
|
1796
|
+
"Some tools may require sudo/admin privileges.",
|
|
1797
|
+
title="🔧 Installation",
|
|
1798
|
+
border_style="cyan",
|
|
1799
|
+
))
|
|
1800
|
+
console.print()
|
|
1801
|
+
|
|
1802
|
+
async def _install():
|
|
1803
|
+
try:
|
|
1804
|
+
from aipt_v2.system_detector import SystemDetector
|
|
1805
|
+
from aipt_v2.local_tool_installer import LocalToolInstaller, TOOLS
|
|
1806
|
+
|
|
1807
|
+
# Detect system first
|
|
1808
|
+
detector = SystemDetector()
|
|
1809
|
+
with console.status("[bold cyan]Detecting system...[/bold cyan]"):
|
|
1810
|
+
system_info = await detector.detect()
|
|
1811
|
+
|
|
1812
|
+
console.print(f"[dim]Detected: {system_info.os_name} with {system_info.package_manager.value}[/dim]")
|
|
1813
|
+
|
|
1814
|
+
installer = LocalToolInstaller(system_info)
|
|
1815
|
+
use_sudo = not getattr(args, 'no_sudo', False)
|
|
1816
|
+
|
|
1817
|
+
# Determine what to install
|
|
1818
|
+
if getattr(args, 'all', False):
|
|
1819
|
+
console.print("[cyan]Installing all available tools...[/cyan]")
|
|
1820
|
+
results = await installer.install_all()
|
|
1821
|
+
elif getattr(args, 'tools', None):
|
|
1822
|
+
console.print(f"[cyan]Installing specific tools: {', '.join(args.tools)}[/cyan]")
|
|
1823
|
+
results = await installer.install_tools(tools=args.tools, use_sudo=use_sudo)
|
|
1824
|
+
elif getattr(args, 'categories', None):
|
|
1825
|
+
console.print(f"[cyan]Installing categories: {', '.join(args.categories)}[/cyan]")
|
|
1826
|
+
results = await installer.install_tools(categories=args.categories, use_sudo=use_sudo)
|
|
1827
|
+
else:
|
|
1828
|
+
# Default: core tools
|
|
1829
|
+
console.print("[cyan]Installing core security tools...[/cyan]")
|
|
1830
|
+
results = await installer.install_core_tools()
|
|
1831
|
+
|
|
1832
|
+
# Show summary
|
|
1833
|
+
installer.print_tool_status(results)
|
|
1834
|
+
return 0
|
|
1835
|
+
|
|
1836
|
+
except ImportError as e:
|
|
1837
|
+
console.print(f"[red]Error: Missing dependency - {e}[/red]")
|
|
1838
|
+
return 1
|
|
1839
|
+
except Exception as e:
|
|
1840
|
+
console.print(f"[red]Error during installation: {e}[/red]")
|
|
1841
|
+
return 1
|
|
1842
|
+
|
|
1843
|
+
loop = asyncio.new_event_loop()
|
|
1844
|
+
asyncio.set_event_loop(loop)
|
|
1845
|
+
try:
|
|
1846
|
+
return loop.run_until_complete(_install())
|
|
1847
|
+
finally:
|
|
1848
|
+
loop.close()
|
|
1849
|
+
|
|
1850
|
+
|
|
1851
|
+
def run_tools_list(args, console):
|
|
1852
|
+
"""List available and installed tools."""
|
|
1853
|
+
import asyncio
|
|
1854
|
+
from rich.table import Table
|
|
1855
|
+
from rich import box
|
|
1856
|
+
|
|
1857
|
+
async def _list():
|
|
1858
|
+
try:
|
|
1859
|
+
from aipt_v2.local_tool_installer import LocalToolInstaller, TOOLS
|
|
1860
|
+
|
|
1861
|
+
installer = LocalToolInstaller()
|
|
1862
|
+
installed = await installer.get_installed_tools()
|
|
1863
|
+
|
|
1864
|
+
category = getattr(args, 'category', 'all')
|
|
1865
|
+
installed_only = getattr(args, 'installed_only', False)
|
|
1866
|
+
|
|
1867
|
+
table = Table(title="Security Tools", box=box.ROUNDED)
|
|
1868
|
+
table.add_column("Tool", style="cyan")
|
|
1869
|
+
table.add_column("Category", style="dim")
|
|
1870
|
+
table.add_column("Status", justify="center")
|
|
1871
|
+
table.add_column("Description")
|
|
1872
|
+
|
|
1873
|
+
for tool_name, tool_def in sorted(TOOLS.items()):
|
|
1874
|
+
if category != 'all' and tool_def.category.value != category:
|
|
1875
|
+
continue
|
|
1876
|
+
|
|
1877
|
+
is_installed = installed.get(tool_name, False)
|
|
1878
|
+
|
|
1879
|
+
if installed_only and not is_installed:
|
|
1880
|
+
continue
|
|
1881
|
+
|
|
1882
|
+
status = "[green]✓ Installed[/green]" if is_installed else "[dim]○ Not installed[/dim]"
|
|
1883
|
+
core_badge = " [yellow]★[/yellow]" if tool_def.is_core else ""
|
|
1884
|
+
|
|
1885
|
+
table.add_row(
|
|
1886
|
+
f"{tool_name}{core_badge}",
|
|
1887
|
+
tool_def.category.value,
|
|
1888
|
+
status,
|
|
1889
|
+
tool_def.description[:50] + "..." if len(tool_def.description) > 50 else tool_def.description
|
|
1890
|
+
)
|
|
1891
|
+
|
|
1892
|
+
console.print()
|
|
1893
|
+
console.print(table)
|
|
1894
|
+
console.print()
|
|
1895
|
+
console.print("[dim]Legend: [yellow]★[/yellow] = Core tool (recommended)[/dim]")
|
|
1896
|
+
|
|
1897
|
+
return 0
|
|
1898
|
+
|
|
1899
|
+
except ImportError as e:
|
|
1900
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1901
|
+
return 1
|
|
1902
|
+
|
|
1903
|
+
loop = asyncio.new_event_loop()
|
|
1904
|
+
asyncio.set_event_loop(loop)
|
|
1905
|
+
try:
|
|
1906
|
+
return loop.run_until_complete(_list())
|
|
1907
|
+
finally:
|
|
1908
|
+
loop.close()
|
|
1909
|
+
|
|
1910
|
+
|
|
1911
|
+
def run_tools_check(args, console):
|
|
1912
|
+
"""Check installed tool status."""
|
|
1913
|
+
import asyncio
|
|
1914
|
+
from rich.table import Table
|
|
1915
|
+
from rich.panel import Panel
|
|
1916
|
+
from rich import box
|
|
1917
|
+
|
|
1918
|
+
async def _check():
|
|
1919
|
+
try:
|
|
1920
|
+
from aipt_v2.local_tool_installer import LocalToolInstaller, TOOLS
|
|
1921
|
+
|
|
1922
|
+
console.print()
|
|
1923
|
+
with console.status("[bold cyan]Checking installed tools...[/bold cyan]"):
|
|
1924
|
+
installer = LocalToolInstaller()
|
|
1925
|
+
installed = await installer.get_installed_tools()
|
|
1926
|
+
|
|
1927
|
+
# Count by category
|
|
1928
|
+
categories = {}
|
|
1929
|
+
for tool_name, tool_def in TOOLS.items():
|
|
1930
|
+
cat = tool_def.category.value
|
|
1931
|
+
if cat not in categories:
|
|
1932
|
+
categories[cat] = {"total": 0, "installed": 0}
|
|
1933
|
+
categories[cat]["total"] += 1
|
|
1934
|
+
if installed.get(tool_name, False):
|
|
1935
|
+
categories[cat]["installed"] += 1
|
|
1936
|
+
|
|
1937
|
+
# Summary table
|
|
1938
|
+
table = Table(box=box.ROUNDED)
|
|
1939
|
+
table.add_column("Category", style="cyan")
|
|
1940
|
+
table.add_column("Installed", justify="right", style="green")
|
|
1941
|
+
table.add_column("Total", justify="right")
|
|
1942
|
+
table.add_column("Coverage", justify="right")
|
|
1943
|
+
|
|
1944
|
+
total_installed = 0
|
|
1945
|
+
total_tools = 0
|
|
1946
|
+
|
|
1947
|
+
for cat, counts in sorted(categories.items()):
|
|
1948
|
+
coverage = (counts["installed"] / counts["total"] * 100) if counts["total"] > 0 else 0
|
|
1949
|
+
coverage_color = "green" if coverage >= 75 else "yellow" if coverage >= 50 else "red"
|
|
1950
|
+
|
|
1951
|
+
table.add_row(
|
|
1952
|
+
cat,
|
|
1953
|
+
str(counts["installed"]),
|
|
1954
|
+
str(counts["total"]),
|
|
1955
|
+
f"[{coverage_color}]{coverage:.0f}%[/{coverage_color}]"
|
|
1956
|
+
)
|
|
1957
|
+
|
|
1958
|
+
total_installed += counts["installed"]
|
|
1959
|
+
total_tools += counts["total"]
|
|
1960
|
+
|
|
1961
|
+
table.add_row("─" * 15, "─" * 5, "─" * 5, "─" * 7)
|
|
1962
|
+
total_coverage = (total_installed / total_tools * 100) if total_tools > 0 else 0
|
|
1963
|
+
table.add_row(
|
|
1964
|
+
"[bold]Total[/bold]",
|
|
1965
|
+
f"[bold]{total_installed}[/bold]",
|
|
1966
|
+
f"[bold]{total_tools}[/bold]",
|
|
1967
|
+
f"[bold]{total_coverage:.0f}%[/bold]"
|
|
1968
|
+
)
|
|
1969
|
+
|
|
1970
|
+
console.print()
|
|
1971
|
+
console.print(table)
|
|
1972
|
+
console.print()
|
|
1973
|
+
|
|
1974
|
+
if total_coverage < 50:
|
|
1975
|
+
console.print(Panel(
|
|
1976
|
+
"[yellow]Many security tools are not installed.[/yellow]\n\n"
|
|
1977
|
+
"Run [bold]aiptx tools install --core[/bold] to install essential tools.",
|
|
1978
|
+
title="💡 Recommendation",
|
|
1979
|
+
border_style="yellow"
|
|
1980
|
+
))
|
|
1981
|
+
|
|
1982
|
+
return 0
|
|
1983
|
+
|
|
1984
|
+
except ImportError as e:
|
|
1985
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1986
|
+
return 1
|
|
1987
|
+
|
|
1988
|
+
loop = asyncio.new_event_loop()
|
|
1989
|
+
asyncio.set_event_loop(loop)
|
|
1990
|
+
try:
|
|
1991
|
+
return loop.run_until_complete(_check())
|
|
1992
|
+
finally:
|
|
1993
|
+
loop.close()
|
|
1994
|
+
|
|
1995
|
+
|
|
1996
|
+
def run_vps_command(args):
|
|
1997
|
+
"""Handle VPS subcommands."""
|
|
1998
|
+
from rich.console import Console
|
|
1999
|
+
from rich.panel import Panel
|
|
2000
|
+
from rich.table import Table
|
|
2001
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
2002
|
+
|
|
2003
|
+
console = Console()
|
|
2004
|
+
|
|
2005
|
+
# Check VPS configuration
|
|
2006
|
+
config = get_config()
|
|
2007
|
+
if not config.vps.host:
|
|
2008
|
+
console.print(Panel(
|
|
2009
|
+
"[bold red]VPS not configured![/bold red]\n\n"
|
|
2010
|
+
"Run [bold green]aiptx setup[/bold green] to configure VPS settings.\n\n"
|
|
2011
|
+
"[bold]Required settings:[/bold]\n"
|
|
2012
|
+
" • VPS_HOST - VPS IP or hostname\n"
|
|
2013
|
+
" • VPS_USER - SSH username (default: ubuntu)\n"
|
|
2014
|
+
" • VPS_KEY - Path to SSH private key",
|
|
2015
|
+
title="⚠️ VPS Configuration Required",
|
|
2016
|
+
border_style="yellow",
|
|
2017
|
+
))
|
|
2018
|
+
return 1
|
|
2019
|
+
|
|
2020
|
+
vps_cmd = getattr(args, 'vps_command', None)
|
|
2021
|
+
|
|
2022
|
+
if vps_cmd == "setup":
|
|
2023
|
+
return run_vps_setup(args, console)
|
|
2024
|
+
elif vps_cmd == "status":
|
|
2025
|
+
return run_vps_status(args, console)
|
|
2026
|
+
elif vps_cmd == "scan":
|
|
2027
|
+
return run_vps_scan(args, console)
|
|
2028
|
+
elif vps_cmd == "script":
|
|
2029
|
+
return run_vps_script(args, console)
|
|
2030
|
+
else:
|
|
2031
|
+
console.print(Panel(
|
|
2032
|
+
"[bold cyan]AIPTX VPS Commands[/bold cyan]\n\n"
|
|
2033
|
+
"[bold]aiptx vps setup[/bold] - Install security tools on VPS\n"
|
|
2034
|
+
"[bold]aiptx vps status[/bold] - Check VPS connection and tools\n"
|
|
2035
|
+
"[bold]aiptx vps scan[/bold] - Run security scan from VPS\n"
|
|
2036
|
+
"[bold]aiptx vps script[/bold] - Generate setup script",
|
|
2037
|
+
title="🖥️ VPS Remote Execution",
|
|
2038
|
+
border_style="cyan",
|
|
2039
|
+
))
|
|
2040
|
+
return 0
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
def run_vps_setup(args, console):
|
|
2044
|
+
"""Install security tools on VPS with real-time progress."""
|
|
2045
|
+
from rich.live import Live
|
|
2046
|
+
from rich.table import Table
|
|
2047
|
+
from rich.panel import Panel
|
|
2048
|
+
from rich.text import Text
|
|
2049
|
+
from rich.console import Group
|
|
2050
|
+
from rich.spinner import Spinner
|
|
2051
|
+
from rich import box
|
|
2052
|
+
|
|
2053
|
+
# Check for asyncssh FIRST before any VPS operations
|
|
2054
|
+
try:
|
|
2055
|
+
import asyncssh
|
|
2056
|
+
except ImportError:
|
|
2057
|
+
console.print()
|
|
2058
|
+
console.print(Panel(
|
|
2059
|
+
"[bold red]Missing Dependency: asyncssh[/bold red]\n\n"
|
|
2060
|
+
"The VPS module requires asyncssh for SSH connectivity.\n\n"
|
|
2061
|
+
"[bold]Install with:[/bold]\n"
|
|
2062
|
+
" [green]pip install asyncssh[/green]\n"
|
|
2063
|
+
" [dim]or[/dim]\n"
|
|
2064
|
+
" [green]pip install aiptx[vps][/green]\n"
|
|
2065
|
+
" [dim]or[/dim]\n"
|
|
2066
|
+
" [green]pip install aiptx[full][/green]",
|
|
2067
|
+
title="⚠️ Dependency Required",
|
|
2068
|
+
border_style="yellow",
|
|
2069
|
+
))
|
|
2070
|
+
console.print()
|
|
2071
|
+
return 1
|
|
2072
|
+
|
|
2073
|
+
from aipt_v2.runtime.vps import VPSRuntime, VPS_TOOLS
|
|
2074
|
+
|
|
2075
|
+
console.print()
|
|
2076
|
+
console.print(Panel(
|
|
2077
|
+
"[bold cyan]VPS Tool Installation[/bold cyan]\n\n"
|
|
2078
|
+
"Installing security tools on your VPS.\n"
|
|
2079
|
+
"This may take 10-30 minutes depending on your VPS speed.",
|
|
2080
|
+
title="🔧 Setup",
|
|
2081
|
+
border_style="cyan",
|
|
2082
|
+
))
|
|
2083
|
+
console.print()
|
|
2084
|
+
|
|
2085
|
+
# Get categories and tools to install
|
|
2086
|
+
categories = getattr(args, 'categories', None)
|
|
2087
|
+
specific_tools = getattr(args, 'tools', None)
|
|
2088
|
+
|
|
2089
|
+
# Build list of tools to install
|
|
2090
|
+
tools_to_install = []
|
|
2091
|
+
if specific_tools:
|
|
2092
|
+
tools_to_install = specific_tools
|
|
2093
|
+
elif categories:
|
|
2094
|
+
for cat in categories:
|
|
2095
|
+
if cat in VPS_TOOLS:
|
|
2096
|
+
tools_to_install.extend(VPS_TOOLS[cat].keys())
|
|
2097
|
+
else:
|
|
2098
|
+
for cat_tools in VPS_TOOLS.values():
|
|
2099
|
+
tools_to_install.extend(cat_tools.keys())
|
|
2100
|
+
|
|
2101
|
+
# State for live display
|
|
2102
|
+
state = {
|
|
2103
|
+
"status": "Connecting...",
|
|
2104
|
+
"current_tool": "",
|
|
2105
|
+
"output": [],
|
|
2106
|
+
"results": {},
|
|
2107
|
+
"total": len(tools_to_install),
|
|
2108
|
+
"completed": 0,
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
def make_display():
|
|
2112
|
+
"""Generate the live display."""
|
|
2113
|
+
lines = []
|
|
2114
|
+
|
|
2115
|
+
# Status line
|
|
2116
|
+
status_text = Text()
|
|
2117
|
+
status_text.append("⚡ ", style="yellow")
|
|
2118
|
+
status_text.append(state["status"], style="bold cyan")
|
|
2119
|
+
lines.append(status_text)
|
|
2120
|
+
|
|
2121
|
+
# Progress
|
|
2122
|
+
if state["total"] > 0:
|
|
2123
|
+
pct = (state["completed"] / state["total"]) * 100
|
|
2124
|
+
bar_width = 40
|
|
2125
|
+
filled = int(bar_width * state["completed"] / state["total"])
|
|
2126
|
+
bar = "█" * filled + "░" * (bar_width - filled)
|
|
2127
|
+
progress_text = Text()
|
|
2128
|
+
progress_text.append(f" [{bar}] ", style="cyan")
|
|
2129
|
+
progress_text.append(f"{state['completed']}/{state['total']} ", style="bold")
|
|
2130
|
+
progress_text.append(f"({pct:.0f}%)", style="dim")
|
|
2131
|
+
lines.append(progress_text)
|
|
2132
|
+
|
|
2133
|
+
# Current tool
|
|
2134
|
+
if state["current_tool"]:
|
|
2135
|
+
tool_text = Text()
|
|
2136
|
+
tool_text.append(" → Installing: ", style="dim")
|
|
2137
|
+
tool_text.append(state["current_tool"], style="bold green")
|
|
2138
|
+
lines.append(tool_text)
|
|
2139
|
+
|
|
2140
|
+
# Recent output (last 5 lines)
|
|
2141
|
+
if state["output"]:
|
|
2142
|
+
lines.append(Text())
|
|
2143
|
+
lines.append(Text(" Recent output:", style="dim"))
|
|
2144
|
+
for line in state["output"][-5:]:
|
|
2145
|
+
output_text = Text()
|
|
2146
|
+
output_text.append(" │ ", style="dim cyan")
|
|
2147
|
+
# Truncate long lines
|
|
2148
|
+
display_line = line[:70] + "..." if len(line) > 70 else line
|
|
2149
|
+
output_text.append(display_line, style="dim")
|
|
2150
|
+
lines.append(output_text)
|
|
2151
|
+
|
|
2152
|
+
return Group(*lines)
|
|
2153
|
+
|
|
2154
|
+
async def setup_vps_live(live):
|
|
2155
|
+
runtime = VPSRuntime()
|
|
2156
|
+
|
|
2157
|
+
# Connect
|
|
2158
|
+
state["status"] = "Connecting to VPS..."
|
|
2159
|
+
live.update(make_display())
|
|
2160
|
+
await runtime.connect()
|
|
2161
|
+
state["status"] = f"Connected to {runtime.host}"
|
|
2162
|
+
state["output"].append(f"✓ Connected to {runtime.host}")
|
|
2163
|
+
live.update(make_display())
|
|
2164
|
+
|
|
2165
|
+
# Setup base dependencies first
|
|
2166
|
+
state["status"] = "Installing base dependencies..."
|
|
2167
|
+
state["current_tool"] = "apt packages, Go, Python, Ruby"
|
|
2168
|
+
live.update(make_display())
|
|
2169
|
+
|
|
2170
|
+
setup_script = """
|
|
2171
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
2172
|
+
apt-get update -qq
|
|
2173
|
+
apt-get install -y -qq git curl wget python3-pip golang-go ruby-full build-essential libssl-dev libffi-dev 2>/dev/null
|
|
2174
|
+
export GOPATH=$HOME/go
|
|
2175
|
+
export PATH=$PATH:$GOPATH/bin:/usr/local/go/bin
|
|
2176
|
+
mkdir -p $GOPATH/bin
|
|
2177
|
+
echo 'Base dependencies installed'
|
|
2178
|
+
"""
|
|
2179
|
+
stdout, stderr, code = await runtime._run_command(f"sudo bash -c '{setup_script}'", timeout=300)
|
|
2180
|
+
state["output"].append("✓ Base dependencies installed")
|
|
2181
|
+
live.update(make_display())
|
|
2182
|
+
|
|
2183
|
+
# Install each tool
|
|
2184
|
+
state["status"] = "Installing security tools..."
|
|
2185
|
+
|
|
2186
|
+
for tool_name in tools_to_install:
|
|
2187
|
+
state["current_tool"] = tool_name
|
|
2188
|
+
state["output"].append(f"Installing {tool_name}...")
|
|
2189
|
+
live.update(make_display())
|
|
2190
|
+
|
|
2191
|
+
success = await runtime.install_tool(tool_name)
|
|
2192
|
+
state["results"][tool_name] = success
|
|
2193
|
+
state["completed"] += 1
|
|
2194
|
+
|
|
2195
|
+
if success:
|
|
2196
|
+
state["output"].append(f"✓ {tool_name} installed")
|
|
2197
|
+
else:
|
|
2198
|
+
state["output"].append(f"✗ {tool_name} failed")
|
|
2199
|
+
|
|
2200
|
+
live.update(make_display())
|
|
2201
|
+
|
|
2202
|
+
state["status"] = "Installation complete!"
|
|
2203
|
+
state["current_tool"] = ""
|
|
2204
|
+
live.update(make_display())
|
|
2205
|
+
|
|
2206
|
+
await runtime.disconnect()
|
|
2207
|
+
return state["results"]
|
|
2208
|
+
|
|
2209
|
+
# Run with live display
|
|
2210
|
+
try:
|
|
2211
|
+
with Live(make_display(), console=console, refresh_per_second=4) as live:
|
|
2212
|
+
results = asyncio.run(setup_vps_live(live))
|
|
2213
|
+
except KeyboardInterrupt:
|
|
2214
|
+
console.print("\n[yellow]Installation interrupted by user[/yellow]")
|
|
2215
|
+
return 130
|
|
2216
|
+
|
|
2217
|
+
# Show final results
|
|
2218
|
+
console.print()
|
|
2219
|
+
table = Table(title="Installation Results", box=box.ROUNDED)
|
|
2220
|
+
table.add_column("Tool", style="cyan")
|
|
2221
|
+
table.add_column("Status", style="green")
|
|
2222
|
+
|
|
2223
|
+
installed = 0
|
|
2224
|
+
failed = 0
|
|
2225
|
+
for tool, success in sorted(results.items()):
|
|
2226
|
+
if success:
|
|
2227
|
+
table.add_row(tool, "[green]✓ Installed[/green]")
|
|
2228
|
+
installed += 1
|
|
2229
|
+
else:
|
|
2230
|
+
table.add_row(tool, "[red]✗ Failed[/red]")
|
|
2231
|
+
failed += 1
|
|
2232
|
+
|
|
2233
|
+
console.print(table)
|
|
2234
|
+
console.print()
|
|
2235
|
+
|
|
2236
|
+
if failed == 0:
|
|
2237
|
+
console.print(Panel(
|
|
2238
|
+
f"[bold green]✓ All {installed} tools installed successfully![/bold green]\n\n"
|
|
2239
|
+
"You can now run:\n"
|
|
2240
|
+
" [bold]aiptx vps scan target.com[/bold]",
|
|
2241
|
+
title="🎉 Setup Complete",
|
|
2242
|
+
border_style="green",
|
|
2243
|
+
))
|
|
2244
|
+
else:
|
|
2245
|
+
console.print(f"[bold]Summary:[/bold] {installed} installed, [red]{failed} failed[/red]")
|
|
2246
|
+
console.print("[dim]Failed tools may require manual installation on VPS[/dim]")
|
|
2247
|
+
|
|
2248
|
+
return 0 if failed == 0 else 1
|
|
2249
|
+
|
|
2250
|
+
|
|
2251
|
+
def run_vps_status(args, console):
|
|
2252
|
+
"""Check VPS connection and installed tools."""
|
|
2253
|
+
from rich.table import Table
|
|
2254
|
+
from rich.panel import Panel
|
|
2255
|
+
from rich.live import Live
|
|
2256
|
+
from rich.text import Text
|
|
2257
|
+
from rich.console import Group
|
|
2258
|
+
from rich import box
|
|
2259
|
+
|
|
2260
|
+
# Check for asyncssh FIRST
|
|
2261
|
+
try:
|
|
2262
|
+
import asyncssh
|
|
2263
|
+
except ImportError:
|
|
2264
|
+
console.print()
|
|
2265
|
+
console.print(Panel(
|
|
2266
|
+
"[bold red]Missing Dependency: asyncssh[/bold red]\n\n"
|
|
2267
|
+
"The VPS module requires asyncssh for SSH connectivity.\n\n"
|
|
2268
|
+
"[bold]Install with:[/bold]\n"
|
|
2269
|
+
" [green]pip install asyncssh[/green]\n"
|
|
2270
|
+
" [dim]or[/dim]\n"
|
|
2271
|
+
" [green]pip install aiptx[vps][/green]",
|
|
2272
|
+
title="⚠️ Dependency Required",
|
|
2273
|
+
border_style="yellow",
|
|
2274
|
+
))
|
|
2275
|
+
console.print()
|
|
2276
|
+
return 1
|
|
2277
|
+
|
|
2278
|
+
from aipt_v2.runtime.vps import VPSRuntime, VPS_TOOLS
|
|
2279
|
+
|
|
2280
|
+
config = get_config()
|
|
2281
|
+
|
|
2282
|
+
# State for live display
|
|
2283
|
+
state = {"status": "Connecting...", "tool": "", "checked": 0, "total": 0}
|
|
2284
|
+
|
|
2285
|
+
def make_status():
|
|
2286
|
+
text = Text()
|
|
2287
|
+
text.append("⚡ ", style="yellow")
|
|
2288
|
+
text.append(state["status"], style="bold cyan")
|
|
2289
|
+
if state["tool"]:
|
|
2290
|
+
text.append(f" - checking {state['tool']}", style="dim")
|
|
2291
|
+
if state["total"] > 0:
|
|
2292
|
+
text.append(f" ({state['checked']}/{state['total']})", style="dim")
|
|
2293
|
+
return text
|
|
2294
|
+
|
|
2295
|
+
async def check_status_live(live):
|
|
2296
|
+
runtime = VPSRuntime()
|
|
2297
|
+
|
|
2298
|
+
# Try to connect
|
|
2299
|
+
state["status"] = "Connecting to VPS..."
|
|
2300
|
+
live.update(make_status())
|
|
2301
|
+
|
|
2302
|
+
try:
|
|
2303
|
+
await runtime.connect()
|
|
2304
|
+
except Exception as e:
|
|
2305
|
+
return False, str(e), {}
|
|
2306
|
+
|
|
2307
|
+
state["status"] = f"Connected to {runtime.host}"
|
|
2308
|
+
live.update(make_status())
|
|
2309
|
+
|
|
2310
|
+
# Count total tools
|
|
2311
|
+
total_tools = sum(len(tools) for tools in VPS_TOOLS.values())
|
|
2312
|
+
state["total"] = total_tools
|
|
2313
|
+
state["status"] = "Checking installed tools..."
|
|
2314
|
+
live.update(make_status())
|
|
2315
|
+
|
|
2316
|
+
# Check each tool
|
|
2317
|
+
tools_status = {}
|
|
2318
|
+
for category, tools in VPS_TOOLS.items():
|
|
2319
|
+
for tool_name, tool_info in tools.items():
|
|
2320
|
+
state["tool"] = tool_name
|
|
2321
|
+
state["checked"] += 1
|
|
2322
|
+
live.update(make_status())
|
|
2323
|
+
|
|
2324
|
+
check_cmd = tool_info.get("check", f"which {tool_name}")
|
|
2325
|
+
stdout, stderr, code = await runtime._run_command(check_cmd, timeout=10)
|
|
2326
|
+
tools_status[tool_name] = code == 0
|
|
2327
|
+
|
|
2328
|
+
state["status"] = "Done!"
|
|
2329
|
+
state["tool"] = ""
|
|
2330
|
+
live.update(make_status())
|
|
2331
|
+
|
|
2332
|
+
await runtime.disconnect()
|
|
2333
|
+
return True, "Connected", tools_status
|
|
2334
|
+
|
|
2335
|
+
console.print()
|
|
2336
|
+
|
|
2337
|
+
try:
|
|
2338
|
+
with Live(make_status(), console=console, refresh_per_second=4) as live:
|
|
2339
|
+
connected, message, tools_status = asyncio.run(check_status_live(live))
|
|
2340
|
+
except KeyboardInterrupt:
|
|
2341
|
+
console.print("\n[yellow]Interrupted[/yellow]")
|
|
2342
|
+
return 130
|
|
2343
|
+
|
|
2344
|
+
# Connection status
|
|
2345
|
+
console.print()
|
|
2346
|
+
if connected:
|
|
2347
|
+
console.print(f"[green]✓[/green] Connected to [bold]{config.vps.host}[/bold]")
|
|
2348
|
+
else:
|
|
2349
|
+
console.print(f"[red]✗[/red] Failed to connect: {message}")
|
|
2350
|
+
return 1
|
|
2351
|
+
|
|
2352
|
+
# Tool status table
|
|
2353
|
+
console.print()
|
|
2354
|
+
table = Table(title="Security Tools Status", box=box.ROUNDED)
|
|
2355
|
+
table.add_column("Category", style="cyan")
|
|
2356
|
+
table.add_column("Tool", style="white")
|
|
2357
|
+
table.add_column("Status", style="green")
|
|
2358
|
+
|
|
2359
|
+
for category, tools in VPS_TOOLS.items():
|
|
2360
|
+
for tool_name in tools:
|
|
2361
|
+
status = tools_status.get(tool_name, False)
|
|
2362
|
+
status_str = "[green]✓ Installed[/green]" if status else "[dim]○ Not installed[/dim]"
|
|
2363
|
+
table.add_row(category, tool_name, status_str)
|
|
2364
|
+
|
|
2365
|
+
console.print(table)
|
|
2366
|
+
|
|
2367
|
+
# Summary
|
|
2368
|
+
installed = sum(1 for v in tools_status.values() if v)
|
|
2369
|
+
total = len(tools_status)
|
|
2370
|
+
console.print()
|
|
2371
|
+
|
|
2372
|
+
if installed == total:
|
|
2373
|
+
console.print(Panel(
|
|
2374
|
+
f"[bold green]✓ All {total} tools installed![/bold green]\n\n"
|
|
2375
|
+
"Your VPS is ready for scanning.\n"
|
|
2376
|
+
"Run: [bold]aiptx vps scan target.com[/bold]",
|
|
2377
|
+
title="🎉 VPS Ready",
|
|
2378
|
+
border_style="green",
|
|
2379
|
+
))
|
|
2380
|
+
else:
|
|
2381
|
+
console.print(f"[bold]Tools:[/bold] {installed}/{total} installed")
|
|
2382
|
+
console.print()
|
|
2383
|
+
console.print("[dim]Run 'aiptx vps setup' to install missing tools[/dim]")
|
|
2384
|
+
|
|
2385
|
+
return 0
|
|
2386
|
+
|
|
2387
|
+
|
|
2388
|
+
def run_vps_scan(args, console):
|
|
2389
|
+
"""Run security scan from VPS."""
|
|
2390
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
2391
|
+
from rich.panel import Panel
|
|
2392
|
+
|
|
2393
|
+
# Check for asyncssh FIRST
|
|
2394
|
+
try:
|
|
2395
|
+
import asyncssh
|
|
2396
|
+
except ImportError:
|
|
2397
|
+
console.print()
|
|
2398
|
+
console.print(Panel(
|
|
2399
|
+
"[bold red]Missing Dependency: asyncssh[/bold red]\n\n"
|
|
2400
|
+
"The VPS module requires asyncssh for SSH connectivity.\n\n"
|
|
2401
|
+
"[bold]Install with:[/bold]\n"
|
|
2402
|
+
" [green]pip install asyncssh[/green]\n"
|
|
2403
|
+
" [dim]or[/dim]\n"
|
|
2404
|
+
" [green]pip install aiptx[vps][/green]",
|
|
2405
|
+
title="⚠️ Dependency Required",
|
|
2406
|
+
border_style="yellow",
|
|
2407
|
+
))
|
|
2408
|
+
console.print()
|
|
2409
|
+
return 1
|
|
2410
|
+
|
|
2411
|
+
target = args.target
|
|
2412
|
+
mode = getattr(args, 'mode', 'standard')
|
|
2413
|
+
tools = getattr(args, 'tools', None)
|
|
2414
|
+
|
|
2415
|
+
console.print()
|
|
2416
|
+
console.print(Panel(
|
|
2417
|
+
f"[bold]Target:[/bold] {target}\n"
|
|
2418
|
+
f"[bold]Mode:[/bold] {mode}\n"
|
|
2419
|
+
f"[bold]Tools:[/bold] {', '.join(tools) if tools else 'Auto-selected'}",
|
|
2420
|
+
title="🎯 VPS Scan Configuration",
|
|
2421
|
+
border_style="cyan",
|
|
2422
|
+
))
|
|
2423
|
+
console.print()
|
|
2424
|
+
|
|
2425
|
+
from aipt_v2.runtime.vps import VPSRuntime
|
|
2426
|
+
|
|
2427
|
+
async def run_scan():
|
|
2428
|
+
runtime = VPSRuntime()
|
|
2429
|
+
await runtime.connect()
|
|
2430
|
+
|
|
2431
|
+
results = await runtime.run_scan(
|
|
2432
|
+
target=target,
|
|
2433
|
+
scan_type=mode,
|
|
2434
|
+
tools=tools,
|
|
2435
|
+
)
|
|
2436
|
+
|
|
2437
|
+
await runtime.disconnect()
|
|
2438
|
+
return results
|
|
2439
|
+
|
|
2440
|
+
with Progress(
|
|
2441
|
+
SpinnerColumn(),
|
|
2442
|
+
TextColumn("[progress.description]{task.description}"),
|
|
2443
|
+
console=console,
|
|
2444
|
+
) as progress:
|
|
2445
|
+
task = progress.add_task(f"[cyan]Scanning {target} from VPS...", total=None)
|
|
2446
|
+
results = asyncio.run(run_scan())
|
|
2447
|
+
|
|
2448
|
+
# Display results
|
|
2449
|
+
console.print()
|
|
2450
|
+
console.print("[bold green]✓ Scan complete![/bold green]")
|
|
2451
|
+
console.print()
|
|
2452
|
+
console.print(f"[bold]Results saved to:[/bold] {results.get('local_results_path', 'N/A')}")
|
|
2453
|
+
console.print()
|
|
2454
|
+
|
|
2455
|
+
# Show tool outputs summary
|
|
2456
|
+
tool_outputs = results.get('tool_outputs', {})
|
|
2457
|
+
if tool_outputs:
|
|
2458
|
+
from rich.table import Table
|
|
2459
|
+
table = Table(title="Tool Execution Summary")
|
|
2460
|
+
table.add_column("Tool", style="cyan")
|
|
2461
|
+
table.add_column("Exit Code", style="green")
|
|
2462
|
+
table.add_column("Output Size", style="yellow")
|
|
2463
|
+
|
|
2464
|
+
for tool, output in tool_outputs.items():
|
|
2465
|
+
exit_code = output.get('exit_code', -1)
|
|
2466
|
+
status = "[green]✓[/green]" if exit_code == 0 else f"[red]{exit_code}[/red]"
|
|
2467
|
+
stdout_len = len(output.get('stdout', ''))
|
|
2468
|
+
table.add_row(tool, status, f"{stdout_len} bytes")
|
|
2469
|
+
|
|
2470
|
+
console.print(table)
|
|
2471
|
+
|
|
2472
|
+
return 0
|
|
2473
|
+
|
|
2474
|
+
|
|
2475
|
+
def run_vps_script(args, console):
|
|
2476
|
+
"""Generate VPS setup script."""
|
|
2477
|
+
from aipt_v2.runtime.vps import generate_vps_setup_script
|
|
2478
|
+
|
|
2479
|
+
categories = getattr(args, 'categories', None)
|
|
2480
|
+
output_file = getattr(args, 'output', None)
|
|
2481
|
+
|
|
2482
|
+
script = generate_vps_setup_script(categories=categories)
|
|
2483
|
+
|
|
2484
|
+
if output_file:
|
|
2485
|
+
with open(output_file, 'w') as f:
|
|
2486
|
+
f.write(script)
|
|
2487
|
+
console.print(f"[green]✓[/green] Script saved to: {output_file}")
|
|
2488
|
+
console.print(f"[dim]Run on VPS: curl -sL <url> | sudo bash[/dim]")
|
|
2489
|
+
else:
|
|
2490
|
+
console.print(script)
|
|
2491
|
+
|
|
2492
|
+
return 0
|
|
2493
|
+
|
|
2494
|
+
|
|
2495
|
+
def run_ai_command(args):
|
|
2496
|
+
"""Handle AI security testing commands."""
|
|
2497
|
+
from rich.console import Console
|
|
2498
|
+
from rich.panel import Panel
|
|
2499
|
+
from rich.table import Table
|
|
2500
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
2501
|
+
from rich import box
|
|
2502
|
+
import json
|
|
2503
|
+
|
|
2504
|
+
console = Console()
|
|
2505
|
+
|
|
2506
|
+
ai_cmd = getattr(args, 'ai_command', None)
|
|
2507
|
+
|
|
2508
|
+
if ai_cmd == "code-review":
|
|
2509
|
+
return run_ai_code_review(args, console)
|
|
2510
|
+
elif ai_cmd == "api-test":
|
|
2511
|
+
return run_ai_api_test(args, console)
|
|
2512
|
+
elif ai_cmd == "web-pentest":
|
|
2513
|
+
return run_ai_web_pentest(args, console)
|
|
2514
|
+
elif ai_cmd == "full":
|
|
2515
|
+
return run_ai_full_assessment(args, console)
|
|
2516
|
+
else:
|
|
2517
|
+
console.print()
|
|
2518
|
+
console.print(Panel(
|
|
2519
|
+
"[bold cyan]AIPTX AI Security Testing[/bold cyan]\n\n"
|
|
2520
|
+
"AI-powered security testing using LLMs (Claude, GPT, etc.)\n\n"
|
|
2521
|
+
"[bold]Commands:[/bold]\n"
|
|
2522
|
+
" [bold green]aiptx ai code-review[/bold green] <path> - AI source code security review\n"
|
|
2523
|
+
" [bold green]aiptx ai api-test[/bold green] <url> - AI REST API security testing\n"
|
|
2524
|
+
" [bold green]aiptx ai web-pentest[/bold green] <url> - AI web penetration testing\n"
|
|
2525
|
+
" [bold green]aiptx ai full[/bold green] <target> - Full AI-driven assessment\n\n"
|
|
2526
|
+
"[bold]Examples:[/bold]\n"
|
|
2527
|
+
" aiptx ai code-review ./src --focus sqli xss\n"
|
|
2528
|
+
" aiptx ai api-test https://api.example.com --openapi swagger.json\n"
|
|
2529
|
+
" aiptx ai web-pentest https://example.com --quick",
|
|
2530
|
+
title="🤖 AI Security Testing",
|
|
2531
|
+
border_style="cyan",
|
|
2532
|
+
))
|
|
2533
|
+
console.print()
|
|
2534
|
+
return 0
|
|
2535
|
+
|
|
2536
|
+
|
|
2537
|
+
def run_ai_code_review(args, console):
|
|
2538
|
+
"""Run AI-powered source code security review."""
|
|
2539
|
+
from rich.panel import Panel
|
|
2540
|
+
from rich.table import Table
|
|
2541
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
|
|
2542
|
+
from rich.live import Live
|
|
2543
|
+
from rich.text import Text
|
|
2544
|
+
from rich import box
|
|
2545
|
+
import json
|
|
2546
|
+
|
|
2547
|
+
target = args.target
|
|
2548
|
+
focus = getattr(args, 'focus', None)
|
|
2549
|
+
model = getattr(args, 'model', 'claude-sonnet-4-20250514')
|
|
2550
|
+
max_steps = getattr(args, 'max_steps', 100)
|
|
2551
|
+
quick = getattr(args, 'quick', False)
|
|
2552
|
+
output_file = getattr(args, 'output', None)
|
|
2553
|
+
|
|
2554
|
+
# Verify target exists
|
|
2555
|
+
if not Path(target).exists():
|
|
2556
|
+
console.print(f"[red]Error:[/red] Target path does not exist: {target}")
|
|
2557
|
+
return 1
|
|
2558
|
+
|
|
2559
|
+
console.print()
|
|
2560
|
+
console.print(Panel(
|
|
2561
|
+
f"[bold]Target:[/bold] {target}\n"
|
|
2562
|
+
f"[bold]Model:[/bold] {model}\n"
|
|
2563
|
+
f"[bold]Mode:[/bold] {'Quick scan' if quick else 'Full review'}\n"
|
|
2564
|
+
f"[bold]Focus:[/bold] {', '.join(focus) if focus else 'All vulnerabilities'}",
|
|
2565
|
+
title="🔍 AI Code Review",
|
|
2566
|
+
border_style="cyan",
|
|
2567
|
+
))
|
|
2568
|
+
console.print()
|
|
2569
|
+
|
|
2570
|
+
# Import agent
|
|
2571
|
+
from aipt_v2.skills.agents.code_review import CodeReviewAgent
|
|
2572
|
+
from aipt_v2.skills.agents.base import AgentConfig
|
|
2573
|
+
|
|
2574
|
+
config = AgentConfig(
|
|
2575
|
+
model=model,
|
|
2576
|
+
max_steps=max_steps,
|
|
2577
|
+
verbose=True,
|
|
2578
|
+
)
|
|
2579
|
+
|
|
2580
|
+
agent = CodeReviewAgent(
|
|
2581
|
+
target_path=target,
|
|
2582
|
+
config=config,
|
|
2583
|
+
focus_areas=focus,
|
|
2584
|
+
)
|
|
2585
|
+
|
|
2586
|
+
# Run the review
|
|
2587
|
+
with Progress(
|
|
2588
|
+
SpinnerColumn(),
|
|
2589
|
+
TextColumn("[progress.description]{task.description}"),
|
|
2590
|
+
console=console,
|
|
2591
|
+
) as progress:
|
|
2592
|
+
task = progress.add_task("[cyan]AI reviewing code...", total=None)
|
|
2593
|
+
|
|
2594
|
+
try:
|
|
2595
|
+
if quick:
|
|
2596
|
+
result = asyncio.run(agent.quick_scan())
|
|
2597
|
+
else:
|
|
2598
|
+
result = asyncio.run(agent.run())
|
|
2599
|
+
except KeyboardInterrupt:
|
|
2600
|
+
console.print("\n[yellow]Review interrupted by user[/yellow]")
|
|
2601
|
+
return 130
|
|
2602
|
+
except Exception as e:
|
|
2603
|
+
console.print(f"\n[red]Error:[/red] {e}")
|
|
2604
|
+
return 1
|
|
2605
|
+
|
|
2606
|
+
# Display results
|
|
2607
|
+
console.print()
|
|
2608
|
+
display_ai_results(console, result, "Code Review")
|
|
2609
|
+
|
|
2610
|
+
# Save to file if requested
|
|
2611
|
+
if output_file:
|
|
2612
|
+
with open(output_file, 'w') as f:
|
|
2613
|
+
json.dump(result.to_dict(), f, indent=2)
|
|
2614
|
+
console.print(f"\n[green]✓[/green] Results saved to: {output_file}")
|
|
2615
|
+
|
|
2616
|
+
return 0 if result.success else 1
|
|
2617
|
+
|
|
2618
|
+
|
|
2619
|
+
def run_ai_api_test(args, console):
|
|
2620
|
+
"""Run AI-powered API security testing."""
|
|
2621
|
+
from rich.panel import Panel
|
|
2622
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
2623
|
+
import json
|
|
2624
|
+
|
|
2625
|
+
target = args.target
|
|
2626
|
+
openapi_spec = getattr(args, 'openapi', None)
|
|
2627
|
+
auth_token = getattr(args, 'auth_token', None)
|
|
2628
|
+
model = getattr(args, 'model', 'claude-sonnet-4-20250514')
|
|
2629
|
+
max_steps = getattr(args, 'max_steps', 100)
|
|
2630
|
+
output_file = getattr(args, 'output', None)
|
|
2631
|
+
|
|
2632
|
+
console.print()
|
|
2633
|
+
console.print(Panel(
|
|
2634
|
+
f"[bold]Target:[/bold] {target}\n"
|
|
2635
|
+
f"[bold]Model:[/bold] {model}\n"
|
|
2636
|
+
f"[bold]OpenAPI Spec:[/bold] {openapi_spec or 'Not provided'}\n"
|
|
2637
|
+
f"[bold]Authentication:[/bold] {'Bearer token' if auth_token else 'None'}",
|
|
2638
|
+
title="🔌 AI API Security Test",
|
|
2639
|
+
border_style="cyan",
|
|
2640
|
+
))
|
|
2641
|
+
console.print()
|
|
2642
|
+
|
|
2643
|
+
# Import agent
|
|
2644
|
+
from aipt_v2.skills.agents.api_tester import APITestAgent
|
|
2645
|
+
from aipt_v2.skills.agents.base import AgentConfig
|
|
2646
|
+
|
|
2647
|
+
config = AgentConfig(
|
|
2648
|
+
model=model,
|
|
2649
|
+
max_steps=max_steps,
|
|
2650
|
+
verbose=True,
|
|
2651
|
+
)
|
|
2652
|
+
|
|
2653
|
+
agent = APITestAgent(
|
|
2654
|
+
base_url=target,
|
|
2655
|
+
config=config,
|
|
2656
|
+
openapi_spec=openapi_spec,
|
|
2657
|
+
auth_token=auth_token,
|
|
2658
|
+
)
|
|
2659
|
+
|
|
2660
|
+
# Run the test
|
|
2661
|
+
with Progress(
|
|
2662
|
+
SpinnerColumn(),
|
|
2663
|
+
TextColumn("[progress.description]{task.description}"),
|
|
2664
|
+
console=console,
|
|
2665
|
+
) as progress:
|
|
2666
|
+
task = progress.add_task("[cyan]AI testing API...", total=None)
|
|
2667
|
+
|
|
2668
|
+
try:
|
|
2669
|
+
result = asyncio.run(agent.run())
|
|
2670
|
+
except KeyboardInterrupt:
|
|
2671
|
+
console.print("\n[yellow]Test interrupted by user[/yellow]")
|
|
2672
|
+
return 130
|
|
2673
|
+
except Exception as e:
|
|
2674
|
+
console.print(f"\n[red]Error:[/red] {e}")
|
|
2675
|
+
return 1
|
|
2676
|
+
|
|
2677
|
+
# Display results
|
|
2678
|
+
console.print()
|
|
2679
|
+
display_ai_results(console, result, "API Test")
|
|
2680
|
+
|
|
2681
|
+
# Save to file if requested
|
|
2682
|
+
if output_file:
|
|
2683
|
+
with open(output_file, 'w') as f:
|
|
2684
|
+
json.dump(result.to_dict(), f, indent=2)
|
|
2685
|
+
console.print(f"\n[green]✓[/green] Results saved to: {output_file}")
|
|
2686
|
+
|
|
2687
|
+
return 0 if result.success else 1
|
|
2688
|
+
|
|
2689
|
+
|
|
2690
|
+
def run_ai_web_pentest(args, console):
|
|
2691
|
+
"""Run AI-powered web penetration testing."""
|
|
2692
|
+
from rich.panel import Panel
|
|
2693
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
2694
|
+
import json
|
|
2695
|
+
|
|
2696
|
+
target = args.target
|
|
2697
|
+
auth_token = getattr(args, 'auth_token', None)
|
|
2698
|
+
cookies_list = getattr(args, 'cookie', None) or []
|
|
2699
|
+
model = getattr(args, 'model', 'claude-sonnet-4-20250514')
|
|
2700
|
+
max_steps = getattr(args, 'max_steps', 100)
|
|
2701
|
+
quick = getattr(args, 'quick', False)
|
|
2702
|
+
output_file = getattr(args, 'output', None)
|
|
2703
|
+
|
|
2704
|
+
# Parse cookies
|
|
2705
|
+
cookies = {}
|
|
2706
|
+
for cookie in cookies_list:
|
|
2707
|
+
if '=' in cookie:
|
|
2708
|
+
key, value = cookie.split('=', 1)
|
|
2709
|
+
cookies[key] = value
|
|
2710
|
+
|
|
2711
|
+
console.print()
|
|
2712
|
+
console.print(Panel(
|
|
2713
|
+
f"[bold]Target:[/bold] {target}\n"
|
|
2714
|
+
f"[bold]Model:[/bold] {model}\n"
|
|
2715
|
+
f"[bold]Mode:[/bold] {'Quick scan' if quick else 'Full pentest'}\n"
|
|
2716
|
+
f"[bold]Authentication:[/bold] {'Token + Cookies' if auth_token and cookies else 'Token' if auth_token else 'Cookies' if cookies else 'None'}",
|
|
2717
|
+
title="🌐 AI Web Penetration Test",
|
|
2718
|
+
border_style="cyan",
|
|
2719
|
+
))
|
|
2720
|
+
console.print()
|
|
2721
|
+
|
|
2722
|
+
# Import agent
|
|
2723
|
+
from aipt_v2.skills.agents.web_pentest import WebPentestAgent
|
|
2724
|
+
from aipt_v2.skills.agents.base import AgentConfig
|
|
2725
|
+
|
|
2726
|
+
config = AgentConfig(
|
|
2727
|
+
model=model,
|
|
2728
|
+
max_steps=max_steps,
|
|
2729
|
+
verbose=True,
|
|
2730
|
+
)
|
|
2731
|
+
|
|
2732
|
+
agent = WebPentestAgent(
|
|
2733
|
+
target=target,
|
|
2734
|
+
config=config,
|
|
2735
|
+
cookies=cookies if cookies else None,
|
|
2736
|
+
auth_token=auth_token,
|
|
2737
|
+
)
|
|
2738
|
+
|
|
2739
|
+
# Run the test
|
|
2740
|
+
with Progress(
|
|
2741
|
+
SpinnerColumn(),
|
|
2742
|
+
TextColumn("[progress.description]{task.description}"),
|
|
2743
|
+
console=console,
|
|
2744
|
+
) as progress:
|
|
2745
|
+
task = progress.add_task("[cyan]AI pentesting web app...", total=None)
|
|
2746
|
+
|
|
2747
|
+
try:
|
|
2748
|
+
if quick:
|
|
2749
|
+
result = asyncio.run(agent.quick_scan())
|
|
2750
|
+
else:
|
|
2751
|
+
result = asyncio.run(agent.run())
|
|
2752
|
+
except KeyboardInterrupt:
|
|
2753
|
+
console.print("\n[yellow]Pentest interrupted by user[/yellow]")
|
|
2754
|
+
return 130
|
|
2755
|
+
except Exception as e:
|
|
2756
|
+
console.print(f"\n[red]Error:[/red] {e}")
|
|
2757
|
+
return 1
|
|
2758
|
+
|
|
2759
|
+
# Display results
|
|
2760
|
+
console.print()
|
|
2761
|
+
display_ai_results(console, result, "Web Pentest")
|
|
2762
|
+
|
|
2763
|
+
# Save to file if requested
|
|
2764
|
+
if output_file:
|
|
2765
|
+
with open(output_file, 'w') as f:
|
|
2766
|
+
json.dump(result.to_dict(), f, indent=2)
|
|
2767
|
+
console.print(f"\n[green]✓[/green] Results saved to: {output_file}")
|
|
2768
|
+
|
|
2769
|
+
return 0 if result.success else 1
|
|
2770
|
+
|
|
2771
|
+
|
|
2772
|
+
def run_ai_full_assessment(args, console):
|
|
2773
|
+
"""Run full AI-driven security assessment."""
|
|
2774
|
+
from rich.panel import Panel
|
|
2775
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
2776
|
+
import json
|
|
2777
|
+
|
|
2778
|
+
target = args.target
|
|
2779
|
+
test_types = getattr(args, 'types', ['web'])
|
|
2780
|
+
model = getattr(args, 'model', 'claude-sonnet-4-20250514')
|
|
2781
|
+
output_file = getattr(args, 'output', None)
|
|
2782
|
+
|
|
2783
|
+
console.print()
|
|
2784
|
+
console.print(Panel(
|
|
2785
|
+
f"[bold]Target:[/bold] {target}\n"
|
|
2786
|
+
f"[bold]Model:[/bold] {model}\n"
|
|
2787
|
+
f"[bold]Test Types:[/bold] {', '.join(test_types)}",
|
|
2788
|
+
title="🎯 Full AI Security Assessment",
|
|
2789
|
+
border_style="cyan",
|
|
2790
|
+
))
|
|
2791
|
+
console.print()
|
|
2792
|
+
|
|
2793
|
+
# Import agent
|
|
2794
|
+
from aipt_v2.skills.agents.security_agent import SecurityAgent
|
|
2795
|
+
from aipt_v2.skills.agents.base import AgentConfig
|
|
2796
|
+
|
|
2797
|
+
config = AgentConfig(
|
|
2798
|
+
model=model,
|
|
2799
|
+
max_steps=150, # More steps for full assessment
|
|
2800
|
+
verbose=True,
|
|
2801
|
+
)
|
|
2802
|
+
|
|
2803
|
+
agent = SecurityAgent(
|
|
2804
|
+
target=target,
|
|
2805
|
+
config=config,
|
|
2806
|
+
test_types=test_types,
|
|
2807
|
+
)
|
|
2808
|
+
|
|
2809
|
+
# Run the assessment
|
|
2810
|
+
with Progress(
|
|
2811
|
+
SpinnerColumn(),
|
|
2812
|
+
TextColumn("[progress.description]{task.description}"),
|
|
2813
|
+
console=console,
|
|
2814
|
+
) as progress:
|
|
2815
|
+
task = progress.add_task("[cyan]Running full AI assessment...", total=None)
|
|
2816
|
+
|
|
2817
|
+
try:
|
|
2818
|
+
results = asyncio.run(agent.run_full_assessment())
|
|
2819
|
+
combined = agent.combine_results(results)
|
|
2820
|
+
except KeyboardInterrupt:
|
|
2821
|
+
console.print("\n[yellow]Assessment interrupted by user[/yellow]")
|
|
2822
|
+
return 130
|
|
2823
|
+
except Exception as e:
|
|
2824
|
+
console.print(f"\n[red]Error:[/red] {e}")
|
|
2825
|
+
return 1
|
|
2826
|
+
|
|
2827
|
+
# Display results
|
|
2828
|
+
console.print()
|
|
2829
|
+
display_ai_results(console, combined, "Full Assessment")
|
|
2830
|
+
|
|
2831
|
+
# Show per-type results
|
|
2832
|
+
for test_type, result in results.items():
|
|
2833
|
+
if result.findings:
|
|
2834
|
+
console.print(f"\n[bold]{test_type.upper()} Findings:[/bold] {len(result.findings)}")
|
|
2835
|
+
|
|
2836
|
+
# Save to file if requested
|
|
2837
|
+
if output_file:
|
|
2838
|
+
with open(output_file, 'w') as f:
|
|
2839
|
+
json.dump(combined.to_dict(), f, indent=2)
|
|
2840
|
+
console.print(f"\n[green]✓[/green] Results saved to: {output_file}")
|
|
2841
|
+
|
|
2842
|
+
return 0 if combined.success else 1
|
|
2843
|
+
|
|
2844
|
+
|
|
2845
|
+
def display_ai_results(console, result, test_name):
|
|
2846
|
+
"""Display AI testing results in a formatted way."""
|
|
2847
|
+
from rich.table import Table
|
|
2848
|
+
from rich.panel import Panel
|
|
2849
|
+
from rich import box
|
|
2850
|
+
|
|
2851
|
+
# Summary panel
|
|
2852
|
+
severity_colors = {
|
|
2853
|
+
"critical": "red",
|
|
2854
|
+
"high": "orange1",
|
|
2855
|
+
"medium": "yellow",
|
|
2856
|
+
"low": "blue",
|
|
2857
|
+
"info": "dim",
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
# Count by severity
|
|
2861
|
+
severity_counts = {}
|
|
2862
|
+
for finding in result.findings:
|
|
2863
|
+
sev = finding.severity.value
|
|
2864
|
+
severity_counts[sev] = severity_counts.get(sev, 0) + 1
|
|
2865
|
+
|
|
2866
|
+
summary_parts = []
|
|
2867
|
+
for sev in ["critical", "high", "medium", "low", "info"]:
|
|
2868
|
+
count = severity_counts.get(sev, 0)
|
|
2869
|
+
if count > 0:
|
|
2870
|
+
color = severity_colors.get(sev, "white")
|
|
2871
|
+
summary_parts.append(f"[{color}]{sev.upper()}: {count}[/{color}]")
|
|
2872
|
+
|
|
2873
|
+
summary = " | ".join(summary_parts) if summary_parts else "[green]No vulnerabilities found[/green]"
|
|
2874
|
+
|
|
2875
|
+
console.print(Panel(
|
|
2876
|
+
f"[bold]Findings:[/bold] {len(result.findings)}\n"
|
|
2877
|
+
f"[bold]Severity:[/bold] {summary}\n"
|
|
2878
|
+
f"[bold]Steps:[/bold] {result.total_steps}\n"
|
|
2879
|
+
f"[bold]Time:[/bold] {result.execution_time:.1f}s\n"
|
|
2880
|
+
f"[bold]Model:[/bold] {result.model_used}",
|
|
2881
|
+
title=f"📊 {test_name} Results",
|
|
2882
|
+
border_style="green" if result.success else "red",
|
|
2883
|
+
))
|
|
2884
|
+
|
|
2885
|
+
# Findings table
|
|
2886
|
+
if result.findings:
|
|
2887
|
+
console.print()
|
|
2888
|
+
table = Table(title="Security Findings", box=box.ROUNDED)
|
|
2889
|
+
table.add_column("#", style="dim", width=3)
|
|
2890
|
+
table.add_column("Severity", width=10)
|
|
2891
|
+
table.add_column("Title", style="white")
|
|
2892
|
+
table.add_column("Location", style="dim")
|
|
2893
|
+
|
|
2894
|
+
for i, finding in enumerate(result.findings, 1):
|
|
2895
|
+
sev_color = severity_colors.get(finding.severity.value, "white")
|
|
2896
|
+
severity_text = f"[{sev_color}]{finding.severity.value.upper()}[/{sev_color}]"
|
|
2897
|
+
table.add_row(
|
|
2898
|
+
str(i),
|
|
2899
|
+
severity_text,
|
|
2900
|
+
finding.title[:50] + "..." if len(finding.title) > 50 else finding.title,
|
|
2901
|
+
finding.location[:40] + "..." if len(finding.location) > 40 else finding.location,
|
|
2902
|
+
)
|
|
2903
|
+
|
|
2904
|
+
console.print(table)
|
|
2905
|
+
|
|
2906
|
+
# Show details for critical/high findings
|
|
2907
|
+
critical_high = [f for f in result.findings if f.severity.value in ["critical", "high"]]
|
|
2908
|
+
if critical_high:
|
|
2909
|
+
console.print()
|
|
2910
|
+
console.print("[bold red]Critical/High Severity Details:[/bold red]")
|
|
2911
|
+
for finding in critical_high[:5]: # Limit to 5 detailed findings
|
|
2912
|
+
console.print(f"\n[bold]{finding.title}[/bold]")
|
|
2913
|
+
console.print(f" [dim]Location:[/dim] {finding.location}")
|
|
2914
|
+
console.print(f" [dim]Description:[/dim] {finding.description[:200]}...")
|
|
2915
|
+
if finding.remediation:
|
|
2916
|
+
console.print(f" [dim]Fix:[/dim] {finding.remediation[:150]}...")
|
|
2917
|
+
|
|
2918
|
+
# Errors
|
|
2919
|
+
if result.errors:
|
|
2920
|
+
console.print()
|
|
2921
|
+
console.print("[bold red]Errors:[/bold red]")
|
|
2922
|
+
for error in result.errors:
|
|
2923
|
+
console.print(f" [red]•[/red] {error}")
|
|
2924
|
+
|
|
2925
|
+
|
|
2926
|
+
if __name__ == "__main__":
|
|
2927
|
+
try:
|
|
2928
|
+
sys.exit(main())
|
|
2929
|
+
except KeyboardInterrupt:
|
|
2930
|
+
# Handle Ctrl+C gracefully without traceback
|
|
2931
|
+
from rich.console import Console
|
|
2932
|
+
Console().print("\n[yellow]Operation cancelled.[/yellow]")
|
|
2933
|
+
sys.exit(130)
|