feat(cybersecurity): auto-commit con 48 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 23:44:39 +02:00
parent efc9911925
commit 729921e16e
48 changed files with 3765 additions and 8 deletions
@@ -0,0 +1,95 @@
---
name: tee_anthropic_sse
kind: function
lang: py
domain: cybersecurity
version: "1.0.0"
purity: impure
signature: "class AnthropicSSETee — mitmproxy addon loaded via mitmdump -s"
description: "Addon de mitmproxy que intercepta el stream SSE de la API de Anthropic (/v1/messages) y emite cada evento significativo a stdout como NDJSON en tiempo real. Cada interaccion de la CLI claude dispara una o varias llamadas a /v1/messages; el addon las etiqueta con stream_id, model y has_tools para que el consumidor pueda distinguir la respuesta principal (claude-opus-X con tools) de las auxiliares (titulo/clasificador en haiku sin tools). Las funciones puras split_sse_events y event_to_ndjson son testeables sin mitmproxy."
tags: [web-proxy, claude, mitmproxy, sse, streaming, anthropic, cybersecurity]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["json", "os", "sys", "mitmproxy"]
tested: true
tests:
- "split buffer completo devuelve 8 bloques"
- "split bloques contienen event y data"
- "split buffer cortado preserva incompleto"
- "split resto mas continuacion reconstruye evento"
- "split buffer vacio"
- "split evento unico sin separador final"
- "text delta p"
- "text delta ong"
- "message stop con stop holder previo"
- "ping devuelve lista vacia"
- "content block start text devuelve vacio"
- "content block start tool use"
- "tool json delta"
- "json invalido en data devuelve vacio"
- "bloque sin data devuelve vacio"
- "integracion secuencia completa produce pong y stop"
- "integracion stream id se propaga"
- "integracion determinismo"
test_file_path: "python/functions/cybersecurity/tests/test_tee_anthropic_sse.py"
file_path: "python/functions/cybersecurity/tee_anthropic_sse.py"
params:
- name: mitmdump_invocation
desc: "No recibe argumentos directos. Se carga con `mitmdump -s tee_anthropic_sse.py`. El puerto del proxy se controla con el flag -p de mitmdump (ej. -p 8901). La flag -q suprime el log de mitmdump en stderr dejando solo el NDJSON en stdout."
- name: FN_WIRE_ONLY_TOOLS
desc: "Variable de entorno opcional. Si vale '1', suprime los streams cuyo request body no incluye el array 'tools' (llamadas auxiliares de titulo/clasificador que usan haiku). Por defecto (sin la env) emite todos los streams etiquetados con stream_id, model y has_tools para que el consumidor filtre."
output: "NDJSON a stdout, un objeto JSON por linea (flush inmediato). Tipos de linea: message_start{stream_id,model,has_tools} al inicio de cada stream; text_delta{stream_id,text} por cada fragmento de texto del modelo; tool_use_start{stream_id,tool_name,tool_id} cuando el modelo inicia una herramienta; tool_json_delta{stream_id,partial_json} por cada fragmento de argumentos JSON de la herramienta; message_stop{stream_id,stop_reason} al finalizar el stream. stderr recibe solo mensajes de diagnóstico del addon (errores, warnings), nunca NDJSON."
---
## Ejemplo
```bash
# Terminal 1: lanzar mitmproxy como proxy de interceptacion
# -q suprime el log de mitmdump; solo se ve el NDJSON en stdout
mitmdump -p 8901 \
-s python/functions/cybersecurity/tee_anthropic_sse.py \
-q
# Terminal 2: lanzar claude por el proxy
# NODE_EXTRA_CA_CERTS hace que el runtime Node de claude confie en la CA de mitmproxy
export HTTPS_PROXY=http://127.0.0.1:8901
export NODE_EXTRA_CA_CERTS="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"
claude -p "di hola"
# Salida en stdout de mitmdump (Terminal 1):
# {"type": "message_start", "stream_id": 1, "model": "claude-haiku-4-5", "has_tools": false}
# {"type": "text_delta", "stream_id": 1, "text": "H"}
# {"type": "text_delta", "stream_id": 1, "text": "ola"}
# {"type": "message_stop", "stream_id": 1, "stop_reason": "end_turn"}
# ...
# {"type": "message_start", "stream_id": 2, "model": "claude-opus-4-8", "has_tools": true}
# {"type": "text_delta", "stream_id": 2, "text": "Hola"}
# ...
# Filtrar solo la respuesta principal (has_tools=true) con jq:
mitmdump -p 8901 -s python/functions/cybersecurity/tee_anthropic_sse.py -q \
| jq -c 'select(.has_tools == true or .stream_id != null and (.type == "text_delta"))'
# O usar la variable de entorno para que el addon ya filtre en origen:
FN_WIRE_ONLY_TOOLS=1 mitmdump -p 8901 \
-s python/functions/cybersecurity/tee_anthropic_sse.py -q
```
## Cuando usarla
Cuando quieras el texto exacto que el modelo genera en tiempo real desde una sesion claude (TUI interactiva o `claude -p`), interceptando la red, sin parsear el render de la terminal ni depender de warmup/idle de la TUI. Util para: capturar la salida del modelo para procesado downstream (logging estructurado, metricas de tokens, replay), observar tool_use en construccion (argumentos parciales), o depurar la diferencia entre streams principales y auxiliares en una misma sesion TUI.
## Gotchas
- **Descompresion via strip de Accept-Encoding**: el hook `request` elimina el header `Accept-Encoding` de las llamadas a `/v1/messages` para que la API responda con el SSE SIN comprimir. Esto es obligatorio: el modo streaming de mitmproxy (`flow.response.stream`) entrega al tee los bytes CRUDOS del cuerpo, que si vinieran con `Content-Encoding: gzip`/`br` nunca contendrian el delimitador `\n\n` de eventos SSE (se veria binario) y no se emitiria ningun delta. Verificado empiricamente el 2026-06-04: sin el strip, solo se emitia `message_start`; con el strip, los `text_delta` salen correctamente. La alternativa (un decompresor de streaming con estado por flujo) es mas fragil. El coste es unos bytes extra en el salto local, irrelevante.
- **NO usar `--set stream_large_bodies`**: el modo streaming se activa con `flow.response.stream = func` en `responseheaders`, sin necesidad de ese flag. Ademas `stream_large_bodies=N` bajo rompe el acceso a `flow.request.content` (necesario para `has_tools`), porque tambien streamea el cuerpo del request y deja de bufferearlo.
- **Requiere mitmproxy + CA confiada por claude**: la CA de mitmproxy (`~/.mitmproxy/mitmproxy-ca-cert.pem`) debe estar configurada en `NODE_EXTRA_CA_CERTS` para que el runtime Node de la CLI claude acepte el certificado MITM. Sin esto, claude rechaza la conexion con error de TLS. Instalar mitmproxy: `uv tool install mitmproxy` o `pip install mitmproxy`. claude tambien respeta `HTTPS_PROXY` para enrutar su trafico por el proxy.
- **Una interaccion TUI dispara varias /v1/messages**: la respuesta real del usuario usa el modelo principal (p.ej. claude-opus-4-8) y su request body incluye el array `tools` con las herramientas de Claude Code. Las llamadas auxiliares (generador de titulo, clasificador) usan claude-haiku y su request NO lleva `tools`. Usa `has_tools=true` o `FN_WIRE_ONLY_TOOLS=1` para aislar la respuesta principal y no mezclar streams.
- **Solo funciona mientras claude no haga TLS pinning**: hoy (2026-06-04) la CLI claude no hace certificate pinning, por lo que el MITM funciona con `NODE_EXTRA_CA_CERTS`. Si una version futura de claude añade pinning, el addon dejara de interceptar.
- **Es trafico de tu propia cuenta y maquina**: el addon captura unicamente el trafico local que tu proxy intercepta. No hay acceso a otras cuentas ni sesiones remotas. El NDJSON se emite solo a stdout local.
- **El endpoint puede cambiar**: la CLI claude hoy usa `POST /v1/messages?beta=true`. El addon filtra por prefix `/v1/messages` para tolerar variantes de query string, pero si Anthropic cambia la ruta base en versiones futuras del protocolo, actualizar el check en `responseheaders`.
- **Chunks parciales**: el addon mantiene un buffer por stream para manejar eventos SSE partidos entre chunks TCP. Si mitmdump se mata con SIGKILL durante un stream activo, el ultimo bloque incompleto del buffer se descarta (no se emite un message_stop artificial).
- **stdout debe ser exclusivamente NDJSON**: no añadir prints de debug a stdout; redirigir diagnosticos a stderr. Si se canaliza la salida a `jq` u otro parser, cualquier linea no-JSON rompe el pipeline.