feat(infra): auto-commit con 88 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ Indice de grupos de capacidades del registry. Cada grupo agrupa >=3 funciones qu
|
||||
| [android](android.md) | 37 | Toolbelt Android desde WSL2: adb, emuladores AVD, APK build/install, Capacitor, logcat |
|
||||
| [web-proxy](web-proxy.md) | 5 | Captura de trafico HTTP/HTTPS liviana (mitmproxy): proxy con rotacion, navegador proxeado, consulta de capturas, tee del SSE de claude. Alternativa ligera a ZAP/Burp |
|
||||
| [flow-replay](flow-replay.md) | 3 | Guardar un flujo web (login, reiniciar server, formulario) como funcion reproducible: destila un HAR a call specs y lo reproduce sin navegador (HTTP puro), con fallback a chromium headless/visible. Consume las capturas de web-proxy |
|
||||
| [hoppscotch](hoppscotch.md) | 7 | Operar Hoppscotch SELF-HOSTED (docker en selfhost/) via API GraphQL: login (magic link headless via mailpit), CRUD de requests (create/update/delete/list), set_environment (idempotente, resuelve secretos pass:). El agente crea/edita y el humano lo ve en vivo en su GUI (subscriptions). build es helper interno de serializacion. Modo .json local ELIMINADO |
|
||||
| [metabase](metabase.md) | 106 | Operar Metabase via API REST: auth, cards, dashboards, collections, snippets, permissions |
|
||||
| [doctor](doctor.md) | 11 | Diagnostico read-only del registry: artefactos, servicios, drift, funciones huerfanas |
|
||||
| [notebook](notebook.md) | 5 | Operar Jupyter Lab colaborativo (discover/read/exec/write/kernel) |
|
||||
@@ -49,6 +50,9 @@ Indice de grupos de capacidades del registry. Cada grupo agrupa >=3 funciones qu
|
||||
| [mesh-3d](mesh-3d.md) | 3 | Carga y upload a GPU de meshes 3D (OBJ, GLB/glTF 2.0): loaders CPU + mesh_gpu_upload OpenGL |
|
||||
| [terminal-capture](terminal-capture.md) | 6 | Automatizar y capturar el texto de una CLI/TUI interactiva via PTY headless: spawn+input scripteado (one-shot y streaming), render del layout 2D (emulador VT), strip ANSI, delta por prefijo, y parseo de la TUI de claude a datos |
|
||||
| [claude-direct](claude-direct.md) | 3 | Hablar directamente con la API de Anthropic Messages usando el token OAuth de Claude Code (Claude Max): leer token, stream SSE, bucle agentico de tool-use |
|
||||
| [obsidian](obsidian.md) | 14 | CRUD headless de vaults y notas Obsidian como Markdown plano (frontmatter YAML + wikilinks): parse/format, read/create/update/delete/list/search notas, list/create vaults, slugify/embeds/resolve. Sin app GUI |
|
||||
| [osint-passive](osint-passive.md) | 8 | Recoleccion OSINT pasiva (fuentes publicas, no intrusiva): EXIF/PDF metadata, whois RDAP, DNS, subdominios crt.sh, guess emails, username enumeration, search dorks |
|
||||
| [osint-enrich](osint-enrich.md) | 3 | Orquestadores de enriquecimiento OSINT: componen osint-passive para aumentar datapoints de personas (emails/usernames/dorks), orgs (whois+dns+subdominios) y metadatos de attachments |
|
||||
|
||||
## Como anadir grupo
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Capability group: `hoppscotch`
|
||||
|
||||
Operar una instancia **self-hosted de Hoppscotch** (consola de APIs, alternativa open-source a
|
||||
Postman) desde el registry, vía su **API GraphQL**. El agente crea/edita requests, colecciones y
|
||||
environments por la API; el humano los ve **en vivo** en su GUI (subscriptions = hot-reload real).
|
||||
Las requests viven en la base de datos del self-host (Postgres), compartida entre el agente y la GUI.
|
||||
|
||||
Este es el **flujo canónico**. El antiguo modo "archivo `.json` local" (funciones
|
||||
`parse_*` / `run_*` / `add_hoppscotch_request`) **fue eliminado**: escribía un `.json` en disco que
|
||||
NO subía al workspace, así que el humano no lo veía en la GUI. No lo reintroduzcas.
|
||||
|
||||
## Stack self-host
|
||||
|
||||
Vive en `projects/web_scraping/hoppscotch/selfhost/` (docker compose: AIO + Postgres + mailpit).
|
||||
|
||||
| Servicio | URL | Para qué |
|
||||
|---|---|---|
|
||||
| App (cliente) | `http://localhost:3009` | la GUI donde el humano usa las colecciones (instalable como PWA) |
|
||||
| Admin dashboard | `http://localhost:3100` | gestión (usuarios, config) |
|
||||
| Backend GraphQL | `http://localhost:3170/graphql` | la API que usan las funciones |
|
||||
| Mailpit | `http://localhost:8025` | captura el magic link del login (SMTP de pruebas, sin correo real) |
|
||||
|
||||
Levantar: `cd selfhost && docker compose up -d`. Team de trabajo: **"registry"**. Cuenta: `admin@example.com`.
|
||||
|
||||
## Funciones
|
||||
|
||||
| ID | Firma corta | Qué hace |
|
||||
|---|---|---|
|
||||
| `hoppscotch_login_py_infra` | `(email, *, backend_url, mailpit_url) -> {access_token,...}` | login por magic link headless (lee el link de mailpit) → JWT |
|
||||
| `hoppscotch_create_request_py_infra` | `(collection_id, method, url, *, title, headers, body, body_type, team_id, access_token) -> dict` | crea una request en una colección de la team |
|
||||
| `hoppscotch_update_request_py_infra` | `(request_id, method, url, *, title, headers, body, body_type, access_token) -> dict` | actualiza una request |
|
||||
| `hoppscotch_delete_request_py_infra` | `(request_id, *, access_token) -> dict` | borra una request |
|
||||
| `hoppscotch_list_requests_py_infra` | `(collection_id, *, access_token) -> {requests:[...]}` | lista las requests de una colección |
|
||||
| `hoppscotch_set_environment_py_infra` | `(team_id, name, variables, *, access_token) -> dict` | crea/actualiza (idempotente) el environment de la team; resuelve secretos `pass:` |
|
||||
| `build_hoppscotch_collection_py_infra` | `(calls, *, name, request_names) -> dict` | **helper interno** de create/update: serializa call specs al formato HoppRESTRequest. NO para escribir `.json` a mano |
|
||||
| `pass_get_secret_py_infra` | `(path, *, line) -> {value}` | lee un secreto de `pass` (lo consume `set_environment` para no hardcodear keys) |
|
||||
|
||||
`access_token` se pasa como **cookie**, no header `Authorization`. Caduca a 24h → re-login con `hoppscotch_login`.
|
||||
|
||||
## Ejemplo canónico (end-to-end)
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join(os.path.expanduser("~/fn_registry"), "python", "functions"))
|
||||
from infra.hoppscotch_login import hoppscotch_login
|
||||
from infra.hoppscotch_create_request import hoppscotch_create_request
|
||||
from infra.hoppscotch_set_environment import hoppscotch_set_environment
|
||||
|
||||
TEAM = "cmq8kn0v500030xls1nvminjy" # team "registry"
|
||||
COLL = "cmq8knppc00040xlskt4ist27" # colección registry_api (de hoppscotch_list/DB)
|
||||
|
||||
tok = hoppscotch_login("admin@example.com")["access_token"]
|
||||
|
||||
# 1. Variables del workspace (secreto resuelto desde pass, no hardcodeado)
|
||||
hoppscotch_set_environment(TEAM, "registry", [
|
||||
{"key": "baseURL", "value": "https://registry.organic-machine.com", "secret": False},
|
||||
{"key": "api_key", "value": "pass:apis/registry", "secret": True}, # pass: -> pass_get_secret
|
||||
], access_token=tok)
|
||||
|
||||
# 2. Crear una request → aparece EN VIVO en la GUI del humano (subscriptions)
|
||||
hoppscotch_create_request(
|
||||
COLL, "GET", "<<baseURL>>/api/status",
|
||||
title="status", headers={"Accept": "application/json"},
|
||||
team_id=TEAM, access_token=tok,
|
||||
)
|
||||
```
|
||||
|
||||
## Fronteras (qué NO cubre)
|
||||
|
||||
- **No es modo archivo**: no escribe colecciones `.json` locales como fuente. Las requests viven en el
|
||||
Postgres del self-host. (Los `.json` en `collections/` son solo respaldo/semilla importable.)
|
||||
- **No automatiza la GUI**: opera por la API; la GUI la mira el humano.
|
||||
- **No gestiona usuarios/teams del dashboard**: eso es el admin dashboard (`:3100`).
|
||||
- **No ejecuta los scripts pre/post-request JS** de Hoppscotch.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `access_token` como **cookie** (`cookies={"access_token": tok}`), no `Authorization`. 24h de vida.
|
||||
- `createRequestInCollection` de esta instancia **exige `team_id`** en el input (no solo el collectionID).
|
||||
- Variables `<<var>>` se resuelven con el environment de la team (subscriptions las propagan a la GUI).
|
||||
- Secretos: usa `value="pass:<ruta>"` en `set_environment` → se resuelve de `pass`, nunca se hardcodea
|
||||
ni se logea en crudo.
|
||||
- El secreto viaja en claro al backend local por GraphQL — es local (`127.0.0.1`), aceptable.
|
||||
@@ -0,0 +1,80 @@
|
||||
# Capability: obsidian
|
||||
|
||||
CRUD headless de vaults y notas de Obsidian, tratadas como Markdown plano con frontmatter YAML y wikilinks `[[...]]`. NO depende de la app GUI de Obsidian ni de su URI scheme — manipula los archivos `.md` directamente en disco. Scriptable, rapido, con telemetria del registry.
|
||||
|
||||
Los vaults de Obsidian del usuario viven en `/home/enmanuel/Obsidian/` y estan enlazados como vaults del registry en el project `obsidian` (`projects/obsidian/vaults/`). Ver `projects/obsidian/project.md`.
|
||||
|
||||
## Funciones
|
||||
|
||||
| ID | Firma | Que hace |
|
||||
|---|---|---|
|
||||
| `parse_obsidian_frontmatter_py_obsidian` | `parse_obsidian_frontmatter(content: str) -> {"frontmatter": dict, "body": str}` | **Pure.** Separa el frontmatter YAML (bloque `---` inicial) del cuerpo. Si no hay frontmatter valido devuelve `{}` + el contenido completo. |
|
||||
| `extract_obsidian_wikilinks_py_obsidian` | `extract_obsidian_wikilinks(body: str) -> list` | **Pure.** Extrae los targets de los wikilinks `[[...]]` y embeds `![[...]]`. Normaliza `[[nota\|alias]]`, `[[nota#heading]]`, `[[nota#^block]]` -> `nota`. Dedup preservando orden. |
|
||||
| `format_obsidian_note_py_obsidian` | `format_obsidian_note(frontmatter: dict, body: str) -> str` | **Pure.** Inversa de parse: serializa frontmatter (YAML entre `---`) + body a una nota `.md` completa. |
|
||||
| `read_obsidian_note_py_obsidian` | `read_obsidian_note(path: str) -> dict` | Lee una nota: `{path, frontmatter, body, wikilinks, tags}`. Compone parse + extract. |
|
||||
| `create_obsidian_note_py_obsidian` | `create_obsidian_note(vault_dir, rel_path, body="", frontmatter=None, overwrite=False) -> str` | Crea nota nueva (crea dirs padre, añade `.md`). Error si existe y `overwrite=False`. |
|
||||
| `update_obsidian_note_py_obsidian` | `update_obsidian_note(path, body=None, set_frontmatter=None, append=None) -> str` | Edita nota existente: merge de frontmatter, reemplazo de body, o append al final. |
|
||||
| `delete_obsidian_note_py_obsidian` | `delete_obsidian_note(path: str) -> bool` | Borra una nota (solo archivo, nunca directorio). Error si no existe. |
|
||||
| `list_obsidian_notes_py_obsidian` | `list_obsidian_notes(vault_dir, subfolder="", tag="") -> list` | Lista paths de notas `.md` (recursivo). Excluye `.obsidian/` y `.trash/`. Filtro opcional por tag de frontmatter. |
|
||||
| `search_obsidian_notes_py_obsidian` | `search_obsidian_notes(vault_dir, query, in_body=True, in_frontmatter=True) -> list` | Busca substring (case-insensitive) en las notas. Devuelve `[{path, matches:[{line, text}]}]`. |
|
||||
| `list_obsidian_vaults_py_obsidian` | `list_obsidian_vaults(base_dir: str) -> list` | Lista los vaults (subdirs con `.obsidian/`) bajo `base_dir`. `[{name, path}]`. |
|
||||
| `create_obsidian_vault_py_obsidian` | `create_obsidian_vault(parent_dir, name) -> str` | Crea un vault nuevo: carpeta + `.obsidian/app.json` minimo. Error si ya existe. |
|
||||
|
||||
## Ejemplo canonico
|
||||
|
||||
Componer varias funciones del grupo se hace por heredoc importando del registry (las funciones se importan, no se reescriben):
|
||||
|
||||
```bash
|
||||
cd /home/enmanuel/fn_registry
|
||||
python/.venv/bin/python3 - <<'PYEOF'
|
||||
import sys
|
||||
sys.path.insert(0, "python/functions")
|
||||
from obsidian import (
|
||||
list_obsidian_vaults, list_obsidian_notes, search_obsidian_notes,
|
||||
create_obsidian_note, read_obsidian_note, update_obsidian_note, delete_obsidian_note,
|
||||
)
|
||||
|
||||
# 1. Descubrir vaults del usuario
|
||||
vaults = list_obsidian_vaults("/home/enmanuel/Obsidian")
|
||||
print("vaults:", [v["name"] for v in vaults])
|
||||
|
||||
# 2. Listar y buscar notas en un vault
|
||||
finanzas = "/home/enmanuel/Obsidian/Finanzas"
|
||||
print("notas:", len(list_obsidian_notes(finanzas)))
|
||||
print("hits:", [h["path"] for h in search_obsidian_notes(finanzas, "presupuesto")][:5])
|
||||
|
||||
# 3. CRUD de una nota (crear -> leer -> editar -> borrar)
|
||||
p = create_obsidian_note(finanzas, "inbox/idea_x", body="Primera linea",
|
||||
frontmatter={"tags": ["inbox"], "created": "2026-06-09"})
|
||||
note = read_obsidian_note(p)
|
||||
print("creada:", note["path"], note["frontmatter"], note["wikilinks"])
|
||||
update_obsidian_note(p, set_frontmatter={"status": "done"}, append="Ver [[Otra Nota]]")
|
||||
delete_obsidian_note(p)
|
||||
PYEOF
|
||||
```
|
||||
|
||||
Para una sola operacion con un id conocido, `fn run` tambien sirve:
|
||||
|
||||
```bash
|
||||
./fn run list_obsidian_vaults /home/enmanuel/Obsidian
|
||||
./fn run list_obsidian_notes /home/enmanuel/Obsidian/Finanzas
|
||||
```
|
||||
|
||||
## Cuando usar el grupo
|
||||
|
||||
- Crear/editar/leer notas de cualquier vault de Obsidian desde un agente o script, sin abrir la app.
|
||||
- Buscar o listar notas por contenido o tag (ingesta, migracion, reporting sobre el vault).
|
||||
- Crear vaults nuevos o inventariar los existentes.
|
||||
|
||||
## Fronteras (que NO cubre)
|
||||
|
||||
- **No habla con la app GUI** (no usa el URI scheme `obsidian://`, no abre notas en la interfaz, no dispara plugins). Si la app esta abierta, escribir en disco puede chocar con sus locks/cache — cerrar la app o refrescar manualmente.
|
||||
- **No resuelve wikilinks a paths** automaticamente (devuelve los targets crudos). Resolver `[[nota]]` -> archivo real es responsabilidad del caller (busqueda por nombre en el vault).
|
||||
- **No renderiza Markdown** ni evalua Dataview/templating. Trata las notas como texto + frontmatter.
|
||||
- **No indexa el grafo** de enlaces entre notas (solo extrae links por nota). Para grafo agregado, componer sobre `list_obsidian_notes` + `extract_obsidian_wikilinks`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Vaults grandes son caros: `NotasDeObsidian` pesa ~554M. `list_obsidian_notes` / `search_obsidian_notes` recorren todo el arbol — filtra por `subfolder` cuando puedas.
|
||||
- `delete_obsidian_note` borra de verdad (no manda a `.trash/`). Para acciones destructivas masivas, listar primero y confirmar.
|
||||
- El frontmatter `tags` puede venir como lista o como CSV string; `read_obsidian_note` lo normaliza a lista.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Capability: osint-enrich
|
||||
|
||||
Orquestadores de enriquecimiento OSINT: componen las funciones atómicas de
|
||||
[osint-passive](osint-passive.md) para aumentar los datapoints de una entidad (persona u
|
||||
organización) del vault `osint` a partir de fuentes públicas. No tocan al objetivo de forma
|
||||
intrusiva. Mismo encuadre dual-use que `osint-passive`: solo investigación autorizada.
|
||||
|
||||
## Funciones
|
||||
|
||||
| ID | Firma | Qué hace |
|
||||
|---|---|---|
|
||||
| `scan_ficha_attachments_metadata_py_cybersecurity` | `scan_ficha_attachments_metadata(attachments_dir) -> dict` | Escanea los attachments de una ficha (imágenes + PDFs), extrae EXIF/PDF metadata y agrega GPS y fechas. |
|
||||
| `enrich_person_passive_py_cybersecurity` | `enrich_person_passive(nombre, apellidos, dominios=None, usernames=None) -> dict` | Candidatos para una persona: emails (guess), username hits, dorks. No verifica ni ejecuta. |
|
||||
| `enrich_org_passive_py_cybersecurity` | `enrich_org_passive(dominio) -> dict` | Perfil pasivo de una org: whois + dns + subdominios. Resiliente a fallo parcial (campo `errors`). |
|
||||
|
||||
## Ejemplo canónico
|
||||
|
||||
```bash
|
||||
cd /home/enmanuel/fn_registry
|
||||
python/.venv/bin/python3 - <<'PYEOF'
|
||||
import sys; sys.path.insert(0, "python/functions")
|
||||
from cybersecurity import (scan_ficha_attachments_metadata,
|
||||
enrich_person_passive, enrich_org_passive)
|
||||
|
||||
# 1. Metadatos de los documentos ya guardados de una persona (datos propios)
|
||||
m = scan_ficha_attachments_metadata(
|
||||
"/home/enmanuel/Obsidian/osint/attachments/personas/enmanuel-gutierrez-perez")
|
||||
print(m["summary"]) # {n_files, n_images, n_pdfs, n_gps_points, n_dates, errors}
|
||||
|
||||
# 2. Candidatos de enriquecimiento de una persona (no toca al objetivo)
|
||||
p = enrich_person_passive("Enmanuel", "Gutierrez Perez",
|
||||
dominios=["gmail.com"], usernames=["enmanuelgp"])
|
||||
print(p["email_candidates"][:5], len(p["dorks"]))
|
||||
|
||||
# 3. Perfil pasivo de una organización por su dominio
|
||||
o = enrich_org_passive("organic-machine.com")
|
||||
print(o["whois"].get("registrar"), o["dns"].get("A"), len(o["subdomains"]), o["errors"])
|
||||
PYEOF
|
||||
```
|
||||
|
||||
## Fronteras
|
||||
|
||||
- Compone solo funciones `osint-passive`. Para activa (port scan, fingerprint) haría falta
|
||||
`osint-active` (no construido).
|
||||
- Devuelve candidatos/datos crudos; **decidir qué escribir en la ficha** (y verificar) es del
|
||||
caller. Encaja con el reporte de `projects/osint/tools/person_datapoints.py`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `enrich_org_passive` nunca peta por una fuente lenta (crt.sh): el fallo va a `errors`.
|
||||
- `enrich_person_passive` puede tardar por `enumerate_username_sites` (un request por sitio).
|
||||
@@ -0,0 +1,64 @@
|
||||
# Capability: osint-passive
|
||||
|
||||
Recolección OSINT **pasiva**: obtener información sin interactuar de forma intrusiva con el
|
||||
objetivo, usando solo fuentes públicas (DNS público, RDAP, Certificate Transparency, metadatos
|
||||
de documentos propios, servicios de perfil públicos). Pensado para investigación autorizada,
|
||||
due diligence, pentest con permiso y enriquecimiento de las fichas del vault `osint`.
|
||||
|
||||
**Encuadre:** dual-use. Úsese solo contra objetivos propios o con autorización. Las funciones
|
||||
que tocan servicios públicos (`enumerate_username_sites`, `enum_subdomains_crtsh`) dejan una
|
||||
huella mínima (un request a cada servicio); respeta sus rate limits.
|
||||
|
||||
## Funciones
|
||||
|
||||
| ID | Firma | Qué hace |
|
||||
|---|---|---|
|
||||
| `extract_exif_metadata_py_cybersecurity` | `extract_exif_metadata(image_path) -> dict` | EXIF de una imagen (fecha, cámara, software, GPS decimal) vía Pillow. |
|
||||
| `extract_pdf_metadata_py_cybersecurity` | `extract_pdf_metadata(pdf_path) -> dict` | Document Info de un PDF (autor, fechas, software, páginas) vía pypdf. |
|
||||
| `guess_email_formats_py_cybersecurity` | `guess_email_formats(nombre, apellidos, dominio) -> list` | **Pure.** Candidatos de email comunes a partir de nombre + dominio. |
|
||||
| `enumerate_username_sites_py_cybersecurity` | `enumerate_username_sites(username, ...) -> list` | ¿Existe un username en ~12 redes públicas? (sherlock ligero, por código HTTP). |
|
||||
| `build_search_dorks_py_cybersecurity` | `build_search_dorks(target, tipo, ...) -> list` | **Pure.** Genera dorks de motor de búsqueda (persona/email/dominio/usuario). |
|
||||
| `dns_records_py_cybersecurity` | `dns_records(dominio, types=None) -> dict` | Registros DNS (A/AAAA/MX/TXT/NS/CNAME) vía `dig`. |
|
||||
| `whois_lookup_py_cybersecurity` | `whois_lookup(dominio, ...) -> dict` | Datos de registro vía RDAP (WHOIS moderno HTTP/JSON, sin CLI). |
|
||||
| `enum_subdomains_crtsh_py_cybersecurity` | `enum_subdomains_crtsh(dominio, ...) -> list` | Subdominios desde Certificate Transparency (crt.sh). |
|
||||
|
||||
Orquestadores (grupo [osint-enrich](osint-enrich.md)): `scan_ficha_attachments_metadata`,
|
||||
`enrich_person_passive`, `enrich_org_passive`.
|
||||
|
||||
## Ejemplo canónico
|
||||
|
||||
```bash
|
||||
cd /home/enmanuel/fn_registry
|
||||
python/.venv/bin/python3 - <<'PYEOF'
|
||||
import sys; sys.path.insert(0, "python/functions")
|
||||
from cybersecurity import (dns_records, whois_lookup, enum_subdomains_crtsh,
|
||||
guess_email_formats, build_search_dorks, extract_exif_metadata)
|
||||
|
||||
# Dominio (org)
|
||||
print(whois_lookup("organic-machine.com")["registrar"]) # OVH sas
|
||||
print(dns_records("organic-machine.com")["A"]) # ['135.125.201.30']
|
||||
print(enum_subdomains_crtsh("organic-machine.com")[:5])
|
||||
|
||||
# Persona
|
||||
print(guess_email_formats("Enmanuel", "Gutierrez Perez", "gmail.com")[:5])
|
||||
print(build_search_dorks("Enmanuel Gutierrez Perez", "persona")[:3])
|
||||
|
||||
# Metadatos de un documento propio
|
||||
print(extract_exif_metadata("/home/enmanuel/Obsidian/osint/attachments/personas/enmanuel-gutierrez-perez/dni-1.jpg"))
|
||||
PYEOF
|
||||
```
|
||||
|
||||
## Fronteras (qué NO es)
|
||||
|
||||
- **No es recolección activa**: no hace port scan, dns brute, ni sondea la infra del objetivo.
|
||||
Eso sería el grupo `osint-active` (no construido todavía).
|
||||
- **No verifica** los candidatos: `guess_email_formats` propone, no confirma que el email exista.
|
||||
- **No ejecuta** los dorks: `build_search_dorks` los genera; ejecutarlos es otro paso (browser).
|
||||
- **No incluye breach/leak lookup** (HIBP requiere API key de pago) — pendiente.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `crt.sh` va lento / rate-limitado y a veces responde 404; los orquestadores lo capturan en
|
||||
`errors` y siguen.
|
||||
- `enumerate_username_sites` da falsos positivos/negativos por anti-bot de algunos sitios.
|
||||
- El GPS de EXIF revela ubicación — dato sensible; trátese como PII.
|
||||
Reference in New Issue
Block a user