feat(recon): grupo de reconocimiento de red + servicios + fingerprint web
Añade el capability group `recon` (dominio cybersecurity + pipelines, Python),
con la política de archivado OSINT y página madre docs/capabilities/recon.md.
Lookups y sondeo (wrappers de CLI):
- whois_lookup, rdap_lookup, dns_records, ping_host, traceroute_host, nmap_scan
- save_scan_to_osint (sink común) + recon_osint (pipeline one-shot scan+archivado)
Escaneo de puertos/servicios nativo (stdlib, sin nmap ni sudo):
- scan_tcp_ports: connect-scan TCP concurrente (open/closed/filtered)
- grab_service_banner: banner grab + identificación de servicio/versión real
- identify_port_service: puro, puerto -> servicio IANA esperado (~120 puertos)
- scan_port_services: pipeline one-shot (scan -> identify + banner por puerto abierto)
Fingerprint de tecnología web (estilo Wappalyzer), patrón pura/impura:
- fetch_http_fingerprint: GET stdlib, recoge headers/html/cookies (solo nombres)
- detect_web_tech: puro, matchea ~50 firmas regex -> tecnologías por categoría
- fingerprint_web_stack: pipeline one-shot url -> tecnologías
Todas devuelven dict {status} sin lanzar. Tests: 43 verdes, sin red externa.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: save_scan_to_osint
|
||||
kind: function
|
||||
lang: py
|
||||
domain: cybersecurity
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def save_scan_to_osint(target: str, scan_type: str, raw: str, summary: dict | None = None, vault_dir: str = '~/Obsidian/osint', service_url: str = 'http://127.0.0.1:8771', tool: str | None = None) -> dict"
|
||||
description: "Sink comun OSINT: persiste el resultado de cualquier escaneo de red (whois|rdap|dns|nmap|traceroute|ping) en el ecosistema OSINT del repo. Dos capas: (1) capa nota SIEMPRE (fuente de verdad) que escribe una nota Markdown tipada en el vault Obsidian bajo dominios/<slug>/recon/<scan_type>-<ts>.md con el raw en bloque de codigo, componiendo create_obsidian_note; (2) capa registro estructurado best-effort que hace POST al service osint_db (DuckDB single-writer) en /api/scan. Si el service esta caido o el endpoint no existe (404), degrada a solo-nota con register_warning sin fallar. No lanza: devuelve dict de estado."
|
||||
tags: [recon, osint, cybersecurity, obsidian, sink]
|
||||
uses_functions: [create_obsidian_note_py_obsidian]
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_py_core"
|
||||
imports: []
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "python/functions/cybersecurity/save_scan_to_osint.py"
|
||||
params:
|
||||
- name: target
|
||||
desc: "Objetivo del scan (dominio, host o IP). Define el slug de la carpeta en el vault."
|
||||
- name: scan_type
|
||||
desc: "Tipo de scan (whois|rdap|dns|nmap|traceroute|ping). Texto libre que se sanea a slug seguro para nombre de archivo y tags."
|
||||
- name: raw
|
||||
desc: "Salida cruda del scan (texto). Se embebe en un bloque de codigo en la nota; si supera ~200KB se trunca dejando una marca."
|
||||
- name: summary
|
||||
desc: "dict opcional con campos resumidos del scan (registrar, ips, puertos, rtt...). Se anade al frontmatter de la nota y se envia al registro estructurado. None -> {}."
|
||||
- name: vault_dir
|
||||
desc: "Raiz del vault OSINT. Se expande ~. Default ~/Obsidian/osint."
|
||||
- name: service_url
|
||||
desc: "Base del service osint_db (FastAPI + DuckDB). Default http://127.0.0.1:8771. Se le concatena /api/scan."
|
||||
- name: tool
|
||||
desc: "Nombre de la herramienta usada (nmap, dig, whois...). Si None usa el scan_type saneado."
|
||||
output: "dict de estado. Caso ok: {status:'ok', target, slug, scan_type, note_path (rel al vault), note_abs (ruta absoluta), registered (bool: si el POST a osint_db tuvo exito), register_warning (str|None: motivo si el registro DuckDB fallo), scan_id (str|None: id devuelto por el service)}. Caso error (solo si falla la escritura critica de la nota): {status:'error', error: str}."
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join("python", "functions"))
|
||||
from cybersecurity.save_scan_to_osint import save_scan_to_osint
|
||||
|
||||
res = save_scan_to_osint(
|
||||
"example.com",
|
||||
"whois",
|
||||
"Domain: EXAMPLE.COM\nRegistrar: X",
|
||||
summary={"registrar": "X"},
|
||||
)
|
||||
print(res["note_path"]) # dominios/example.com/recon/whois-YYYYMMDD-HHMM.md
|
||||
print(res["registered"]) # True si osint_db esta vivo y expone POST /api/scan, False si degrado
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Tras cualquier escaneo de red (whois/rdap/dns/nmap/traceroute/ping), para que el
|
||||
resultado quede archivado y navegable en el vault OSINT. Llamar SIEMPRE despues de
|
||||
un scan. Es el sink comun del ecosistema: cualquier funcion de scan del registry
|
||||
(whois_lookup, dns_records, scan_port_tcp, etc.) deberia volcar aqui su salida.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Impura**: escribe en disco (el vault Obsidian) y hace una request HTTP de red.
|
||||
- **overwrite=True**: un re-scan del mismo target+tipo dentro del mismo minuto pisa la
|
||||
nota anterior (el timestamp del nombre de archivo tiene granularidad de minuto,
|
||||
`YYYYMMDD-HHMM`).
|
||||
- **Registro DuckDB best-effort**: la capa 2 depende de que el service `osint_db` este
|
||||
vivo y exponga `POST /api/scan`. Si esta caido (ConnectionError) o el endpoint no
|
||||
existe todavia (404), la funcion NO falla: degrada a solo-nota y devuelve
|
||||
`registered=False` + `register_warning` con el motivo. `status` sigue siendo `"ok"`
|
||||
porque la capa critica (la nota) se guardo.
|
||||
- **Single-writer DuckDB**: la DB esta abierta por el service `osint_db`. NUNCA abrir
|
||||
`osint.duckdb` directo en paralelo; el registro estructurado pasa SIEMPRE por HTTP.
|
||||
- **Solo `status:"error"`** si falla la escritura de la nota (capa critica). Un fallo de
|
||||
red nunca produce error.
|
||||
- **Contrato del endpoint** (lo crea el service osint_db): `POST /api/scan` con JSON
|
||||
`{target, target_slug, scan_type, tool, note_path, summary, scan_ts}`; respuesta 2xx,
|
||||
opcionalmente `{"id": "..."}` que se devuelve como `scan_id`.
|
||||
|
||||
## Notas
|
||||
|
||||
Compone `create_obsidian_note_py_obsidian` (del grupo obsidian) para la capa nota y usa
|
||||
`urllib.request` de stdlib para la capa de registro (sin dependencias nuevas). El
|
||||
timeout HTTP es de 5s. El raw se envuelve en un bloque de codigo fenced con backticks
|
||||
suficientes para no colisionar con backticks internos del propio raw.
|
||||
Reference in New Issue
Block a user