---
name: nmap_scan
kind: function
lang: py
domain: cybersecurity
version: "1.1.0"
purity: impure
signature: "def nmap_scan(target: str, profile: str = 'quick', ports: str | None = None, extra_args: list[str] | None = None, out_dir: str | None = None, timeout_s: int = 1800, confirm: bool = False, allowlist: list[str] | None = None) -> dict"
description: "Wrapper de `nmap` por perfiles para reconocimiento de red. Ejecuta nmap como subprocess forzando salida XML (-oX), la parsea con ElementTree y devuelve puertos abiertos y hosts vivos de forma estructurada. Funcion estrella de recon: corre en primer plano (quick, top1000, service) y segundo plano para scans largos (full-tcp, vuln, udp-top). NO lanza: devuelve dict status ok/error. Sin sudo por defecto (connect-scan TCP)."
tags: [recon, nmap, portscan, cybersecurity]
params:
  - name: target
    desc: "Host, IP o rango CIDR a escanear (ej. 'scanme.nmap.org', '192.168.1.10', o '192.168.1.0/24' con el perfil discovery). Vacio devuelve status error."
  - name: profile
    desc: "Clave de PROFILES que determina los flags de nmap. quick=(-T4 -F) top 100 puertos rapido; top1000=(-T4) los 1000 puertos default; full-tcp=(-p- -T4) los 65535 TCP, LARGO; service=(-sV -sC -T4) deteccion de version + scripts default; udp-top=(-sU --top-ports 100 -T4) UDP top 100, LARGO y suele requerir sudo; vuln=(-sV --script vuln -T4) scripts de vulnerabilidades, LARGO; discovery=(-sn) ping sweep / host discovery de una subred; aggressive=(-A -T4) OS+version+script+traceroute (el -O interno puede pedir sudo); os=(-O) OS detection, REQUIERE sudo/root. Perfil invalido devuelve status error listando los validos."
  - name: ports
    desc: "Especificacion de puertos para -p (ej. '22,80,443' o '1-1000'). Si se pasa, anade '-p <ports>' al comando. None deja los puertos que defina el perfil."
  - name: extra_args
    desc: "Lista de flags adicionales de nmap a anadir tal cual al comando (ej. ['--open', '-Pn']). None no anade nada."
  - name: out_dir
    desc: "Directorio donde guardar el XML. Si se pasa, se crea y el XML se guarda como nmap-<profile>-<target>-<timestamp>.xml (util para scans largos en background y conservar el resultado). None usa un archivo temporal."
  - name: timeout_s
    desc: "Segundos maximos de ejecucion del subprocess. Default 1800 (30 min). Para scans largos (full-tcp, vuln, udp-top) subir este valor; superarlo devuelve status error con mensaje claro."
  - name: confirm
    desc: "Confirmacion explicita para escanear un target publico o desconocido. Default False: si el target no es claramente privado/local (10.x, 192.168.x, 127.x, localhost, *.local/.lan/.internal/.home/.corp) y no esta en allowlist, el escaneo se rechaza con status error y needs_confirm=True (proteccion anti-escaneo no autorizado). Pasar True solo con autorizacion. No hace DNS lookup (sin red)."
  - name: allowlist
    desc: "Lista de targets autorizados. Un target pasa el guard sin confirm si coincide exactamente con una entrada o termina en ella (ej. ['scanme.nmap.org'] o ['example.com']). None o lista vacia no autoriza nada."
output: "dict. ok: {status:'ok', target, profile, command (cmd ejecutado), open_ports:[{port:int,proto,state,service,product,version}] (solo open/open|filtered), hosts_up:[ips] (host discovery), host_status, xml_path (siempre presente), raw (stdout de nmap, siempre presente), elapsed_s:float, started (ISO)}. error: {status:'error', error:str}. Si el guard rechaza el target (publico/desconocido sin confirm ni allowlist) el error tambien incluye needs_confirm:True. Nunca lanza excepciones."
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_py_core"
imports: []
tested: true
tests:
  - test_parse_xml_extrae_puertos_abiertos_y_hosts_up
  - test_guard_publico_sin_confirm_rechaza_y_no_ejecuta
  - test_guard_privado_procede_y_parsea
  - test_guard_confirm_true_sobre_publico_procede
  - test_guard_allowlist_procede
  - test_perfil_invalido_devuelve_error
  - test_target_vacio_devuelve_error
  - test_target_is_private_clasifica
test_file_path: "python/functions/cybersecurity/nmap_scan_test.py"
file_path: "python/functions/cybersecurity/nmap_scan.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from cybersecurity import nmap_scan

# 1) Scan rapido en primer plano contra el host oficial de pruebas de nmap
#    (scanme.nmap.org es legal escanear).
res = nmap_scan("scanme.nmap.org", profile="quick", timeout_s=120)
if res["status"] == "ok":
    for p in res["open_ports"]:
        print(p["port"], p["proto"], p["service"], p["product"], p["version"])
else:
    print("error:", res["error"])

# 2) Scan LARGO de los 65535 puertos TCP guardando el XML en out_dir.
#    Lanzar en segundo plano (background) por la duracion; el XML queda en disco.
res = nmap_scan(
    "scanme.nmap.org",
    profile="full-tcp",
    out_dir="/tmp/nmap-runs",
    timeout_s=7200,  # 2h: full-tcp puede tardar minutos a horas
    allowlist=["scanme.nmap.org"],  # autorizado -> pasa el guard sin confirm
)
print(res["status"], res.get("xml_path"))

# 3) Guard de seguridad: un target publico SIN confirm ni allowlist se rechaza.
res = nmap_scan("8.8.8.8")          # publico, sin confirm
print(res["status"], res.get("needs_confirm"))  # "error" True

# Para escanear un publico autorizado: confirm=True (o anadirlo a allowlist).
res = nmap_scan("8.8.8.8", confirm=True)
# Un target privado/local NO requiere confirm:
res = nmap_scan("192.168.1.10")     # procede directamente
```

## Cuando usarla

Usala para el reconocimiento de puertos y servicios de un host: mapear la
superficie de ataque antes de un pentest autorizado, descubrir que servicios
y versiones expone una IP, o barrer una subred con `profile="discovery"` para
ver que hosts estan vivos. Es la funcion estrella de recon del registry.

Para scans largos (`full-tcp`, `vuln`, `udp-top`) lanza la llamada en SEGUNDO
PLANO: tardan de minutos a horas. Pasa `out_dir` para conservar el XML en disco
y sube `timeout_s` (p.ej. 7200) para que no aborte por timeout.

## Gotchas

- GUARD anti-escaneo no autorizado: por defecto (`confirm=False`) la funcion
  RECHAZA con status error + `needs_confirm=True` cualquier target que no sea
  claramente privado/local (rangos privados, loopback, link-local, `localhost`,
  `*.local/.lan/.internal/.home/.corp`). Para escanear un target publico o un
  hostname desconocido tienes que pasar `confirm=True` o incluirlo en
  `allowlist` (match exacto o por sufijo). El guard NO hace DNS lookup (sin red,
  KISS): un hostname publico se considera "indecidible" y cae al lado seguro
  (requiere confirm). Esto NO sustituye tu responsabilidad legal — solo evita
  disparos accidentales contra infra ajena.
- LEGAL: solo escanea hosts que sean tuyos o para los que tengas autorizacion
  explicita. `scanme.nmap.org` es el host oficial de pruebas de nmap, legal
  escanear; cualquier otro objetivo de terceros sin permiso puede ser delito.
- Privilegios: los perfiles `os` (-O), `udp-top` (-sU) y parte de `aggressive`
  (-O interno) requieren sudo/root. Sin privilegios nmap cae a connect-scan TCP
  (-sT) y esos modos fallan o quedan incompletos — esta funcion no usa sudo.
- Duracion: `full-tcp` (65535 puertos), `vuln` (scripts NSE) y `udp-top` (UDP
  es lento) tardan minutos a horas. Sube `timeout_s` y/o lanza en background con
  `out_dir`; superar `timeout_s` devuelve status error.
- Deteccion: firewalls / IDS / WAF pueden detectar y bloquear el escaneo (sobre
  todo `aggressive`, `vuln` y `-T4`). El resultado puede venir filtrado o
  incompleto si el objetivo defiende activamente.
- `discovery` (-sn) espera notacion de host o subred en CIDR (ej.
  "192.168.1.0/24"); puebla `hosts_up`, no `open_ports`.
- No lanza excepciones: siempre revisa `res["status"]` antes de leer
  `open_ports`/`hosts_up`. `raw` y `xml_path` solo estan garantizados en ok.

## Capability growth log

- v1.1.0 (2026-06-14) — guard `confirm`/`allowlist` anti-escaneo-no-autorizado:
  targets publicos/desconocidos se rechazan (status error + needs_confirm) salvo
  confirm=True o estar en allowlist; privados/local proceden sin confirm. Sin DNS
  lookup. Anadidos tests (8 casos: parseo XML, guard publico/privado/confirm/
  allowlist, perfil invalido, target vacio, clasificacion _target_is_private).
