# How LazyOwn Auto-Executes Tools Based on Nmap Scan Results

> Discover how LazyOwn auto-executes tools using Nmap scan results. Automatically parse Nmap data map services to command templates and leverage LLMs for penetration testing.

- Repository: [Grisuno/lazyown](https://github.com/grisuno/lazyown)
- Tags: how-to-guide
- Published: 2026-03-02

---

**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`](https://github.com/grisuno/lazyown/blob/main/lazynmap.sh) (located at [`modules/lazynmap.sh`](https://github.com/grisuno/lazyown/blob/main/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`](https://github.com/grisuno/lazyown/blob/main/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:

```python

# 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`](https://github.com/grisuno/lazyown/blob/main/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:

```python

# 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`](https://github.com/grisuno/lazyown/blob/main/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:

```python

# 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`](https://github.com/grisuno/lazyown/blob/main/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:

```python

# 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_csv`** in [`main/utils.py`](https://github.com/grisuno/lazyown/blob/main/main/utils.py) converts CSV scan results into a service-grouped dictionary.
- **`create_synthetic_yaml`** maps services to specific command templates (MySQL, SMB, etc.) or falls back to generic probes.
- **`_create_strict_yaml_prompt`** injects the generated commands into LLM prompts, informing the AI that reconnaissance is complete.
- **[`modules/agent_runner.py`](https://github.com/grisuno/lazyown/blob/main/modules/agent_runner.py)** prevents workflow stagnation by tracking `cmd_nmap` usage 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`](https://github.com/grisuno/lazyown/blob/main/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`](https://github.com/grisuno/lazyown/blob/main/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`](https://github.com/grisuno/lazyown/blob/main/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.