SpooNMAP is a Python 3.6+ wrapper that orchestrates masscan (fast port discovery) followed by nmap (service banner grabbing). Both external tools must be installed separately.
# Interactive mode (prompts for all options)
./spoonmap.py
# Config file mode (skip all prompts)
cp config.json.sample config.json
# Edit config.json, then:
./spoonmap.pyRun the test suite with:
uv run pytest tests/
uv run pytest tests/test_spoonmap.py::TestGenerateFindings # single classPass --cleanup [dir] to remove prior scan output non-interactively (reads output_path from config.json if no directory is given).
Internal discovery runs a single masscan sweep (no source-port override) followed by an optional concurrent nmap -sn sweep:
_discover_internal_masscan()— single masscan sweep acrossDISCOVERY_MASSCAN_PORTS_INTERNAL(10 ports: 22, 80, 135, 443, 445, 1433, 3306, 3389, 5985, 8080) with no-gflag. Rate is capped toINTERNAL_DISCOVERY_MAX_RATE = 1000 pps; at 1000 pps with a 60 s half-open timeout, state table entries peak at ~60 K. Uses--retries 1. Port list trimmed to 5 aboveINTERNAL_DISCOVERY_STATE_CEILING = 262_144(/14).--waitis adaptive: 1 s for 512 targets, 2 s for 4096, 3 s otherwise._internal_host_discovery()— for target counts at or belowHOST_DISCOVERY_NMAP_THRESHOLD = 65_536, starts_nmap_host_discovery()in a backgroundthreading.Threadbefore the masscan sweep begins, then joins the thread after masscan returns. Returns masscan IPs union nmap IPs.
Note: The -g 88 (Kerberos source port) bypass for Windows Firewall in AD environments is no longer attempted. The no-source-port sweep is used exclusively.
- Shell injection prevention: all subprocess calls use list form (
subprocess.Popen(cmd_list)) not shell strings - IP deduplication: uses Python
set()in memory per port; also reads existing files on resume to avoid duplicates - Terminal state: saves/restores
termiosstate around each subprocess call; falls back tostty sane - Interrupt handling: masscan raises
KeyboardInterruptand re-raises after cleanup; nmap usesthreading.Eventpolling so all worker threads can be signaled cleanly - Resume behavior:
nmap_scan()skips ports wherenmap_results/portN.xmlalready exists. Discovery resume is mtime-gated:preprocess_targets()writesdiscovery/resolved_targets.txtvia_write_if_changed()(rewritten only when the resolved target set changes), and_host_discovery(),mass_scan(),_nmap_port_discovery(), and_nmap_udp_discovery()reuse cached output only when it is at least as new asresolved_targets.txt. So an unchangedranges.txtskips discovery on resume; a changed one bumps the mtime and forces re-discovery of the affected phases. - Hostname support: hostnames in the target file are resolved once at startup; nmap receives the original hostname (for SNI/vhost), masscan receives the resolved IP
- Firewall state table safety: internal discovery caps masscan at
INTERNAL_DISCOVERY_MAX_RATE = 1000 pps; at that rate with a 60 s half-open timeout, concurrent state entries peak at ~60 K regardless of target range size; for ranges aboveINTERNAL_DISCOVERY_STATE_CEILING = 262_144hosts the port list is trimmed from 10 to 5 to keep total packet volume bounded