8742cb25be
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
118 lines
6.9 KiB
Markdown
118 lines
6.9 KiB
Markdown
# Flow Replay — Guardar un flujo web como función reproducible
|
|
|
|
Tag: `flow-replay`. Grupo de funciones para convertir un flujo de navegador que se hizo
|
|
una vez a mano (login en un panel, reiniciar un servidor, rellenar un formulario) en una
|
|
**función del registry reproducible sin intervención**. Materializa la doctrina del issue
|
|
0087: el registry crece promoviendo secuencias repetidas a operaciones de un solo paso.
|
|
|
|
Filtro MCP: `mcp__registry__fn_search query="" tag="flow-replay"`.
|
|
|
|
Complementa al grupo [`web-proxy`](web-proxy.md): `web-proxy` **graba** el tráfico,
|
|
`flow-replay` lo **destila y reproduce**.
|
|
|
|
## El patrón: grabar → destilar → reproducir
|
|
|
|
Tres fases, con una jerarquía de reproducción de más barato a más caro:
|
|
|
|
```
|
|
Fase 0 — GRABAR (una vez, siempre con browser + proxy)
|
|
web_proxy ON → haces la acción a mano en el navegador → exportas el tramo a HAR
|
|
(funciones del grupo web-proxy: start_mitm_capture, launch_chromium_proxy, query_mitm_flows --har)
|
|
|
|
Fase 1 — DESTILAR (del HAR a una secuencia de requests)
|
|
har_filter_flows → descarta estáticos/analytics, deja los flujos que importan
|
|
har_extract_calls → normaliza cada flujo a una "call spec" reproducible (método, url,
|
|
headers, cookies, body), aislando los datos de auth
|
|
|
|
Fase 2 — REPRODUCIR, en orden de preferencia:
|
|
Nivel 1 HTTP puro http_replay_sequence — rápido, headless, scriptable. PREFERIDO.
|
|
Nivel 2 headless chromium (fallback) — cuando hay token dinámico firmado en cliente,
|
|
challenge JS o WAF con fingerprint de navegador. Reutiliza
|
|
cdp_extract_recipe + cdp_save_storage_state (ver Fronteras).
|
|
Nivel 3 chromium visible + acciones humanizadas — último recurso si headless es detectado
|
|
(cdp_click_xy_human, cdp_move_mouse_human del dominio browser).
|
|
```
|
|
|
|
La función-acción concreta que guardas en el registry (`reboot_<panel>_server`,
|
|
`login_<panel>`, etc.) envuelve el nivel que funcione: idealmente una llamada a
|
|
`http_replay_sequence` con su secuencia + parámetros, y los secretos resueltos desde
|
|
`pass`/vault.
|
|
|
|
## Funciones del grupo
|
|
|
|
| ID | Firma corta | Qué hace |
|
|
|---|---|---|
|
|
| [har_filter_flows_py_cybersecurity](../../python/functions/cybersecurity/har_filter_flows.md) | `har_filter_flows(har, *, hosts, methods, drop_static, drop_analytics) -> list[dict]` | Filtra un HAR: descarta recursos estáticos y hosts de telemetría, deja los flujos candidatos a "acción". Pura. |
|
|
| [har_extract_calls_py_cybersecurity](../../python/functions/cybersecurity/har_extract_calls.md) | `har_extract_calls(entries, *, drop_headers) -> list[dict]` | Convierte entries HAR en "call specs" normalizadas (método/url/headers/cookies/body/body_type), aislando cookies de auth y descartando headers hop-by-hop. Pura. |
|
|
| [http_replay_sequence_py_infra](../../python/functions/infra/http_replay_sequence.md) | `http_replay_sequence(calls, *, params, extract, timeout_s, verify_tls, allow_redirects, base_headers) -> dict` | Motor de replay HTTP: ejecuta la secuencia compartiendo cookie jar, substituye `{{param}}` y extrae valores de una respuesta para inyectarlos en pasos siguientes (flujo CSRF-like). Impura. |
|
|
|
|
## Ejemplo canónico end-to-end
|
|
|
|
Destilar un HAR capturado y reproducir el flujo sin navegador. Las tres funciones se
|
|
encadenan; la extracción del paso 1 (un token) se inyecta en el paso 2:
|
|
|
|
```python
|
|
import sys, os
|
|
sys.path.insert(0, os.path.join("python", "functions"))
|
|
from cybersecurity.har_filter_flows import har_filter_flows
|
|
from cybersecurity.har_extract_calls import har_extract_calls
|
|
from infra.http_replay_sequence import http_replay_sequence
|
|
|
|
# 1. HAR exportado por: query_mitm_flows ~/captures/traffic-*.mitm --har ~/sesion.har
|
|
import json
|
|
har = json.load(open(os.path.expanduser("~/sesion.har")))
|
|
|
|
# 2. Destilar: del ruido a la secuencia mínima
|
|
flows = har_filter_flows(har, hosts=["panel.midominio.com"]) # solo el host del panel
|
|
calls = har_extract_calls(flows) # call specs reproducibles
|
|
|
|
# 3. Reproducir (Nivel 1, HTTP puro). El token del GET inicial se inyecta en el POST.
|
|
res = http_replay_sequence(
|
|
calls,
|
|
params={"server_id": "vps-42"}, # parametrizado por el caller
|
|
extract=[{"from": 0, "type": "json", "expr": "csrf", "as": "csrf"}],
|
|
verify_tls=True,
|
|
)
|
|
print(res["status"], [s["status_code"] for s in res["steps"]])
|
|
```
|
|
|
|
Una vez validado, el flujo se promueve a una función-acción nombrada del registry
|
|
(p. ej. `reboot_vps_server_<panel>`) que internamente llama a `http_replay_sequence`
|
|
con su secuencia fija, recibe los parámetros del caller y resuelve los secretos desde
|
|
`pass`. Esa función-acción es lo que el agente invoca en un solo paso a partir de entonces.
|
|
|
|
## Fronteras
|
|
|
|
- **No graba**: la captura es del grupo [`web-proxy`](web-proxy.md). Este grupo empieza
|
|
con un HAR ya existente.
|
|
- **No auto-parametriza** (todavía). `har_extract_calls` normaliza pero NO detecta solo
|
|
qué valor es un token dinámico ni dónde se reinyecta. La parametrización (`{{param}}`)
|
|
y las reglas de `extract` las decide el humano/agente leyendo el HAR. La detección
|
|
automática de tokens/CSRF sería una función nueva del grupo, no una ampliación.
|
|
- **No incluye el runner de Nivel 2/3** (browser fallback). Está especificado en el
|
|
patrón pero no implementado: cuando un flujo real falle en HTTP puro, se construye un
|
|
"action recipe" reutilizando casi entero `cdp_extract_recipe_py_pipelines` (mismo
|
|
formato YAML, steps de acción en vez de extracción) + `cdp_save_storage_state_go_browser`
|
|
para saltarse el login. No se construye por adelantado (KISS / registry-first).
|
|
- **No gestiona secretos**: los secretos viajan como `{{param}}` desde `pass`/vault. El
|
|
grupo nunca los hardcodea ni los persiste.
|
|
|
|
## Gotchas (seguridad — leer antes de usar)
|
|
|
|
- **El HAR es sensible**: contiene cookies y tokens en crudo. Trátalo como un secreto —
|
|
gitignored, no subir a Gitea, no indexar, borrar tras destilar. El output de
|
|
`har_extract_calls` también lleva esos valores hasta que los sustituyes por `{{param}}`.
|
|
- **Secretos a `pass`/vault**, nunca en el código de la función-acción.
|
|
- **Replay con efectos = peligroso**: reproducir un POST que reinicia, borra o paga es
|
|
destructivo. La función-acción debe pedir confirmación o exponer un flag explícito
|
|
(`--yes`/`confirm=True`) antes de disparar. Nunca replay ciego de una acción irreversible.
|
|
- **HTTP puro no siempre reproduce**: token firmado en cliente, challenge JS, o WAF que
|
|
exige fingerprint de navegador → cae a Nivel 2 (headless) o 3 (visible humanizado).
|
|
- `http_replay_sequence` sigue redirects por defecto y `verify_tls=True`. La extracción
|
|
JSON es dot-path simple (`a.b.0.c`), no JSONPath completo.
|
|
|
|
## Prerequisitos
|
|
|
|
- Fase 0 (grabar): grupo `web-proxy` operativo (mitmproxy + chromium). Ver su página.
|
|
- Fase 1-2: `requests` en `python/.venv` (ya presente). Sin dependencias nuevas.
|