#!/usr/bin/env python3 """Genera las fichas de herramientas OSINT en el vault osint + el MOC indice. Usa la funcion del registry create_obsidian_note_py_obsidian para escribir cada ficha como .md plano con frontmatter YAML (sin abrir la GUI de Obsidian). Fuente de verdad: la lista TOOLS de abajo. Idempotente con overwrite=True. """ import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "python", "functions")) # Permitir ejecucion desde la raiz del repo tambien: sys.path.insert(0, os.path.join("/home/enmanuel/fn_registry", "python", "functions")) from obsidian import create_obsidian_note # noqa: E402 VAULT = "/home/enmanuel/Obsidian/osint" # Etiquetas de categoria legibles para el MOC. CAT_LABELS = { "buscador": "Buscadores y meta-buscadores", "identidad": "Identidad — username, email, telefono, brechas", "social": "Redes sociales y rostros", "dominio": "Dominios e infraestructura (pasivo)", "geolocalizacion": "Geolocalizacion (imagen, mapas, satelite, metadatos, IP)", "imagen-forense": "Verificacion forense de imagen y video", "archivo": "Archivo e historico web", "empresa-es": "Empresas y registros (España)", "framework": "Frameworks y portales de referencia", } CAT_ORDER = list(CAT_LABELS.keys()) # Cada tool: slug, nombre, url, cat, coste, reg (requiere cuenta), amb, antibot # (bloquea curl / WAF, necesita navegador real), tags extra, para, como, gotchas. TOOLS = [ # ---------- Buscadores ---------- dict(slug="google-dorking", nombre="Google Dorking", url="https://www.google.com/advanced_search", cat="buscador", coste="free", reg=False, amb="global", antibot=False, tags=["dorks", "buscador"], para="Busqueda avanzada con operadores (site:, filetype:, intext:, inurl:) para encontrar documentos, perfiles y filtraciones indexadas.", como="Combina operadores: `site:linkedin.com \"nombre\"`, `filetype:pdf \"empresa\"`, `intext:\"email@dominio\"`.", gotchas=["Google limita resultados y muestra captcha si detecta scraping.", "Los dorks no son ilegales pero acceder a lo que exponen puede serlo segun contexto."]), dict(slug="bing", nombre="Bing Search", url="https://www.bing.com", cat="buscador", coste="free", reg=False, amb="global", antibot=False, tags=["buscador"], para="Segundo indice: a veces conserva resultados que Google ya purgo y soporta operadores propios.", como="Usa operadores `site:`, `ip:`, `feed:`. Util como contraste de Google.", gotchas=["Indice menor que Google; complementario, no sustituto."]), dict(slug="yandex-search", nombre="Yandex Search", url="https://yandex.com", cat="buscador", coste="free", reg=False, amb="global", antibot=False, tags=["buscador", "ruso"], para="Buscador ruso con cobertura fuerte de contenido del este de Europa y Asia que Google indexa peor.", como="Busqueda normal + su reverse image (ver ficha yandex-images) es la mejor del mercado.", gotchas=["Interfaz/resultados sesgados a region; usa yandex.com (no .ru) para ingles."]), dict(slug="duckduckgo", nombre="DuckDuckGo", url="https://duckduckgo.com", cat="buscador", coste="free", reg=False, amb="global", antibot=False, tags=["buscador", "privacidad"], para="Buscador sin tracking; sus bangs (!g, !w) saltan a otros indices rapido.", como="Usa `!` bangs para redirigir busquedas; util para consultas sin personalizacion.", gotchas=["Indice propio limitado; mezcla resultados de Bing."]), dict(slug="brave-search", nombre="Brave Search", url="https://search.brave.com", cat="buscador", coste="free", reg=False, amb="global", antibot=False, tags=["buscador", "privacidad"], para="Indice independiente (no depende de Google/Bing); buen contraste para descubrir fuentes distintas.", como="Busqueda directa; ofrece API de pago para automatizar.", gotchas=["Indice mas joven; cobertura desigual por idioma."]), dict(slug="intelligence-x", nombre="Intelligence X", url="https://intelx.io", cat="buscador", coste="freemium", reg=True, amb="global", antibot=False, tags=["leaks", "pastes", "darkweb"], para="Motor que indexa filtraciones, pastes, documentos, darkweb y datos historicos por selector (email, dominio, BTC, IP).", como="Busca un selector (email/dominio); la vista previa es gratis, el contenido completo requiere creditos.", gotchas=["Manejar datos de brechas puede tener implicaciones legales segun jurisdiccion.", "Cuenta gratuita muy limitada."]), # ---------- Identidad ---------- dict(slug="whatsmyname", nombre="WhatsMyName", url="https://whatsmyname.app", cat="identidad", coste="free", reg=False, amb="global", antibot=False, tags=["username", "enumeracion"], para="Enumera en que cientos de sitios existe un nombre de usuario concreto.", como="Escribe el username; devuelve presencia por sitio. Mismo dataset que usan herramientas CLI.", gotchas=["Falsos positivos: algunos sitios devuelven 200 para cualquier usuario.", "No confirma que sea la MISMA persona, solo que el handle existe."]), dict(slug="sherlock", nombre="Sherlock (CLI)", url="https://github.com/sherlock-project/sherlock", cat="identidad", coste="free", reg=False, amb="global", antibot=False, tags=["username", "cli"], para="Herramienta CLI que busca un username en 400+ redes sociales.", como="`sherlock `; genera lista de URLs donde el handle existe.", gotchas=["Falsos positivos frecuentes; verificar a mano.", "Requiere instalacion local (pip/docker)."]), dict(slug="maigret", nombre="Maigret (CLI)", url="https://github.com/soxoj/maigret", cat="identidad", coste="free", reg=False, amb="global", antibot=False, tags=["username", "cli"], para="Sucesor mas potente de Sherlock: 2500+ sitios y extrae datos del perfil (no solo presencia).", como="`maigret --html`; genera informe con perfiles y metadatos extraidos.", gotchas=["Mas lento por la cobertura; usar --top-sites para acotar.", "Instalacion local requerida."]), dict(slug="namechk", nombre="Namechk", url="https://namechk.com", cat="identidad", coste="free", reg=False, amb="global", antibot=True, tags=["username", "dominio"], para="Comprueba disponibilidad de un username (y dominios) en muchas plataformas a la vez.", como="Escribe el handle; marca ocupado/libre por servicio.", gotchas=["Pensado para branding, no para OSINT: no enlaza al perfil.", "Protegido por WAF; necesita navegador real (curl da 403)."]), dict(slug="instantusername", nombre="Instant Username Search", url="https://instantusername.com", cat="identidad", coste="free", reg=False, amb="global", antibot=True, tags=["username"], para="Chequeo de username en tiempo real sobre decenas de servicios.", como="Escribe el handle; resultados incrementales segun teclea.", gotchas=["Cobertura menor que Maigret.", "WAF: necesita navegador real."]), dict(slug="hunter-io", nombre="Hunter.io", url="https://hunter.io", cat="identidad", coste="freemium", reg=True, amb="global", antibot=False, tags=["email", "empresa"], para="Encuentra y verifica direcciones de correo asociadas a un dominio corporativo, con el patron (nombre.apellido@).", como="Introduce un dominio; devuelve correos conocidos + patron. Verificador de entregabilidad incluido.", gotchas=["Plan gratis pocas busquedas/mes.", "Solo correos corporativos publicos, no personales."]), dict(slug="emailrep", nombre="EmailRep", url="https://emailrep.io", cat="identidad", coste="freemium", reg=True, amb="global", antibot=False, tags=["email", "reputacion"], para="Perfil de reputacion de un email: antiguedad, perfiles sociales ligados, si aparece en brechas, si es desechable.", como="Consulta el email via web o API; devuelve senales de riesgo y presencia.", gotchas=["API key gratuita con cuota baja.", "Datos agregados, no siempre actuales."]), dict(slug="epieos", nombre="Epieos", url="https://epieos.com", cat="identidad", coste="freemium", reg=False, amb="global", antibot=True, tags=["email", "telefono", "google-account"], para="A partir de un email o telefono revela cuenta de Google asociada (nombre, foto, reviews, calendario publico) y servicios vinculados.", como="Introduce email o telefono; muestra Google account, Gravatar y servicios donde esta registrado.", gotchas=["Protegido por DataDome (captcha); necesita navegador real.", "Funciones avanzadas de pago."]), dict(slug="holehe", nombre="holehe (CLI)", url="https://github.com/megadose/holehe", cat="identidad", coste="free", reg=False, amb="global", antibot=False, tags=["email", "cli"], para="Comprueba en que 120+ servicios esta registrado un email, sin alertar al titular.", como="`holehe email@dominio`; marca used/not-used por servicio via flujo de recuperacion.", gotchas=["Algunos modulos quedan obsoletos cuando los sitios cambian su login.", "Instalacion local requerida."]), dict(slug="haveibeenpwned", nombre="Have I Been Pwned", url="https://haveibeenpwned.com", cat="identidad", coste="freemium", reg=False, amb="global", antibot=False, tags=["brechas", "email"], para="Indica si un email aparece en brechas de datos publicas conocidas y en cuales.", como="Introduce el email en la web; la API (para automatizar) es de pago.", gotchas=["No muestra la contraseña, solo la brecha.", "API key de pago; el chequeo web es gratis."]), dict(slug="dehashed", nombre="DeHashed", url="https://dehashed.com", cat="identidad", coste="pago", reg=True, amb="global", antibot=True, tags=["brechas", "credenciales"], para="Buscador de credenciales filtradas por email, usuario, nombre, telefono o IP, con el dato concreto de la brecha.", como="Busca un selector; muestra registros (a veces con contraseña en claro/hash). Requiere suscripcion.", gotchas=["Uso de credenciales filtradas: cuidado legal y etico.", "De pago; WAF, navegador real."]), dict(slug="leakcheck", nombre="LeakCheck", url="https://leakcheck.io", cat="identidad", coste="freemium", reg=True, amb="global", antibot=False, tags=["brechas", "credenciales"], para="Alternativa a DeHashed: busca apariciones en brechas por email, usuario, telefono o dominio.", como="Introduce el selector; vista parcial gratis, detalle con plan.", gotchas=["Mismas precauciones legales que cualquier base de brechas."]), dict(slug="phoneinfoga", nombre="PhoneInfoga (CLI)", url="https://github.com/sundowndev/phoneinfoga", cat="identidad", coste="free", reg=False, amb="global", antibot=False, tags=["telefono", "cli"], para="Recon pasivo de numeros de telefono: operador, tipo de linea, pais y dorks automaticos para rastrear el numero.", como="`phoneinfoga scan -n +34...`; genera footprint y enlaces de busqueda.", gotchas=["No 'hackea' el numero; solo agrega datos publicos.", "Instalacion local; algunos scanners requieren API keys."]), dict(slug="truecaller", nombre="Truecaller", url="https://www.truecaller.com", cat="identidad", coste="freemium", reg=True, amb="global", antibot=False, tags=["telefono", "identificacion"], para="Identifica el nombre asociado a un numero de telefono via su base colaborativa.", como="Buscar el numero en web/app (requiere login). Util para name lookup.", gotchas=["Requiere cuenta y a veces app movil.", "Datos aportados por usuarios: pueden ser erroneos o estar desactualizados."]), # ---------- Social ---------- dict(slug="social-searcher", nombre="Social Searcher", url="https://www.social-searcher.com", cat="social", coste="freemium", reg=False, amb="global", antibot=False, tags=["redes", "monitorizacion"], para="Busca menciones publicas de una palabra/usuario en varias redes a la vez y analiza sentimiento.", como="Introduce termino/handle; agrega posts publicos recientes.", gotchas=["Solo contenido publico e indexado recientemente.", "Cuota gratuita limitada por dia."]), dict(slug="instaloader", nombre="Instaloader (CLI)", url="https://instaloader.github.io", cat="social", coste="free", reg=False, amb="global", antibot=False, tags=["instagram", "cli"], para="Descarga posts, stories, bio y metadatos publicos de perfiles de Instagram de forma reproducible.", como="`instaloader profile `; baja media + JSON de metadatos.", gotchas=["Instagram limita por rate y puede pedir login; usar con moderacion.", "Loguearse con tu cuenta deja de ser pasivo y arriesga baneo."]), dict(slug="x-advanced-search", nombre="X (Twitter) Advanced Search", url="https://x.com/search-advanced", cat="social", coste="free", reg=True, amb="global", antibot=False, tags=["twitter", "x"], para="Filtra tweets por usuario, fecha, palabras exactas, ubicacion y tipo, sin scrapear.", como="Usa operadores `from:`, `since:`, `until:`, `near:`, `geocode:`.", gotchas=["X ahora exige login para ver resultados.", "Busqueda geo (`geocode:`) cada vez mas limitada."]), dict(slug="pimeyes", nombre="PimEyes", url="https://pimeyes.com", cat="social", coste="pago", reg=True, amb="global", antibot=False, tags=["rostro", "reverse-face"], para="Buscador de caras: sube una foto y encuentra otras apariciones del mismo rostro en la web.", como="Sube la imagen del rostro; muestra coincidencias. Ver donde aparecen requiere plan de pago.", gotchas=["Implicaciones de privacidad serias; uso responsable.", "Ver URLs de las coincidencias es de pago."]), dict(slug="facecheck-id", nombre="FaceCheck.ID", url="https://facecheck.id", cat="social", coste="freemium", reg=True, amb="global", antibot=False, tags=["rostro", "reverse-face"], para="Reverse face search orientado a redes y noticias; alternativa a PimEyes.", como="Sube el rostro; devuelve coincidencias con score. Detalle completo con creditos.", gotchas=["Resultados con ruido; verificar siempre.", "Mismas cautelas de privacidad que PimEyes."]), # ---------- Dominio ---------- dict(slug="shodan", nombre="Shodan", url="https://www.shodan.io", cat="dominio", coste="freemium", reg=True, amb="global", antibot=False, tags=["infra", "puertos", "iot"], para="Motor de busqueda de dispositivos conectados: puertos, servicios, banners, camaras, ICS, ya indexados (no escaneas tu).", como="Busca `hostname:`, `org:`, `port:`, `country:`. La consulta lee el indice de Shodan, es pasivo.", gotchas=["Cuenta gratis con filtros/resultados limitados.", "El dato puede estar cacheado/desactualizado."]), dict(slug="censys", nombre="Censys Search", url="https://search.censys.io", cat="dominio", coste="freemium", reg=True, amb="global", antibot=True, tags=["infra", "certificados", "hosts"], para="Inventario de hosts y certificados en internet; muy fuerte para mapear infraestructura y certificados de una org.", como="Busca por host, certificado o dominio; pivota por fingerprint de cert.", gotchas=["WAF: navegador real.", "Free tier con cuota de consultas."]), dict(slug="fofa", nombre="FOFA", url="https://fofa.info", cat="dominio", coste="freemium", reg=True, amb="global", antibot=False, tags=["infra", "ciberespacio"], para="Buscador chino tipo Shodan/Censys; util para encontrar assets que los otros no indexan.", como="Sintaxis `domain=`, `ip=`, `title=`. Resultados del indice (pasivo).", gotchas=["Interfaz parcialmente en chino.", "Free tier limitado."]), dict(slug="crtsh", nombre="crt.sh", url="https://crt.sh", cat="dominio", coste="free", reg=False, amb="global", antibot=True, tags=["certificados", "subdominios"], para="Consulta Certificate Transparency: revela subdominios de un dominio a partir de los certificados emitidos.", como="`https://crt.sh/?q=%25.dominio.com` lista todos los subdominios certificados.", gotchas=["Servidor a veces lento o caido (curl da timeout); reintentar.", "Solo ve dominios con certificado emitido."]), dict(slug="securitytrails", nombre="SecurityTrails", url="https://securitytrails.com", cat="dominio", coste="freemium", reg=True, amb="global", antibot=True, tags=["dns", "historico", "whois"], para="DNS e historico WHOIS pasivo: registros DNS actuales e historicos, subdominios, cambios de propietario.", como="Busca el dominio; ve historico A/MX/NS y subdominios. API para automatizar.", gotchas=["Free tier muy limitado.", "WAF: navegador real."]), dict(slug="dnsdumpster", nombre="DNSDumpster", url="https://dnsdumpster.com", cat="dominio", coste="free", reg=False, amb="global", antibot=False, tags=["dns", "subdominios", "mapa"], para="Mapa DNS gratuito de un dominio: subdominios, registros MX/NS y grafo de hosts.", como="Introduce el dominio; genera tabla + grafo de la superficie DNS.", gotchas=["Datos pasivos, no exhaustivos.", "Limite de consultas por dia."]), dict(slug="viewdns", nombre="ViewDNS.info", url="https://viewdns.info", cat="dominio", coste="freemium", reg=False, amb="global", antibot=False, tags=["dns", "whois", "reverse-ip"], para="Caja de herramientas DNS/WHOIS: reverse IP (que dominios comparten IP), whois, propagacion, historico.", como="Elige la herramienta (reverse IP lookup, whois history) e introduce dominio/IP.", gotchas=["API de pago; web gratis con limites.", "Reverse IP incompleto en hostings compartidos grandes."]), dict(slug="urlscan-io", nombre="urlscan.io", url="https://urlscan.io", cat="dominio", coste="freemium", reg=False, amb="global", antibot=False, tags=["url", "scan", "screenshot"], para="Analiza una URL en sandbox: capturas, recursos cargados, dominios contactados, tecnologias. La busqueda de scans previos es 100% pasiva.", como="Busca el dominio en la pestaña Search para ver scans publicos previos sin lanzar uno nuevo.", gotchas=["Lanzar un scan publico es visible para terceros; usa 'unlisted/private' si no quieres alertar.", "Un scan activo toca el sitio objetivo (deja de ser pasivo)."]), # ---------- Geolocalizacion ---------- dict(slug="yandex-images", nombre="Yandex Images (reverse)", url="https://yandex.com/images/", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["reverse-image", "rostro", "lugar"], para="La mejor busqueda inversa de imagenes para lugares y rostros; clave para geolocalizar fotos.", como="Sube la foto o pega su URL; Yandex sugiere imagenes y lugares visualmente similares.", gotchas=["Sesgo a contenido del este; usar junto a Google Lens.", "Puede pedir captcha tras varias subidas."]), dict(slug="google-lens", nombre="Google Lens", url="https://lens.google.com", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["reverse-image", "objetos", "texto"], para="Reconoce objetos, texto, productos y lugares en una imagen; bueno para identificar carteles, logos, edificios.", como="Sube la imagen; recorta la zona de interes (cartel, fachada) para afinar.", gotchas=["Mejor para objetos/texto que para coincidencia exacta de foto.", "Combinar con Yandex para lugares."]), dict(slug="bing-visual-search", nombre="Bing Visual Search", url="https://www.bing.com/visualsearch", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["reverse-image"], para="Tercera busqueda inversa para contrastar Yandex y Google.", como="Sube la imagen; revisa coincidencias y paginas que la contienen.", gotchas=["Cobertura menor; usar como tercera opinion."]), dict(slug="tineye", nombre="TinEye", url="https://tineye.com", cat="geolocalizacion", coste="freemium", reg=False, amb="global", antibot=False, tags=["reverse-image", "origen"], para="Reverse image enfocado al ORIGEN y primera aparicion de una imagen (no a contenido similar).", como="Sube la imagen; ordena por 'oldest' para encontrar la fuente original.", gotchas=["No busca caras/objetos similares, solo la misma imagen y ediciones.", "Indice mas pequeño que Yandex."]), dict(slug="geospy", nombre="GeoSpy AI", url="https://geospy.ai", cat="geolocalizacion", coste="freemium", reg=True, amb="global", antibot=False, tags=["geolocalizacion", "ia", "lugar"], para="Estima la ubicacion de una foto mediante IA, incluso sin metadatos, a partir de pistas visuales.", como="Sube la imagen; devuelve una hipotesis de pais/ciudad con probabilidad.", gotchas=["Estimacion probabilistica: verificar siempre con mapas/street view.", "Acceso completo de pago."]), dict(slug="google-earth", nombre="Google Earth Pro", url="https://earth.google.com", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["satelite", "historico", "3d"], para="Imagenes satelitales con HISTORICO temporal y vista 3D; clave para datar cambios y medir.", como="Usa la barra de tiempo para ver la misma zona en distintas fechas; mide distancias y alturas.", gotchas=["Resolucion y fechas varian mucho por zona.", "Version Pro de escritorio tiene mas herramientas que la web."]), dict(slug="google-maps", nombre="Google Maps + Street View", url="https://maps.google.com", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["mapa", "street-view", "negocios"], para="Mapa, vista de calle con historico, reseñas y fotos de usuarios para confirmar y datar una ubicacion.", como="En Street View usa el reloj para ver capturas antiguas; revisa fotos de reseñas para interiores.", gotchas=["Street View no cubre todo; combinar con Mapillary.", "Las fotos de usuarios pueden estar mal ubicadas."]), dict(slug="bing-maps", nombre="Bing Maps (Bird's Eye)", url="https://www.bing.com/maps", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["mapa", "oblicua", "satelite"], para="Vista oblicua 'Bird's Eye' (45 grados) que muestra fachadas que el cenital no ve.", como="Activa 'Bird's Eye'; rota la vista para ver los cuatro lados de un edificio.", gotchas=["Cobertura oblicua limitada a ciertas ciudades."]), dict(slug="yandex-maps", nombre="Yandex Maps", url="https://yandex.com/maps", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["mapa", "street-view"], para="Street view (panoramas) y mapas con cobertura fuerte en Rusia, Turquia, Asia central y este de Europa.", como="Activa panoramas; util donde Google Street View no llega.", gotchas=["Cobertura floja en Europa occidental.", "Interfaz parcialmente en ruso."]), dict(slug="mapillary", nombre="Mapillary", url="https://www.mapillary.com", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=True, tags=["street-level", "crowdsourced"], para="Imagenes a nivel de calle aportadas por usuarios; cubre rutas y zonas que Street View ignora.", como="Navega el mapa; las lineas verdes son tramos con fotos. Filtra por fecha.", gotchas=["Calidad y cobertura desiguales (depende de aportaciones).", "WAF en la home; navegador real."]), dict(slug="kartaview", nombre="KartaView", url="https://kartaview.org", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["street-level", "crowdsourced", "open"], para="Alternativa abierta a Mapillary con imagenes a nivel de calle crowdsourced.", como="Explora el mapa para tramos fotografiados; descarga libre.", gotchas=["Cobertura menor que Mapillary."]), dict(slug="openstreetmap", nombre="OpenStreetMap", url="https://www.openstreetmap.org", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["mapa", "open", "datos"], para="Mapa libre con datos crudos etiquetados (tipo de edificio, comercio, altura) consultables.", como="Usa el editor/inspector para ver tags de un elemento; base para Overpass.", gotchas=["Detalle depende de la comunidad local."]), dict(slug="overpass-turbo", nombre="Overpass Turbo", url="https://overpass-turbo.eu", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["consulta", "osm", "features"], para="Consulta los datos de OSM por tipo de objeto: 'todas las gasolineras / antenas / iglesias en esta zona'.", como="Escribe una query Overpass QL y ejecutala sobre el area visible; ideal para acotar candidatos al geolocalizar.", gotchas=["Curva de aprendizaje de la sintaxis QL.", "Solo encuentra lo que esta etiquetado en OSM."]), dict(slug="wikimapia", nombre="Wikimapia", url="https://wikimapia.org", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=True, tags=["mapa", "crowdsourced", "etiquetas"], para="Mapa colaborativo con descripciones de lugares y edificios que otros mapas no etiquetan.", como="Navega y lee las descripciones de los poligonos dibujados por usuarios.", gotchas=["Informacion sin verificar; tratar como pista.", "WAF; navegador real."]), dict(slug="sentinel-eo-browser", nombre="Sentinel Hub EO Browser", url="https://apps.sentinel-hub.com/eo-browser/", cat="geolocalizacion", coste="free", reg=True, amb="global", antibot=False, tags=["satelite", "sentinel", "multiespectral"], para="Imagenes Sentinel/Landsat recientes y multiespectrales; ver cambios, incendios, agua, vegetacion por fecha.", como="Selecciona zona y fecha; cambia entre bandas (true color, NDVI, etc.).", gotchas=["Resolucion ~10m (no ve coches/personas).", "Requiere cuenta gratuita para algunas capas."]), dict(slug="zoom-earth", nombre="Zoom Earth", url="https://zoom.earth", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["satelite", "tiempo-real", "clima"], para="Vista satelital casi en tiempo real con capas meteorologicas (nubes, tormentas, incendios).", como="Navega el globo; util para contexto temporal/meteo de un evento.", gotchas=["Resolucion baja; para contexto, no detalle."]), dict(slug="nasa-worldview", nombre="NASA Worldview", url="https://worldview.earthdata.nasa.gov", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["satelite", "historico", "nasa"], para="Imagenes satelitales diarias historicas de la NASA (MODIS/VIIRS) con barra temporal global.", como="Elige fecha y capa; descarga la imagen del dia para una region.", gotchas=["Resolucion baja (cientos de m); para fenomenos grandes."]), dict(slug="suncalc", nombre="SunCalc", url="https://www.suncalc.org", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["chronolocation", "sol", "sombras"], para="Calcula la posicion del sol y direccion de las sombras para una ubicacion y hora dadas (chronolocation).", como="Fija el punto en el mapa y la fecha; ajusta la hora hasta que la sombra coincida con la de la foto.", gotchas=["Necesitas estimar bien la fecha/estacion.", "Funciona al reves: deducir la hora a partir de la sombra observada."]), dict(slug="shadowmap", nombre="Shadowmap", url="https://shadowmap.org", cat="geolocalizacion", coste="freemium", reg=False, amb="global", antibot=False, tags=["chronolocation", "sombras", "3d"], para="Simula sombras de edificios en 3D segun hora/fecha; util para chronolocation en ciudad.", como="Coloca la vista en la zona y desliza la hora para comparar sombras de edificios.", gotchas=["Modelo 3D solo en ciudades grandes.", "Funciones avanzadas de pago."]), dict(slug="exiftool", nombre="ExifTool (CLI)", url="https://exiftool.org", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["metadatos", "exif", "cli"], para="Lee todos los metadatos de una imagen/archivo: GPS, fecha, camara, software. El estandar para EXIF.", como="`exiftool foto.jpg`; busca GPSLatitude/Longitude y DateTimeOriginal.", gotchas=["Las redes sociales borran EXIF al subir; suele haber GPS solo en originales.", "Instalacion local (perl)."]), dict(slug="metadata2go", nombre="Metadata2Go", url="https://www.metadata2go.com", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["metadatos", "exif", "online"], para="Visor EXIF online sin instalar nada: sube el archivo y lee sus metadatos.", como="Sube la imagen; muestra GPS, fecha y datos de camara.", gotchas=["Subes el archivo a un tercero: no usar con material sensible.", "Preferir ExifTool local para evidencia."]), dict(slug="jimpl", nombre="Jimpl", url="https://jimpl.com", cat="geolocalizacion", coste="free", reg=False, amb="global", antibot=False, tags=["metadatos", "exif", "gps", "online"], para="Visor EXIF online que situa la coordenada GPS de la foto directamente en un mapa.", como="Sube la imagen; si tiene GPS, lo pinta en el mapa.", gotchas=["Mismo aviso de privacidad: subes a un tercero."]), dict(slug="ipinfo", nombre="IPinfo", url="https://ipinfo.io", cat="geolocalizacion", coste="freemium", reg=True, amb="global", antibot=False, tags=["ip-geo", "asn"], para="Geolocalizacion de IP, ASN, organizacion y tipo de conexion (hosting/movil/vpn).", como="`ipinfo.io/` en web o API; util para ubicar el origen aproximado de una IP.", gotchas=["Geo de IP es aproximada (ciudad/region, no direccion).", "VPN/proxy falsean la ubicacion."]), dict(slug="ipgeolocation", nombre="ipgeolocation.io", url="https://ipgeolocation.io", cat="geolocalizacion", coste="freemium", reg=True, amb="global", antibot=False, tags=["ip-geo", "api"], para="API de geolocalizacion de IP con zona horaria, moneda y datos de red.", como="Consulta por IP via API; devuelve pais, region, ciudad aproximada.", gotchas=["Precision limitada; varias fuentes discrepan.", "Requiere API key."]), dict(slug="maxmind", nombre="MaxMind GeoIP", url="https://www.maxmind.com", cat="geolocalizacion", coste="freemium", reg=True, amb="global", antibot=False, tags=["ip-geo", "base-datos"], para="Base de datos GeoIP estandar de la industria (GeoLite2 gratis) para resolver IP a ubicacion offline.", como="Descarga GeoLite2 (cuenta gratis) y consulta localmente; o usa su web demo.", gotchas=["GeoLite2 menos precisa que la version de pago.", "Requiere cuenta para descargar las DB."]), # ---------- Imagen forense ---------- dict(slug="invid-weverify", nombre="InVID / WeVerify", url="https://www.invid-project.eu", cat="imagen-forense", coste="free", reg=False, amb="global", antibot=False, tags=["video", "verificacion", "frames", "extension"], para="Extension del navegador para verificar videos/imagenes: extrae frames, hace reverse search, lee metadatos y detecta manipulaciones.", como="Instala la extension (Fake News Debunker); pega la URL del video para sacar keyframes y buscarlos.", gotchas=["Es una extension de navegador, no una web de consulta directa.", "Algunas integraciones de redes se rompen cuando cambian sus APIs."]), dict(slug="forensically", nombre="Forensically", url="https://29a.ch/photo-forensics/", cat="imagen-forense", coste="free", reg=False, amb="global", antibot=False, tags=["forense", "ela", "clonado"], para="Suite forense de imagen en el navegador: ELA, deteccion de clonado, analisis de ruido, lupa de detalle.", como="Sube la foto; usa Clone Detection y Error Level Analysis para detectar zonas editadas.", gotchas=["ELA da pistas, no pruebas; interpretar con cuidado.", "Funciona en local en el navegador (no sube la imagen)."]), dict(slug="fotoforensics", nombre="FotoForensics", url="https://fotoforensics.com", cat="imagen-forense", coste="free", reg=False, amb="global", antibot=False, tags=["forense", "ela"], para="Analisis ELA online clasico para detectar montajes en imagenes JPEG.", como="Sube la imagen; las zonas con distinto nivel de error sugieren edicion.", gotchas=["Subes la imagen a un tercero (queda publica un tiempo).", "ELA tiene muchos falsos positivos en imagenes recomprimidas."]), # ---------- Archivo ---------- dict(slug="wayback-machine", nombre="Wayback Machine", url="https://web.archive.org", cat="archivo", coste="free", reg=False, amb="global", antibot=False, tags=["archivo", "historico", "web"], para="Archivo historico de paginas web: ver como era un sitio/perfil en una fecha pasada.", como="Pega la URL; navega por las capturas en la linea de tiempo. Permite guardar una captura nueva.", gotchas=["No todo esta archivado ni en todas las fechas.", "robots.txt o peticiones de borrado pueden ocultar capturas."]), dict(slug="archive-today", nombre="archive.today", url="https://archive.ph", cat="archivo", coste="free", reg=False, amb="global", antibot=False, tags=["archivo", "snapshot"], para="Captura puntual de una pagina (incluye contenido dinamico) que queda congelada e inmune a borrados.", como="Pega la URL para crear o buscar una captura; util para preservar evidencia antes de que se borre.", gotchas=["Capturas puntuales, no rastreo continuo como Wayback.", "Dominios espejo varios (.ph/.is/.today)."]), dict(slug="cachedview", nombre="CachedView", url="https://cachedview.nl", cat="archivo", coste="free", reg=False, amb="global", antibot=False, tags=["cache", "google"], para="Atajo para ver versiones cacheadas de una pagina (Google Cache, Wayback) en un sitio.", como="Pega la URL; ofrece enlaces a las caches disponibles.", gotchas=["Google Cache esta practicamente retirado; cada vez menos util.", "Depende de que el tercero conserve la copia."]), # ---------- Empresa ES ---------- dict(slug="catastro", nombre="Sede Catastro", url="https://www.sedecatastro.gob.es", cat="empresa-es", coste="free", reg=False, amb="espana", antibot=False, tags=["inmueble", "catastro", "geo"], para="Catastro español: localiza inmuebles por direccion o referencia catastral, con superficie, uso, año y geometria sobre mapa.", como="Busca por direccion; obtiene referencia catastral, plano y datos del inmueble (titular parcial sin certificado).", gotchas=["El titular completo requiere certificado/identificacion y acreditar interes.", "Cruzar referencia catastral con la direccion del objetivo."]), dict(slug="borme", nombre="BORME (BOE)", url="https://www.boe.es/diario_borme/", cat="empresa-es", coste="free", reg=False, amb="espana", antibot=False, tags=["mercantil", "empresa", "boletin"], para="Boletin Oficial del Registro Mercantil: constituciones, nombramientos, ceses y cambios de empresas españolas.", como="Busca por nombre de persona o sociedad; revela en que empresas figura alguien como administrador.", gotchas=["URL correcta es /diario_borme/ (la /borme/ da 404).", "Datos historicos por fecha de publicacion."]), dict(slug="libreborme", nombre="Libreborme", url="https://libreborme.net", cat="empresa-es", coste="free", reg=False, amb="espana", antibot=True, tags=["mercantil", "empresa", "buscador"], para="Interfaz buscable sobre los datos del BORME: ficha de persona/empresa con sus cargos y relaciones.", como="Busca el nombre; ve sus sociedades, cargos y co-administradores de un vistazo.", gotchas=["Cobertura desde ~2009; datos antiguos pueden faltar.", "WAF; navegador real."]), dict(slug="einforma", nombre="eInforma", url="https://www.einforma.com", cat="empresa-es", coste="freemium", reg=True, amb="espana", antibot=False, tags=["mercantil", "empresa", "informe"], para="Informes mercantiles de empresas españolas: cargos, cuentas, CIF, vinculaciones.", como="Busca la empresa o el administrador; datos basicos gratis, informe completo de pago.", gotchas=["Lo detallado es de pago.", "Requiere cuenta."]), dict(slug="axesor", nombre="Axesor", url="https://www.axesor.es", cat="empresa-es", coste="freemium", reg=True, amb="espana", antibot=False, tags=["mercantil", "empresa", "rating"], para="Informacion mercantil y de riesgo de empresas españolas; alternativa a eInforma.", como="Busca empresa/persona; ficha basica gratis, informe financiero de pago.", gotchas=["Datos detallados de pago.", "Requiere cuenta."]), dict(slug="infoempresa", nombre="InfoEmpresa", url="https://www.infoempresa.com", cat="empresa-es", coste="freemium", reg=False, amb="espana", antibot=False, tags=["mercantil", "empresa"], para="Buscador de empresas españolas con administradores, objeto social y datos de contacto.", como="Busca por nombre de empresa o de persona para ver sus cargos.", gotchas=["Parte de la ficha de pago.", "Datos a veces desactualizados."]), dict(slug="idealista", nombre="Idealista", url="https://www.idealista.com", cat="empresa-es", coste="free", reg=False, amb="espana", antibot=True, tags=["inmueble", "fotos", "geo"], para="Portal inmobiliario: fotos y planos de viviendas que permiten cruzar una direccion con el interior y el entorno.", como="Busca por zona/direccion; las fotos del anuncio revelan interior, vistas y referencias del lugar.", gotchas=["Anuncios caducan; usar Wayback para los retirados.", "WAF (DataDome); navegador real."]), dict(slug="fotocasa", nombre="Fotocasa", url="https://www.fotocasa.es", cat="empresa-es", coste="free", reg=False, amb="espana", antibot=False, tags=["inmueble", "fotos", "geo"], para="Segundo portal inmobiliario español; util para contrastar fotos/anuncios con Idealista.", como="Busca por zona; cruza fotos y precios con el otro portal.", gotchas=["Cobertura solapada con Idealista; usar ambos."]), dict(slug="infobel", nombre="Infobel", url="https://www.infobel.com/es", cat="empresa-es", coste="free", reg=False, amb="espana", antibot=True, tags=["telefono", "directorio", "paginas-blancas"], para="Paginas blancas/amarillas online: cruza nombre, telefono y direccion en España y otros paises.", como="Busca por nombre o telefono para resolver el otro dato.", gotchas=["Cobertura desigual; muchos numeros no listados (LOPD).", "WAF; navegador real."]), # ---------- Frameworks ---------- dict(slug="osint-framework", nombre="OSINT Framework", url="https://osintframework.com", cat="framework", coste="free", reg=False, amb="global", antibot=False, tags=["indice", "arbol", "recursos"], para="Arbol navegable de cientos de recursos OSINT clasificados por tipo de dato (email, username, dominio, geo...).", como="Navega el arbol por categoria para descubrir herramientas especificas para cada dato.", gotchas=["Algunos enlaces estan muertos o desactualizados.", "Es un indice, no ejecuta nada."]), dict(slug="bellingcat-toolkit", nombre="Bellingcat's Online Toolkit", url="https://bellingcat.gitbook.io/toolkit", cat="framework", coste="free", reg=False, amb="global", antibot=False, tags=["indice", "investigacion", "geo"], para="Coleccion curada por Bellingcat de herramientas para investigacion, con fuerte enfasis en geolocalizacion y verificacion.", como="Busca por categoria (maps, satellite, social) la herramienta recomendada y probada por investigadores.", gotchas=["Curada pero amplia; empezar por las marcadas como favoritas."]), dict(slug="inteltechniques", nombre="IntelTechniques Tools", url="https://inteltechniques.com/tools/", cat="framework", coste="free", reg=False, amb="global", antibot=False, tags=["indice", "formularios", "bazzell"], para="Conjunto de formularios de busqueda (Michael Bazzell) que automatizan consultas por email, username, telefono, etc.", como="Abre la herramienta del dato que tengas; rellena y lanza busquedas en multiples servicios.", gotchas=["Algunas herramientas se rompen cuando los servicios cambian sus URLs.", "Parte del contenido premium esta en sus libros."]), dict(slug="start-me-osint", nombre="Start.me — OSINT Collection", url="https://start.me/p/DPYPMz/the-ultimate-osint-collection", cat="framework", coste="free", reg=False, amb="global", antibot=True, tags=["indice", "enlaces", "dashboard"], para="Dashboard comunitario con cientos de enlaces OSINT agrupados por tema; bueno para descubrir fuentes nuevas.", como="Navega los paneles por categoria; marca los enlaces utiles.", gotchas=["Calidad variable (es comunitario).", "WAF; navegador real."]), ] # ============================================================================ # Grupo recon: funciones del registry (NO sitios web) que se ejecutan con # `fn run` y archivan su resultado por objetivo en el vault + DuckDB. # docs/capabilities/recon.md + projects/osint/CONVENTIONS.md §9. # ============================================================================ RECON_TOOLS = [ dict(slug="whois-lookup", nombre="whois_lookup", rid="whois_lookup_py_cybersecurity", modo="pasivo", sudo=False, persiste=True, comando="fn run recon_osint whois", para="Lookup WHOIS de un dominio o IP: registrar, pais del registrante, fechas (creacion/expiracion/actualizacion) y name servers, parseados best-effort sobre el raw.", gotchas=["Pasivo (consulta el registro, no toca al objetivo).", "El parseo depende del formato del registrar; el `raw` completo siempre se guarda."]), dict(slug="rdap-lookup", nombre="rdap_lookup", rid="rdap_lookup_py_cybersecurity", modo="pasivo", sudo=False, persiste=True, comando="fn run recon_osint rdap", para="Lookup RDAP (sustituto JSON moderno de WHOIS) de dominio, IP o ASN (p.ej. AS15169); devuelve datos estructurados.", gotchas=["Pasivo.", "Mas fiable de parsear que WHOIS; preferir cuando el TLD lo soporta."]), dict(slug="dns-records", nombre="dns_records", rid="dns_records_py_cybersecurity", modo="pasivo", sudo=False, persiste=True, comando="fn run recon_osint dns", para="Registros DNS via `dig +short` (A, AAAA, MX, NS, SOA, TXT, CNAME por defecto) en un dict por tipo.", gotchas=["Pasivo (consulta a resolvers DNS, no al objetivo).", "Resultados dependen del resolver/cache local."]), dict(slug="ping-host", nombre="ping_host", rid="ping_host_py_cybersecurity", modo="activo", sudo=False, persiste=True, comando="fn run recon_osint ping", para="Sondeo ICMP: porcentaje de perdida y RTT (avg/min/max) hacia el host.", gotchas=["ACTIVO: envia paquetes al objetivo.", "Host filtrado por firewall = loss 100% pero status:ok (no es error)."]), dict(slug="traceroute-host", nombre="traceroute_host", rid="traceroute_host_py_cybersecurity", modo="activo", sudo=False, persiste=True, comando="fn run recon_osint traceroute", para="Traza la ruta de red hasta el host: lista de hops con nombre/IP/RTT.", gotchas=["ACTIVO: genera trafico hacia el objetivo y la ruta intermedia.", "Hops sin respuesta (`* * *`) salen con hosts vacios."]), dict(slug="nmap-scan", nombre="nmap_scan", rid="nmap_scan_py_cybersecurity", modo="activo", sudo="perfiles os/udp-top/aggressive", persiste=True, comando="fn run recon_osint nmap # perfil quick por defecto", para="Escaneo de puertos y servicios con nmap por perfiles (quick, top1000, service -sV -sC, vuln, udp-top, aggressive -A, discovery CIDR, os). Salida XML parseada a open_ports/hosts_up.", gotchas=["ACTIVO E INTRUSIVO: solo contra objetivos PROPIOS o con autorizacion explicita. Escanear infra ajena puede ser ilegal.", "Perfiles os/udp-top/aggressive requieren sudo.", "Perfiles largos (vuln, aggressive) → lanzar en segundo plano (&/background).", "discovery acepta CIDR: cuidado de no barrer rangos que no son tuyos."]), dict(slug="save-scan-to-osint", nombre="save_scan_to_osint", rid="save_scan_to_osint_py_cybersecurity", modo="sink", sudo=False, persiste=True, comando="# se invoca dentro del pipeline; uso directo para archivar un raw externo", para="Sink comun: archiva el resultado de CUALQUIER scan en el ecosistema OSINT. Dos capas: nota Markdown tipada en el vault (siempre) + POST a osint_db para registro DuckDB (best-effort).", gotchas=["Si el service osint_db esta caido o el endpoint da 404, degrada a solo-nota (register_warning) sin fallar.", "No lanza excepciones: devuelve dict de estado con note_path/registered/scan_id."]), dict(slug="recon-osint", nombre="recon_osint (pipeline)", rid="recon_osint_py_pipelines", modo="segun scan", sudo="si el scan lo pide", persiste=True, comando="fn run recon_osint ", para="Pipeline one-shot: ejecuta un scan del tipo pedido y lo archiva en OSINT en una sola llamada. El camino canonico para recon + archivado por objetivo.", gotchas=["Hereda el modo del scan elegido (whois/rdap/dns pasivos; ping/traceroute/nmap activos).", "`save=False` ejecuta sin archivar (raro; por defecto archiva)."]), ] RECON_PERSIST_LINES = [ "- **Nota** (siempre, fuente de verdad): `~/Obsidian/osint/dominios//recon/-.md` (`tipo: scan-red`, raw en bloque de codigo).", "- **DuckDB** (best-effort): tabla `network_scans` en `osint_db` via `POST 127.0.0.1:8771/api/scan`.", "- **Consultar** (code block `osintdb` del plugin): `SELECT * FROM network_scans WHERE target_slug='';`", "- Service `osint_db` caido → degrada a solo-nota sin fallar.", ] def build_recon_frontmatter(t): return { "tipo": "herramienta", "nombre": t["nombre"], "slug": t["slug"], "registry_id": t["rid"], "categoria": "recon", "osint_modo": t["modo"], "coste": "free", "comando": t["comando"], "requiere_sudo": t["sudo"], "persiste_en_vault": bool(t["persiste"]), "tags": ["herramienta", "osint", "recon", "registry", "red"], } def build_recon_body(t): lines = [] lines.append("> Funcion del registry (no es un sitio web): se ejecuta con `fn run` y archiva el resultado por objetivo.") lines.append("") lines.append("## Para que") lines.append("") lines.append(t["para"]) lines.append("") lines.append("## Como llamar") lines.append("") lines.append("```bash") lines.append(t["comando"]) lines.append("```") lines.append("") lines.append(f"Registry ID: `{t['rid']}`. Inspeccionar: `mcp__registry__fn_show id=\"{t['rid']}\"`.") lines.append("") lines.append("## Persistencia (resultados por objetivo)") lines.append("") if t["modo"] == "sink": lines.append("Es el sink que escribe la nota y registra en DuckDB:") else: lines.append("Al pasar por el pipeline `recon_osint` (o por `save_scan_to_osint`):") lines.append("") lines.extend(RECON_PERSIST_LINES) lines.append("") lines.append("## Gotchas") lines.append("") for g in t["gotchas"]: lines.append(f"- {g}") return "\n".join(lines) def build_body(t): lines = [] lines.append("## Para que") lines.append("") lines.append(t["para"]) lines.append("") lines.append("## Como usar") lines.append("") lines.append(t["como"]) lines.append("") lines.append("## Gotchas") lines.append("") for g in t["gotchas"]: lines.append(f"- {g}") return "\n".join(lines) def build_frontmatter(t): fm = { "tipo": "herramienta", "nombre": t["nombre"], "slug": t["slug"], "url": t["url"], "categoria": t["cat"], "osint_modo": "pasivo", "coste": t["coste"], "registro": bool(t["reg"]), "ambito": t["amb"], "anti_bot": bool(t["antibot"]), "tags": ["herramienta", "osint", t["cat"]] + t["tags"], } return fm def main(): created = [] # Validar slugs unicos (web + recon no colisionan). slugs = [t["slug"] for t in TOOLS] + [t["slug"] for t in RECON_TOOLS] assert len(slugs) == len(set(slugs)), "slugs duplicados: " + str( [s for s in slugs if slugs.count(s) > 1] ) for t in TOOLS: rel = f"herramientas/{t['slug']}.md" path = create_obsidian_note( vault_dir=VAULT, rel_path=rel, body=build_body(t), frontmatter=build_frontmatter(t), overwrite=True, ) created.append((t["cat"], t["slug"], path)) for t in RECON_TOOLS: rel = f"herramientas/{t['slug']}.md" path = create_obsidian_note( vault_dir=VAULT, rel_path=rel, body=build_recon_body(t), frontmatter=build_recon_frontmatter(t), overwrite=True, ) created.append(("recon", t["slug"], path)) # ----- MOC index ----- moc_lines = [] moc_lines.append("Indice de herramientas online para investigaciones OSINT pasivo.") moc_lines.append("") moc_lines.append( "Cada herramienta tiene su ficha con `## Para que`, `## Como usar` y `## Gotchas`. " "El campo `anti_bot: true` marca las que bloquean clientes simples y necesitan un navegador real " "(perfil Chromium dedicado). Modo de uso por defecto: **pasivo** (no se interactua con los sistemas del objetivo)." ) moc_lines.append("") total = len(TOOLS) + len(RECON_TOOLS) moc_lines.append( f"**Total: {total} herramientas** ({len(TOOLS)} sitios/CLIs externos pasivos " f"+ {len(RECON_TOOLS)} funciones del registry del grupo `recon`, estas ultimas ejecutables " f"con `fn run` y con archivado por objetivo)." ) moc_lines.append("") for cat in CAT_ORDER: items = [t for t in TOOLS if t["cat"] == cat] if not items: continue moc_lines.append(f"## {CAT_LABELS[cat]}") moc_lines.append("") moc_lines.append("| Herramienta | Coste | Cuenta | Ambito | Para que |") moc_lines.append("|---|---|---|---|---|") for t in items: cuenta = "si" if t["reg"] else "no" ab = " ⚠️navegador" if t["antibot"] else "" link = f"[[{t['slug']}\\|{t['nombre']}]]" resumen = t["para"].strip() moc_lines.append( f"| {link}{ab} | {t['coste']} | {cuenta} | {t['amb']} | {resumen} |" ) moc_lines.append("") # ----- Seccion recon (funciones del registry, no sitios web) ----- moc_lines.append("## Recon de red (registry, ejecutable)") moc_lines.append("") moc_lines.append( "Estas NO son sitios web: son funciones del registry (grupo `recon`, dominio `cybersecurity`) " "que se ejecutan con `fn run` y que **archivan su resultado por objetivo** en el vault + DuckDB. " "Doctrina: *todo escaneo se guarda siempre*. Pagina madre: `docs/capabilities/recon.md`." ) moc_lines.append("") moc_lines.append("| Herramienta | Registry ID | Modo | Sudo | Para que |") moc_lines.append("|---|---|---|---|---|") for t in RECON_TOOLS: sudo = t["sudo"] if isinstance(t["sudo"], str) else ("si" if t["sudo"] else "no") modo_tag = " ⚠️activo" if t["modo"] == "activo" else "" link = f"[[{t['slug']}\\|{t['nombre']}]]" moc_lines.append( f"| {link}{modo_tag} | `{t['rid']}` | {t['modo']} | {sudo} | {t['para'].strip()} |" ) moc_lines.append("") moc_lines.append("**Patron canonico (recon + archivado por objetivo en 1 call):**") moc_lines.append("") moc_lines.append("```bash") moc_lines.append("fn run recon_osint ") moc_lines.append("```") moc_lines.append("") moc_lines.append("Cada call deja:") moc_lines.extend(RECON_PERSIST_LINES) moc_lines.append("") moc_lines.append("## Notas de uso") moc_lines.append("") moc_lines.append( "- **Pasivo vs activo:** todo este indice es OSINT pasivo (fuentes publicas y de terceros). " "Lanzar un scan que toque el sistema del objetivo (p.ej. un scan nuevo en urlscan, loguearse en una cuenta) " "deja de ser pasivo." ) moc_lines.append( "- **Geolocalizacion de una foto (flujo tipico):** 1) `[[exiftool]]`/`[[jimpl]]` por si hay GPS en metadatos; " "2) reverse image con `[[yandex-images]]` + `[[google-lens]]`; 3) confirmar en `[[google-maps]]`/`[[google-earth]]` + `[[mapillary]]`; " "4) datar por sombras con `[[suncalc]]`/`[[shadowmap]]`; 5) acotar features con `[[overpass-turbo]]`." ) moc_lines.append( "- **España:** para inmuebles/direcciones cruzar `[[catastro]]` + `[[idealista]]`/`[[fotocasa]]`; " "para empresas y cargos `[[borme]]`/`[[libreborme]]`." ) moc_lines.append( "- **Brechas y credenciales** (`[[dehashed]]`, `[[leakcheck]]`, `[[intelligence-x]]`): manejar con cautela legal/etica." ) moc_lines.append( "- **Recon de red:** `[[ping-host]]`, `[[traceroute-host]]` y sobre todo `[[nmap-scan]]` son ACTIVOS " "(tocan al objetivo). Solo contra infra PROPIA o con autorizacion explicita. `[[whois-lookup]]`/`[[rdap-lookup]]`/`[[dns-records]]` " "son pasivos. Todo lo que ejecutes con `[[recon-osint]]` queda archivado por objetivo en el vault." ) moc_fm = { "tipo": "index", "tags": ["osint", "moc", "herramientas"], } moc_path = create_obsidian_note( vault_dir=VAULT, rel_path="herramientas/_indice.md", body="\n".join(moc_lines), frontmatter=moc_fm, overwrite=True, ) print(f"Fichas creadas: {len(created)}") print(f"MOC: {moc_path}") # Resumen por categoria from collections import Counter c = Counter(cat for cat, _, _ in created) for cat in CAT_ORDER: if c.get(cat): print(f" {cat:18} {c[cat]}") if __name__ == "__main__": main()