How LazyOwn Auto-Executes Tools Based on Nmap Scan Results
LazyOwn automatically parses Nmap CSV output, maps discovered services to predefined command templates, and injects these into LLM prompts to drive the next penetration testing phase without manual intervention.
LazyOwn, an open-source penetration testing framework maintained in the grisuno/lazyown repository, streamlines reconnaissance by treating Nmap scans as triggers for automated tool execution. After the initial port discovery phase completes, the framework parses scan results, synthesizes ready-to-run commands, and guides its AI assistant toward service-specific exploitation tools rather than redundant scanning.
Parsing Nmap Output into Actionable Data
The auto-execution workflow begins with structured data extraction from Nmap's CSV output. When lazynmap.sh (located at modules/lazynmap.sh) completes a scan, it stores results in sessions/scan_<target>.nmap.csv for downstream processing.
The parse_nmap_csv Function
In main/utils.py, the parse_nmap_csv function reads this CSV file and transforms it into a nested dictionary that groups open ports by service name:
# main/utils.py
def parse_nmap_csv(csv_path):
"""
Reads an Nmap CSV file and returns a dict:
{
"http": [{"ip": "10.0.0.5", "port": "80"}],
"ssh": [{"ip": "10.0.0.5", "port": "22"}],
...
}
"""
# implementation uses the `csv` module and groups rows by the
# service column.
This dictionary becomes the foundation for all subsequent auto-execution decisions, enabling the framework to identify exactly which services require further enumeration.
Mapping Services to Command Templates
Once the services are identified, LazyOwn generates executable command strings through template mapping rather than requiring manual input.
Generating Synthetic YAML Prompts
The create_synthetic_yaml function in main/utils.py maintains a commands dictionary that associates service names with specific enumeration tools. For each discovered service, it either applies a specialized template or falls back to a generic nmap -sV probe:
# main/utils.py
def create_synthetic_yaml(nmap_services):
commands = {
"mysql": "nmap -sV --script=mysql-enum {ip} -p {port}",
"smb": "nmap -p {port} --script=smb-enum {ip}",
# fallback is generic nmap -sV …
}
yaml_lines = []
for service_name, instances in nmap_services.items():
for inst in instances:
cmd = commands.get(service_name,
"nmap -sV -p {port} {ip}").format(**inst)
yaml_lines.append(f"- {service_name}: {cmd}")
return "\n".join(yaml_lines)
When the function encounters MySQL on port 3306, it generates nmap -sV --script=mysql-enum 10.10.10.10 -p 3306. If no specific template exists for a service, the fallback ensures the AI still receives a valid follow-up command rather than stalling the workflow.
Driving the Auto-Execution Pipeline
With structured data and command templates ready, LazyOwn injects this context into its AI-driven workflow engine to trigger the actual tool execution.
Injecting Context into LLM Prompts
In main/lazyown.py, the _create_strict_yaml_prompt method assembles the final prompt by combining the base instructions, detected services, and the synthetic YAML commands:
# main/lazyown.py
def _create_strict_yaml_prompt(self, base_prompt, nmap_services, knowledge_base):
nmap_context = "Services detected during reconnaissance:\n"
for service, instances in nmap_services.items():
for i in instances:
nmap_context += f"- {service} on {i['ip']}:{i['port']}\n"
synthetic_yaml = create_synthetic_yaml(nmap_services)
return f"""{base_prompt}
{nmap_context}
---
{synthetic_yaml}
"""
This prompt explicitly tells the AI assistant that reconnaissance is complete and specific enumeration commands are already available, nudging the model to suggest executing these rather than initiating new scans.
Preventing Redundant Scans with Usage Guards
To enforce workflow progression, modules/agent_runner.py implements a usage tracking mechanism that prevents infinite Nmap loops. The run_tool method maintains a self.tool_usage_count dictionary and applies strict limits after the first scan:
# modules/agent_runner.py
def run_tool(self, tool_name, *args, **kwargs):
# increase usage counter
self.tool_usage_count[tool_name] = self.tool_usage_count.get(tool_name, 0) + 1
# after an Nmap run, warn and restrict further Nmap calls
if tool_name == "cmd_nmap" and self.tool_usage_count["cmd_nmap"] > 0:
self._send_system_message(
"SISTEMA: Ya has escaneado. NO uses nmap de nuevo. "
"Analiza los puertos abiertos y usa otra herramienta específica "
"(ej: curl, gobuster, smbclient) o da tu reporte final."
)
# limit suggestions to one tool for the next step
limit = 1
Once cmd_nmap appears in the usage count, the system emits a system message explicitly instructing the AI that scanning is complete and specific tools like curl, gobuster, or smbclient should be used instead. This guard ensures the auto-execution flow moves decisively from reconnaissance to exploitation.
Summary
- LazyOwn treats Nmap scans as the initiation trigger for a three-phase auto-execution pipeline.
parse_nmap_csvinmain/utils.pyconverts CSV scan results into a service-grouped dictionary.create_synthetic_yamlmaps services to specific command templates (MySQL, SMB, etc.) or falls back to generic probes._create_strict_yaml_promptinjects the generated commands into LLM prompts, informing the AI that reconnaissance is complete.modules/agent_runner.pyprevents workflow stagnation by trackingcmd_nmapusage and restricting repeated scanning.- The system automatically transitions from port discovery to service enumeration without requiring manual tool selection.
Frequently Asked Questions
How does parse_nmap_csv structure the scan data?
The function reads the Nmap CSV file and returns a dictionary where keys are service names (e.g., "http", "ssh") and values are lists of dictionaries containing ip and port strings. This structure allows create_synthetic_yaml to iterate over each service instance and generate targeted commands.
What prevents the AI from suggesting Nmap repeatedly?
The agent_runner.py module tracks how many times cmd_nmap has been executed using self.tool_usage_count. After the first invocation, it sends a system message explicitly forbidding further Nmap usage and limits the AI to suggesting one specific follow-up tool, forcing progression to the exploitation phase.
Where are the command templates defined for each service?
Command templates are defined in the commands dictionary within main/utils.py's create_synthetic_yaml function. Current mappings include MySQL enumeration scripts and SMB enumeration, with a generic nmap -sV -p {port} {ip} fallback for unrecognized services.
Can I customize the auto-execution commands?
Yes. Developers can modify the commands dictionary in main/utils.py to add new service-to-tool mappings or change existing templates. Since the system uses Python's str.format() method with ip and port placeholders, any command string using these variables will work with the auto-execution pipeline.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →