Advanced async port scanner with real-time UI, security audit, CVE lookup and enterprise-grade frontend architecture.
Escáner de puertos asíncrono con UI en tiempo real, auditoría de seguridad, búsqueda de CVEs y arquitectura frontend de nivel enterprise.
LukitaPort is a full-stack network reconnaissance tool built for educational and authorised security research. It streams port-scan results to the browser in real time over SSE, then automatically chains HTTP header auditing, technology fingerprinting, sensitive-path discovery, SSL/TLS analysis and NVD CVE lookup — all in a single, zero-dependency Vanilla JS frontend.
The backend is a Python / FastAPI async server. The frontend is a CSP-compliant ES-module application with rAF-batched rendering capable of smoothly handling 65,535-port full scans at 60 FPS.
⚠️ For educational use only. Only scan hosts you own or have explicit written permission to test.
- Async TCP port scanner —
asyncioconnect probes, configurable timeout and concurrency - Scan modes — Quick (top 100), Custom (user-defined range), Full (1–65535)
- Scan profiles — Normal, Stealth (random delays, slower rate), Aggressive (higher concurrency)
- Anonymous mode — forces Stealth profile and applies random per-port jitter
- Real-time SSE stream — server pushes
meta,port,doneevents; browser renders rows as they arrive - GeoIP enrichment — country, city, ASN, ISP, flag emoji from the resolved IP
- nmap fingerprinting — version detection (
-sV) on open ports via subprocess bridge
- HTTP headers audit — scores A+ → F, grades missing/present/dangerous headers
- Technology detection — ~60 signatures (servers, frameworks, CDNs, analytics) from JSON config
- Sensitive path scan — probes admin panels, backups, config files,
.git,.envetc. - SSL/TLS analysis — certificate chain, expiry, SANs, cipher, protocol versions, self-signed detection
- CVE lookup — NVD (nvd.nist.gov) batch queries per detected service/version with CVSS scoring
- ICMP/ping sweep — live host discovery across a CIDR range (e.g.
192.168.1.0/24) - Subdomain enumeration — crt.sh certificate transparency log lookup with DNS resolution
- Screenshot capture — Playwright Chromium headless, shared browser instance (one process per server lifetime)
- JSON — full structured report with geo, versions and risk ratings
- CSV — flat table, quoted fields, comma-safe banners
- HTML — self-contained single-file report with embedded styles
- Markdown — server-rendered pentest-ready report
- PDF — server-rendered report via WeasyPrint
- ES Modules —
state.js,ui.js,api.js,templates.js,export.js,main.js - rAF + DocumentFragment batching — 60 FPS stable at 1,000 SSE events/second
- Smart auto-scroll — only follows new rows if the user was already near the bottom
- Hermetic EventSource teardown — null handlers before
.close()on every scan stop (no memory leaks across repeated scans) - CSP
script-src 'self'compliant — zeroonclick/onmouseoverattributes, event delegation viadata-actionmap - Non-blocking exports — chunked
yieldToMain()processing, 5,000 rows/chunk, spinner never freezes - Anti-XSS —
escapeHTML()+safeHref()on every backend string beforeinnerHTMLinjection - i18n — full ES / EN bilingual UI, switchable at runtime
┌─────────────────────────────────────────────────────────────┐
│ Browser (Vanilla JS ES Modules) │
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌────────────┐ │
│ │ state.js │ │ ui.js │ │templates.js│ │ export.js │ │
│ │ (store) │ │ (render) │ │ (HTML tmpl)│ │ (chunked) │ │
│ └────┬─────┘ └────┬─────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴──────────────┴───────────────┴──────┐ │
│ │ main.js (event delegation) │ │
│ │ api.js (SSE + fetch) │ │
│ └─────────────────────────┬───────────────────────────────┘ │
└────────────────────────────│────────────────────────────────┘
│ HTTP / SSE
┌────────────────────────────┴────────────────────────────────┐
│ FastAPI (Python 3.11+) │
│ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ scanner.py │ │ auditor.py │ │ ssl_analyzer │ │
│ │(async TCP) │ │(headers+ │ │ .py │ │
│ │ │ │ paths+tech)│ └──────────────┘ │
│ └────────────┘ └────────────┘ ┌──────────────┐ │
│ ┌────────────┐ ┌────────────┐ │ cve_lookup │ │
│ │ resolver.py│ │scan_service│ │ .py (NVD) │ │
│ │(DNS+SSRF) │ │.py (shots) │ └──────────────┘ │
│ └────────────┘ └────────────┘ │
│ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ cache.py │ │ models.py │ │pdf_generator │ │
│ │(TTL-LRU) │ │ (Pydantic) │ │ .py │ │
│ └────────────┘ └────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
| Dependency | Version | Purpose |
|---|---|---|
| Python | 3.11+ | Runtime |
| FastAPI | 0.111+ | Web framework |
| Uvicorn | 0.29+ | ASGI server |
| aiohttp | 3.9+ | Async HTTP (audit, CVE) |
| dnspython | 2.6+ | DNS resolution |
| geoip2 | 4.x | GeoIP database |
| WeasyPrint | 62+ | PDF generation |
| Playwright | 1.44+ | Screenshots (optional) |
| nmap | system | Fingerprinting (optional) |
No build step. No bundler. No npm. ES Modules loaded natively by the browser.
git clone https://github.com/jaimefg1888/lukitaport.git
cd lukitaportpython -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtPlaywright (screenshots):
pip install playwright
playwright install chromiumnmap (fingerprinting):
# Debian / Ubuntu
sudo apt install nmap
# macOS
brew install nmap
# Windows
winget install Insecure.NmapGeoIP database (geo enrichment):
# Download GeoLite2-City.mmdb from maxmind.com and place it in:
./data/GeoLite2-City.mmdbcp .env.example .env
# Edit .env — see Configuration section belowAll configuration is through environment variables. Copy .env.example to .env.
# ── Security ──────────────────────────────────────────────────────────────────
# Allow scanning private/internal IP addresses.
# Set to "true" ONLY for local lab environments.
# Default: false (production-safe — blocks 10.x, 192.168.x, 127.x, 169.254.x, etc.)
ALLOW_PRIVATE_IPS=false
# ── Server ────────────────────────────────────────────────────────────────────
HOST=0.0.0.0
PORT=8000
# ── Logging ───────────────────────────────────────────────────────────────────
# DEBUG | INFO | WARNING | ERROR
LOG_LEVEL=INFO
# ── NVD API (optional — increases CVE rate limits) ────────────────────────────
# Get a free key at https://nvd.nist.gov/developers/request-an-api-key
NVD_API_KEY=
# ── GeoIP database path ───────────────────────────────────────────────────────
GEOIP_DB_PATH=./data/GeoLite2-City.mmdb| Value | Behaviour | Use case |
|---|---|---|
false (default) |
Blocks all RFC-1918, loopback, link-local, APIPA and reserved addresses. Returns HTTP 403. | Production, public-facing servers |
true |
Allows scanning any IP including internal ones | Local lab, educational use |
The SSRF check runs after DNS resolution to defend against DNS-rebinding attacks (a hostname that resolves to an internal IP is still blocked).
uvicorn main:app --host 0.0.0.0 --port 8000 --reloaduvicorn main:app --host 0.0.0.0 --port 8000 --workers 1
# Note: use workers=1 — the scan engine holds per-process state.
# For horizontal scaling, put a reverse proxy in front.Open http://localhost:8000 in your browser.
All endpoints return JSON unless noted otherwise.
Returns the port risk map used by the frontend.
Resolves a hostname or IP. Returns { ip, hostname, input } or { error }.
Returns 403 if the resolved IP is internal and ALLOW_PRIVATE_IPS=false.
GeoIP lookup. Returns { country, city, asn, isp, flag }.
Streams port scan results as Server-Sent Events.
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
target |
string | required | Host to scan |
mode |
quick|custom|full |
quick |
Port range preset |
profile |
normal|stealth|aggressive |
normal |
Scan behaviour |
port_start |
int | 1 | Start of custom range |
port_end |
int | 1024 | End of custom range |
timeout |
float | 1.0 | Per-port timeout in seconds |
anon |
0|1 |
0 |
Force stealth + random delays |
Event types:
data: {"type": "meta", "ip": "1.2.3.4", "hostname": "example.com", "total_ports": 100, "geo": {...}}
data: {"type": "port", "port": 80, "state": "open", "service": "http", "response_time_ms": 42, "scanned": 1, "progress": 1.0}
data: {"type": "done"}
data: {"type": "port", "error": "ssrf_blocked", "status": 403}
nmap -sV version detection on a list of open ports. Returns per-port { product, version, extrainfo, cpe }.
Full security audit. Returns { headers, technologies, paths }.
SSL/TLS certificate analysis per HTTPS port.
NVD CVE batch lookup.
Body: { "80": { "name": "nginx", "version": "1.24.0" }, ... }
ICMP ping sweep of a CIDR range. Returns { alive: [{ip, rtt_ms}], alive_count, total_hosts }.
crt.sh + DNS resolution subdomain enumeration.
Trigger a Playwright screenshot in the background.
Retrieve a previously captured screenshot as image/png.
Server-side PDF report generation via WeasyPrint.
Server-side Markdown report generation.
Health / readiness check. Returns Playwright status, cache stats, allow_private_ips value.
| Module | Responsibility |
|---|---|
state.js |
Single source of truth — state object, getRisk(), initConfig() |
ui.js |
DOM helpers, toast, rendering (rAF batching, smart scroll), audit render, copyText |
templates.js |
Pure template functions, escapeHTML, safeHref, zero inline handlers |
api.js |
SSE scan lifecycle, hermetic EventSource teardown, all fetch calls |
export.js |
Chunked async exports (JSON, CSV, HTML) + server-side PDF / Markdown |
main.js |
App entry point, event delegation (data-action map), static listeners |
Instead of onclick attributes, all dynamically-rendered interactive elements use data-action:
<!-- Copy button (no onclick) -->
<button data-action="copy" data-copy-text="nginx config here">Copy nginx</button>
<!-- Scan a discovered host (no onclick) -->
<div data-action="scan-host" data-host="192.168.1.1">192.168.1.1</div>
<!-- Launch CVE lookup (no onclick, no window.* global) -->
<button data-action="launch-cve">Search CVEs</button>A single listener on document.body dispatches to _ACTION_HANDLERS:
document.body.addEventListener('click', e => {
const el = e.target.closest('[data-action]');
if (el) _ACTION_HANDLERS[el.dataset.action]?.(el, e);
});Checks resolved IP against six ipaddress properties before any connection is made:
| Property | Covers |
|---|---|
is_loopback |
127.0.0.0/8, ::1 |
is_private |
10/8, 172.16/12, 192.168/16, fc00::/7 |
is_link_local |
169.254.0.0/16 (incl. AWS metadata 169.254.169.254), fe80::/10 |
is_reserved |
0.0.0.0/8, 240.0.0.0/4 |
is_multicast |
224/4, ff00::/8 |
is_unspecified |
0.0.0.0, :: |
The check runs post-DNS so a hostname that DNS-rebinds to 10.0.0.1 is still blocked.
The frontend is fully compatible with:
Content-Security-Policy: default-src 'self'; script-src 'self'
- Zero
onclick,onmouseover,onmouseoutattributes in any generated HTML - Zero
window.*global function assignments - Hover states for dynamic components delivered via CSS class (
.btn-cve-launch:hover) - Screenshot blob URLs created with
URL.createObjectURL(), appended via DOM API
Every backend string entering innerHTML passes through escapeHTML() (replaces & < > " ').
External URLs pass through safeHref() (rejects javascript:, data:, vbscript:).
EventSource teardown on every scan stop:
es.onmessage = null; // 1. Null handlers first (breaks GC root)
es.onerror = null;
es.onopen = null;
es.close(); // 2. Close TCP stream
state.eventSource = null; // 3. Release JS reference| Format | Engine | Blocking? | Notes |
|---|---|---|---|
| JSON | Client-side | No (chunked) | Full report with geo, risk, versions |
| CSV | Client-side | No (chunked) | Quoted fields, comma-safe banners |
| HTML | Client-side | No (chunked + prebuilt rows) | Self-contained, no external deps |
| Markdown | Server-side | No (async fetch) | Pentest-report structure |
| Server-side | No (async fetch) | WeasyPrint, styled report |
All client-side exports use yieldToMain() (setTimeout 0 ms) every 5,000 rows to keep the UI responsive during large exports.
# docker-compose.yml
version: "3.9"
services:
lukitaport:
build: .
ports:
- "8000:8000"
environment:
- ALLOW_PRIVATE_IPS=false
- LOG_LEVEL=INFO
- NVD_API_KEY=${NVD_API_KEY}
volumes:
- ./data:/app/data # GeoIP database
restart: unless-stopped# Dockerfile
FROM python:3.12-slim
RUN apt-get update && apt-get install -y nmap && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN playwright install chromium --with-deps
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]docker compose up --buildlukitaport/
│
├── main.py # FastAPI app, all endpoints, lifespan
├── scanner.py # Async TCP port scanner
├── auditor.py # HTTP headers + tech detection + path scan
├── ssl_analyzer.py # SSL/TLS certificate analysis
├── cve_lookup.py # NVD API client with exponential backoff
├── scan_service.py # Screenshot capture, GeoIP, nmap bridge
├── resolver.py # DNS resolution + SSRF protection
├── cache.py # TTL-LRU screenshot cache
├── models.py # Pydantic v2 request/response models
├── config.py # PORT_RISK map
├── logging_config.py # Structured JSON logging
├── pdf_generator.py # WeasyPrint PDF renderer
├── tech_signatures.json # Technology detection signatures
│
├── static/ # Frontend (served by FastAPI)
│ ├── index.html
│ ├── styles.css
│ ├── state.js
│ ├── ui.js
│ ├── api.js
│ ├── templates.js
│ ├── export.js
│ └── main.js
│
├── data/
│ └── GeoLite2-City.mmdb # MaxMind GeoIP database (not included)
│
├── .env.example
├── requirements.txt
├── Dockerfile
└── docker-compose.yml
LukitaPort es una herramienta de reconocimiento de red full-stack diseñada para uso educativo e investigación de seguridad autorizada. Envía resultados de escaneo de puertos al navegador en tiempo real mediante SSE y encadena automáticamente auditoría de cabeceras HTTP, fingerprinting de tecnologías, descubrimiento de rutas sensibles, análisis SSL/TLS y búsqueda de CVEs en NVD — todo en un frontend Vanilla JS de cero dependencias.
El backend es un servidor Python/FastAPI asíncrono. El frontend es una aplicación de ES Modules compatible con CSP, con renderizado en lotes mediante rAF que gestiona escaneos completos de 65.535 puertos a 60 FPS de forma fluida.
⚠️ Solo para uso educativo. Escanea únicamente hosts que sean de tu propiedad o para los que tengas permiso escrito explícito.
- Escáner TCP asíncrono — sondas de conexión
asyncio, timeout y concurrencia configurables - Modos de escaneo — Rápido (top 100), Personalizado (rango definido por el usuario), Completo (1–65535)
- Perfiles — Normal, Stealth (delays aleatorios, tasa reducida), Agresivo (mayor concurrencia)
- Modo anónimo — fuerza el perfil Stealth y aplica jitter aleatorio por puerto
- Stream SSE en tiempo real — el servidor envía eventos
meta,port,done; el navegador renderiza filas al recibirlos - Enriquecimiento GeoIP — país, ciudad, ASN, ISP, emoji de bandera desde la IP resuelta
- Fingerprinting con nmap — detección de versiones (
-sV) en puertos abiertos mediante subproceso
- Auditoría de cabeceras HTTP — puntuación de A+ a F, clasifica cabeceras ausentes/presentes/peligrosas
- Detección de tecnologías — ~60 firmas (servidores, frameworks, CDNs, analíticas) desde config JSON
- Escaneo de rutas sensibles — sondea paneles admin, backups, ficheros de configuración,
.git,.env, etc. - Análisis SSL/TLS — cadena de certificados, caducidad, SANs, cifrado, versiones de protocolo, detección de autofirmados
- Búsqueda de CVEs — consultas por lotes a NVD (nvd.nist.gov) por servicio/versión detectados con puntuación CVSS
- Barrido ICMP/ping — descubrimiento de hosts activos en un rango CIDR (p.ej.
192.168.1.0/24) - Enumeración de subdominios — consulta a logs de transparencia de certificados crt.sh con resolución DNS
- Captura de capturas de pantalla — Playwright Chromium headless, instancia de browser compartida (un proceso por vida del servidor)
- JSON — informe estructurado completo con geo, versiones y clasificaciones de riesgo
- CSV — tabla plana, campos entrecomillados, banners seguros ante comas
- HTML — informe autocontenido en un único fichero con estilos embebidos
- Markdown — informe renderizado en servidor listo para pentesting
- PDF — informe renderizado en servidor via WeasyPrint
- ES Modules —
state.js,ui.js,api.js,templates.js,export.js,main.js - Batching rAF + DocumentFragment — 60 FPS estables a 1.000 eventos SSE por segundo
- Auto-scroll inteligente — solo sigue las nuevas filas si el usuario ya estaba cerca del fondo
- Teardown hermético del EventSource — null de handlers antes de
.close()en cada parada de escaneo (sin fugas de memoria entre escaneos sucesivos) - Cumplimiento CSP
script-src 'self'— cero atributosonclick/onmouseover, delegación de eventos via mapadata-action - Exportaciones no bloqueantes — procesamiento por chunks con
yieldToMain(), 5.000 filas por chunk, el spinner nunca se congela - Anti-XSS —
escapeHTML()+safeHref()en cada string del backend antes de inyectarlo eninnerHTML - i18n — UI bilingüe ES/EN completa, switchable en tiempo de ejecución
┌─────────────────────────────────────────────────────────────┐
│ Navegador (Vanilla JS ES Modules) │
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌────────────┐ │
│ │ state.js │ │ ui.js │ │templates.js│ │ export.js │ │
│ │ (estado) │ │(renderiz)│ │(plantillas)│ │ (por chunks│ │
│ └────┬─────┘ └────┬─────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴──────────────┴───────────────┴──────┐ │
│ │ main.js (delegación de eventos) │ │
│ │ api.js (SSE + fetch) │ │
│ └─────────────────────────┬───────────────────────────────┘ │
└────────────────────────────│────────────────────────────────┘
│ HTTP / SSE
┌────────────────────────────┴────────────────────────────────┐
│ FastAPI (Python 3.11+) │
│ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ scanner.py │ │ auditor.py │ │ ssl_analyzer │ │
│ │ (TCP asíc.)│ │(cabeceras+ │ │ .py │ │
│ │ │ │rutas+tech) │ └──────────────┘ │
│ └────────────┘ └────────────┘ ┌──────────────┐ │
│ ┌────────────┐ ┌────────────┐ │ cve_lookup │ │
│ │ resolver.py│ │scan_service│ │ .py (NVD) │ │
│ │(DNS+SSRF) │ │.py (shots) │ └──────────────┘ │
│ └────────────┘ └────────────┘ │
│ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ cache.py │ │ models.py │ │pdf_generator │ │
│ │(TTL-LRU) │ │ (Pydantic) │ │ .py │ │
│ └────────────┘ └────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
| Dependencia | Versión | Uso |
|---|---|---|
| Python | 3.11+ | Runtime |
| FastAPI | 0.111+ | Framework web |
| Uvicorn | 0.29+ | Servidor ASGI |
| aiohttp | 3.9+ | HTTP asíncrono (auditoría, CVE) |
| dnspython | 2.6+ | Resolución DNS |
| geoip2 | 4.x | Base de datos GeoIP |
| WeasyPrint | 62+ | Generación de PDF |
| Playwright | 1.44+ | Capturas de pantalla (opcional) |
| nmap | sistema | Fingerprinting (opcional) |
Sin build. Sin bundler. Sin npm. ES Modules cargados nativamente por el navegador.
git clone https://github.com/jaimefg1888/lukitaport.git
cd lukitaportpython -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtPlaywright (capturas de pantalla):
pip install playwright
playwright install chromiumnmap (fingerprinting):
# Debian / Ubuntu
sudo apt install nmap
# macOS
brew install nmap
# Windows
winget install Insecure.NmapBase de datos GeoIP (enriquecimiento geográfico):
# Descarga GeoLite2-City.mmdb desde maxmind.com y colócala en:
./data/GeoLite2-City.mmdbcp .env.example .env
# Edita .env — consulta la sección Configuración a continuaciónToda la configuración se realiza mediante variables de entorno. Copia .env.example a .env.
# ── Seguridad ─────────────────────────────────────────────────────────────────
# Permite escanear IPs privadas/internas.
# Poner a "true" SOLO en entornos de laboratorio local.
# Por defecto: false (seguro para producción — bloquea 10.x, 192.168.x, 127.x, etc.)
ALLOW_PRIVATE_IPS=false
# ── Servidor ──────────────────────────────────────────────────────────────────
HOST=0.0.0.0
PORT=8000
# ── Logs ──────────────────────────────────────────────────────────────────────
# DEBUG | INFO | WARNING | ERROR
LOG_LEVEL=INFO
# ── API NVD (opcional — aumenta los límites de tasa para CVEs) ────────────────
# Obtén una clave gratuita en https://nvd.nist.gov/developers/request-an-api-key
NVD_API_KEY=
# ── Ruta de la base de datos GeoIP ───────────────────────────────────────────
GEOIP_DB_PATH=./data/GeoLite2-City.mmdb| Valor | Comportamiento | Caso de uso |
|---|---|---|
false (defecto) |
Bloquea todas las IPs RFC-1918, loopback, link-local, APIPA y reservadas. Devuelve HTTP 403. | Producción, servidores públicos |
true |
Permite escanear cualquier IP incluyendo las internas | Lab local, uso educativo |
La verificación SSRF se ejecuta después de la resolución DNS para defenderse de ataques de DNS-rebinding (un hostname que resuelve a una IP interna sigue siendo bloqueado).
uvicorn main:app --host 0.0.0.0 --port 8000 --reloaduvicorn main:app --host 0.0.0.0 --port 8000 --workers 1
# Nota: usa workers=1 — el motor de escaneo mantiene estado por proceso.
# Para escalado horizontal, pon un proxy inverso delante.Abre http://localhost:8000 en tu navegador.
Todos los endpoints devuelven JSON salvo indicación contraria.
Devuelve el mapa de riesgo de puertos usado por el frontend.
Resuelve un hostname o IP. Devuelve { ip, hostname, input } o { error }.
Devuelve 403 si la IP resuelta es interna y ALLOW_PRIVATE_IPS=false.
Consulta GeoIP. Devuelve { country, city, asn, isp, flag }.
Envía los resultados del escaneo como Server-Sent Events.
Parámetros query:
| Parámetro | Tipo | Defecto | Descripción |
|---|---|---|---|
target |
string | obligatorio | Host a escanear |
mode |
quick|custom|full |
quick |
Preset de rango de puertos |
profile |
normal|stealth|aggressive |
normal |
Comportamiento del escaneo |
port_start |
int | 1 | Inicio del rango personalizado |
port_end |
int | 1024 | Fin del rango personalizado |
timeout |
float | 1.0 | Timeout por puerto en segundos |
anon |
0|1 |
0 |
Forzar stealth + delays aleatorios |
Tipos de evento:
data: {"type": "meta", "ip": "1.2.3.4", "hostname": "ejemplo.com", "total_ports": 100, "geo": {...}}
data: {"type": "port", "port": 80, "state": "open", "service": "http", "response_time_ms": 42, ...}
data: {"type": "done"}
data: {"type": "port", "error": "ssrf_blocked", "status": 403}
Detección de versiones nmap -sV sobre una lista de puertos abiertos.
Auditoría de seguridad completa. Devuelve { headers, technologies, paths }.
Análisis de certificados SSL/TLS por puerto HTTPS.
Búsqueda por lotes de CVEs en NVD.
Cuerpo: { "80": { "name": "nginx", "version": "1.24.0" }, ... }
Barrido ping ICMP en un rango CIDR. Devuelve { alive: [{ip, rtt_ms}], ... }.
Enumeración de subdominios vía crt.sh + resolución DNS.
Lanza una captura de pantalla Playwright en segundo plano.
Recupera una captura de pantalla previa como image/png.
Generación de informe PDF en servidor via WeasyPrint.
Generación de informe Markdown en servidor.
Health check. Devuelve estado de Playwright, estadísticas de caché, valor de allow_private_ips.
| Módulo | Responsabilidad |
|---|---|
state.js |
Fuente única de verdad — objeto state, getRisk(), initConfig() |
ui.js |
Helpers DOM, toast, renderizado (batching rAF, scroll inteligente), render de auditoría, copyText |
templates.js |
Funciones de plantilla puras, escapeHTML, safeHref, cero handlers inline |
api.js |
Ciclo de vida del SSE, teardown hermético del EventSource, todas las llamadas fetch |
export.js |
Exportaciones asíncronas por chunks (JSON, CSV, HTML) + PDF/Markdown en servidor |
main.js |
Punto de entrada, delegación de eventos (mapa data-action), listeners estáticos |
En lugar de atributos onclick, todos los elementos interactivos renderizados dinámicamente usan data-action:
<!-- Botón copiar (sin onclick) -->
<button data-action="copy" data-copy-text="config nginx aquí">Copiar nginx</button>
<!-- Escanear host descubierto (sin onclick) -->
<div data-action="scan-host" data-host="192.168.1.1">192.168.1.1</div>
<!-- Lanzar búsqueda CVE (sin onclick, sin global window.*) -->
<button data-action="launch-cve">Buscar CVEs</button>Un único listener en document.body despacha a _ACTION_HANDLERS:
document.body.addEventListener('click', e => {
const el = e.target.closest('[data-action]');
if (el) _ACTION_HANDLERS[el.dataset.action]?.(el, e);
});Verifica la IP resuelta contra seis propiedades de ipaddress antes de realizar cualquier conexión:
| Propiedad | Cubre |
|---|---|
is_loopback |
127.0.0.0/8, ::1 |
is_private |
10/8, 172.16/12, 192.168/16, fc00::/7 |
is_link_local |
169.254.0.0/16 (incl. metadata AWS 169.254.169.254), fe80::/10 |
is_reserved |
0.0.0.0/8, 240.0.0.0/4 |
is_multicast |
224/4, ff00::/8 |
is_unspecified |
0.0.0.0, :: |
La verificación se ejecuta post-DNS, por lo que un hostname que DNS-rebinda a 10.0.0.1 sigue siendo bloqueado.
El frontend es totalmente compatible con:
Content-Security-Policy: default-src 'self'; script-src 'self'
- Cero atributos
onclick,onmouseover,onmouseouten ningún HTML generado - Cero asignaciones de funciones globales
window.* - Estados hover de componentes dinámicos entregados via clase CSS (
.btn-cve-launch:hover) - URLs blob de capturas de pantalla creadas con
URL.createObjectURL(), añadidas via API DOM
Cada string del backend que entra en innerHTML pasa por escapeHTML() (reemplaza & < > " ').
Las URLs externas pasan por safeHref() (rechaza javascript:, data:, vbscript:).
Teardown del EventSource en cada parada de escaneo:
es.onmessage = null; // 1. Null handlers primero (rompe la raíz del GC)
es.onerror = null;
es.onopen = null;
es.close(); // 2. Cierra el stream TCP
state.eventSource = null; // 3. Libera la referencia JS| Formato | Motor | ¿Bloqueante? | Notas |
|---|---|---|---|
| JSON | Cliente | No (por chunks) | Informe completo con geo, riesgo, versiones |
| CSV | Cliente | No (por chunks) | Campos entrecomillados, banners seguros |
| HTML | Cliente | No (chunks + filas preconstruidas) | Autocontenido, sin dependencias externas |
| Markdown | Servidor | No (fetch asíncrono) | Estructura de informe de pentest |
| Servidor | No (fetch asíncrono) | WeasyPrint, informe con estilos |
Todas las exportaciones del lado cliente usan yieldToMain() (setTimeout 0 ms) cada 5.000 filas para mantener la UI responsiva durante exportaciones grandes.
# docker-compose.yml
version: "3.9"
services:
lukitaport:
build: .
ports:
- "8000:8000"
environment:
- ALLOW_PRIVATE_IPS=false
- LOG_LEVEL=INFO
- NVD_API_KEY=${NVD_API_KEY}
volumes:
- ./data:/app/data # Base de datos GeoIP
restart: unless-stopped# Dockerfile
FROM python:3.12-slim
RUN apt-get update && apt-get install -y nmap && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN playwright install chromium --with-deps
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]docker compose up --buildlukitaport/
│
├── main.py # App FastAPI, todos los endpoints, lifespan
├── scanner.py # Escáner TCP asíncrono
├── auditor.py # Cabeceras HTTP + detección de tecnologías + rutas
├── ssl_analyzer.py # Análisis de certificados SSL/TLS
├── cve_lookup.py # Cliente API NVD con backoff exponencial
├── scan_service.py # Capturas de pantalla, GeoIP, puente nmap
├── resolver.py # Resolución DNS + protección SSRF
├── cache.py # Caché TTL-LRU de capturas de pantalla
├── models.py # Modelos Pydantic v2 de request/response
├── config.py # Mapa PORT_RISK
├── logging_config.py # Logs estructurados en JSON
├── pdf_generator.py # Renderizador PDF con WeasyPrint
├── tech_signatures.json # Firmas de detección de tecnologías
│
├── static/ # Frontend (servido por FastAPI)
│ ├── index.html
│ ├── styles.css
│ ├── state.js
│ ├── ui.js
│ ├── api.js
│ ├── templates.js
│ ├── export.js
│ └── main.js
│
├── data/
│ └── GeoLite2-City.mmdb # Base de datos MaxMind GeoIP (no incluida)
│
├── .env.example
├── requirements.txt
├── Dockerfile
└── docker-compose.yml
LukitaPort · jaimefg1888 · For educational use only · Solo para uso educativo