exploitsynth 0.3.2__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/PKG-INFO +1 -1
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/__init__.py +1 -1
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/cli.py +74 -32
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/pyproject.toml +1 -1
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/.gitignore +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/README.md +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/__main__.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/client.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/config.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/discover.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/live.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/output.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/parsers/__init__.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/parsers/nessus.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/parsers/nmap.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/ports.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/targets.py +0 -0
- {exploitsynth-0.3.2 → exploitsynth-0.3.3}/exploitsynth/tunnel.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: exploitsynth
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: ExploitSynth scanner CLI — AI port-identification from your terminal
|
|
5
5
|
Project-URL: Homepage, https://scan.exploitsynth.com
|
|
6
6
|
Project-URL: Documentation, https://scan.exploitsynth.com/docs
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
import re
|
|
6
7
|
import sys
|
|
7
8
|
from pathlib import Path
|
|
@@ -63,6 +64,68 @@ def _client() -> Client:
|
|
|
63
64
|
output.fail(str(e))
|
|
64
65
|
|
|
65
66
|
|
|
67
|
+
def _resolve_scan_id(client: Client, partial: str) -> str:
|
|
68
|
+
"""Accept a full UUID or the short prefix that `scans` prints (e.g. 004bafa9)."""
|
|
69
|
+
p = partial.strip()
|
|
70
|
+
if len(p) >= 32: # already a full UUID
|
|
71
|
+
return p
|
|
72
|
+
try:
|
|
73
|
+
rows = client.list_scans(limit=100)
|
|
74
|
+
except ApiError as e:
|
|
75
|
+
output.fail(str(e))
|
|
76
|
+
matches = [str(r["id"]) for r in rows if str(r.get("id", "")).startswith(p)]
|
|
77
|
+
if not matches:
|
|
78
|
+
output.fail(f"No recent scan matching '{partial}'.")
|
|
79
|
+
if len(matches) > 1:
|
|
80
|
+
output.fail(f"'{partial}' matches several scans; use more characters.")
|
|
81
|
+
return matches[0]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _render_scan(
|
|
85
|
+
client: Client,
|
|
86
|
+
scan_id: str,
|
|
87
|
+
*,
|
|
88
|
+
reasoning: bool = False,
|
|
89
|
+
output_path: "Optional[Path]" = None,
|
|
90
|
+
json_output: bool = False,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Fetch one scan and print its results; optionally save the full payload to a file."""
|
|
93
|
+
full_id = _resolve_scan_id(client, scan_id)
|
|
94
|
+
try:
|
|
95
|
+
data = client.get_scan(full_id)
|
|
96
|
+
except ApiError as e:
|
|
97
|
+
output.fail(str(e))
|
|
98
|
+
|
|
99
|
+
if output_path is not None:
|
|
100
|
+
output_path.write_text(json.dumps(data, indent=2) + "\n")
|
|
101
|
+
output.note(f"Saved results to {output_path}")
|
|
102
|
+
|
|
103
|
+
if json_output:
|
|
104
|
+
output.emit_json(data)
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
scan = data["scan"]
|
|
108
|
+
results = data["results"]
|
|
109
|
+
|
|
110
|
+
header = Text()
|
|
111
|
+
header.append(scan.get("ip", "?"), style="bold white")
|
|
112
|
+
header.append(f" {scan.get('status', '?')}", style="dim")
|
|
113
|
+
out.print(header)
|
|
114
|
+
|
|
115
|
+
if not results:
|
|
116
|
+
out.print(Text("No services identified.", style="dim"))
|
|
117
|
+
else:
|
|
118
|
+
out.print(results_table(results))
|
|
119
|
+
if reasoning:
|
|
120
|
+
for r in sorted(results, key=lambda x: x.get("port", 0)):
|
|
121
|
+
if r.get("extra_info"):
|
|
122
|
+
out.print(Text(f"\n:{r.get('port')} {r.get('extra_info')}", style="dim"))
|
|
123
|
+
|
|
124
|
+
if scan.get("summary"):
|
|
125
|
+
out.print(Text("\nSummary", style="bold"))
|
|
126
|
+
out.print(Text(scan["summary"], style="dim"))
|
|
127
|
+
|
|
128
|
+
|
|
66
129
|
# ─────────────────────────── login ───────────────────────────
|
|
67
130
|
@app.command()
|
|
68
131
|
def login(
|
|
@@ -324,17 +387,24 @@ def scan(
|
|
|
324
387
|
# ─────────────────────────── scans (list) ─────────────────────
|
|
325
388
|
@app.command(name="scans")
|
|
326
389
|
def list_scans(
|
|
390
|
+
scan_id: Optional[str] = typer.Argument(None, help="A scan id (full or short) to view its results; omit to list."),
|
|
327
391
|
project: Optional[str] = typer.Option(None, "--project", help="Filter by project name."),
|
|
328
392
|
limit: int = typer.Option(20, "--limit", "-n", help="Max rows."),
|
|
393
|
+
output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="When viewing one scan, save its results as JSON here."),
|
|
329
394
|
json_output: bool = JSON_OPT,
|
|
330
395
|
):
|
|
331
|
-
"""List recent scans
|
|
396
|
+
"""List recent scans, or view one: `exploitsynth scans <id>`.
|
|
332
397
|
|
|
333
398
|
Example:
|
|
334
399
|
exploitsynth scans --json | jq '.[] | {id, status}'
|
|
335
400
|
"""
|
|
336
401
|
output.set_json(json_output)
|
|
337
402
|
client = _client()
|
|
403
|
+
|
|
404
|
+
if scan_id:
|
|
405
|
+
_render_scan(client, scan_id, output_path=output_path, json_output=json_output)
|
|
406
|
+
return
|
|
407
|
+
|
|
338
408
|
try:
|
|
339
409
|
rows = client.list_scans(project=project, limit=limit)
|
|
340
410
|
except ApiError as e:
|
|
@@ -370,43 +440,15 @@ def list_scans(
|
|
|
370
440
|
# ─────────────────────────── show ────────────────────────────
|
|
371
441
|
@app.command()
|
|
372
442
|
def show(
|
|
373
|
-
scan_id: str = typer.Argument(..., help="Scan id (full UUID)."),
|
|
443
|
+
scan_id: str = typer.Argument(..., help="Scan id (full UUID or the short prefix `scans` prints)."),
|
|
374
444
|
reasoning: bool = typer.Option(False, "--reasoning", "-r", help="Include the agent's reasoning per port."),
|
|
445
|
+
output_path: Optional[Path] = typer.Option(None, "--output", "-o", help="Save the full results as JSON to this file."),
|
|
375
446
|
json_output: bool = JSON_OPT,
|
|
376
447
|
):
|
|
377
448
|
"""Show one scan's results."""
|
|
378
449
|
output.set_json(json_output)
|
|
379
450
|
client = _client()
|
|
380
|
-
|
|
381
|
-
data = client.get_scan(scan_id)
|
|
382
|
-
except ApiError as e:
|
|
383
|
-
output.fail(str(e))
|
|
384
|
-
|
|
385
|
-
if json_output:
|
|
386
|
-
output.emit_json(data)
|
|
387
|
-
return
|
|
388
|
-
|
|
389
|
-
scan = data["scan"]
|
|
390
|
-
results = data["results"]
|
|
391
|
-
|
|
392
|
-
header = Text()
|
|
393
|
-
header.append(scan.get("ip", "?"), style="bold white")
|
|
394
|
-
header.append(f" {scan.get('status', '?')}", style="dim")
|
|
395
|
-
out.print(header)
|
|
396
|
-
|
|
397
|
-
if not results:
|
|
398
|
-
out.print(Text("No services identified.", style="dim"))
|
|
399
|
-
else:
|
|
400
|
-
out.print(results_table(results))
|
|
401
|
-
|
|
402
|
-
if reasoning:
|
|
403
|
-
for r in sorted(results, key=lambda x: x.get("port", 0)):
|
|
404
|
-
if r.get("extra_info"):
|
|
405
|
-
out.print(Text(f"\n:{r.get('port')} — {r.get('extra_info')}", style="dim"))
|
|
406
|
-
|
|
407
|
-
if scan.get("summary"):
|
|
408
|
-
out.print(Text("\nSummary", style="bold"))
|
|
409
|
-
out.print(Text(scan["summary"], style="dim"))
|
|
451
|
+
_render_scan(client, scan_id, reasoning=reasoning, output_path=output_path, json_output=json_output)
|
|
410
452
|
|
|
411
453
|
|
|
412
454
|
# ─────────────────────────── credits ─────────────────────────
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|