# 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__server`, `login_`, 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_`) 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.