clsniff, un sniffer para aplicaciones de consola

clsniff es una herramienta que “envuelve” cualquier comando de consola (y sí, también claude) e intercepta todo su tráfico HTTP/HTTPS, guardando cada petición y respuesta en un fichero JSON.

La idea es sencilla: saber exactamente qué está enviando y recibiendo una aplicación de consola cuando hace llamadas a una API. Depurar, entender el protocolo, ver los prompts que manda… por eso clsniff.

Y queda claro que este tema me gusta porque mi anterior post iba sobre algo parecido Sniffing HttpClient.

En mi búsqueda previa encontré herramientas que ya hacían algo parecido (https://github.com/dreampulse/claude-code-logger, https://github.com/seifghazi/claude-code-proxy, etc) pero no termino de confiar en un proxy de terceros para interceptar tráfico sensible… o no del todo, porque antes de tener clsniff estaba usando mitmproxy con un Docker y un logger personalizado, pero la experiencia no era tan fluida como me gustaría (por no decir que era un poco engorrosa).

Primero tenía que levantar el contenedor de mitmproxy con el logger personalizado:

Los siguientes comandos asumen estás posicionado en un directorio con subdirectorios data y logs y dentro de data un fichero logger.py.

docker run -it `
  -p 8080:8080 -p 8081:8081 `
  -v "${PWD}\data:/home/mitmproxy/.mitmproxy" `
  -v "${PWD}\logs:/logs" `
  mitmproxy/mitmproxy mitmweb `
  --listen-host 0.0.0.0 `
  --web-host 0.0.0.0 `
  --set block_global=false `
  --set stream_large_bodies=10m `
  --set web_password=P@ssw0rd `
  --scripts /home/mitmproxy/.mitmproxy/logger.py
import json
import datetime
from mitmproxy import http

MONITORED_HOSTS = [
    "anthropic.com",
    "claude.ai",
]

def response(flow: http.HTTPFlow):
    host = flow.request.host
    if not any(h in host for h in MONITORED_HOSTS):
        return
   
    try:
        req_body = flow.request.get_text()
    except:
        req_body = ""
   
    try:
        res_body = flow.response.get_text()
    except:
        res_body = ""

    record = {
        "timestamp": datetime.datetime.utcnow().isoformat(),
        "host": host,
        "path": flow.request.path,
        "method": flow.request.method,
        "status": flow.response.status_code,
        "request": req_body,
        "response": res_body,
    }

    date = datetime.date.today().strftime("%Y%m%d")
    with open(f"/logs/claude-{date}.jsonl", "a", encoding="utf-8") as f:
        f.write(json.dumps(record, ensure_ascii=False) + "\n")

Sólo la primera vez, había además que confiar en el certificado mitmproxy-ca-cert.cer que se genera.

Después y por cada sesión del terminal, añadir las variables de entorno para que el tráfico pase por el proxy:

$env:HTTP_PROXY="http://127.0.0.1:8080"
$env:HTTPS_PROXY="http://127.0.0.1:8080"
$env:NO_PROXY="localhost,127.0.0.1"
$env:NODE_EXTRA_CA_CERTS="$PWD\data\mitmproxy-ca-cert.pem"

Y entonces ya sí, abrir claude y esperar a que el logger hiciera su trabajo. Entiéndeme, no es complicado, pero tenía que tirar de chuleta para no olvidar ningún paso.

¡Y es por eso que nació clsniff!, como una herramienta para simplificar todo el proceso.

clsniff monta un proxy MITM (man-in-the-middle) en un puerto aleatorio e inyecta las variables de entorno necesarias en el proceso hijo (HTTP_PROXY, HTTPS_PROXY, NODE_EXTRA_CA_CERTS, REQUESTS_CA_BUNDLE, etc.) para que el tráfico pase por él. El proceso envuelto no se entera de nada: su stdin, stdout y stderr se redirigen directamente al terminal.

El certificado CA se genera automáticamente en el primer arranque, sin instalación manual.

Para usarlo bastaría con lo siguiente:

npx -y clsniff@latest claude

Es decir, pasamos del engorro a algo algo fácil, directo y sin complicaciones.

Personalmente, lo uso principalmente con claude y es por eso que es la aplicación que más he testeado. No obstante, se puedes usar con cualquier aplicación de consola que haga peticiones HTTP y respete las cabeceras de proxy.

npx -y clsniff@latest --merge-sse --mask-headers "authorization" claude

--mask-headers "authorization" redacta la cabecera con la API key antes de guardarla, para no tener claves en los logs.

Cada ejecución genera una carpeta con marca de tiempo y un fichero JSON por petición:

{
  "id": 1,
  "timestamp": "2026-04-08T07:23:11.842Z",
  "duration": 4821,
  "request": {
    "method": "POST",
    "url": "https://api.anthropic.com/v1/messages",
    "headers": {
      "authorization": "**REDACTED**",
      "content-type": "application/json"
    },
    "body": "..."
  },
  "response": {
    "statusCode": 200,
    "headers": { ... },
    "body": "..."
  }
}

Otras opciones útiles podrían ser:

  • --filter <regex> — registra solo las URLs que casen con el patrón
  • --exclude <regex> — excluye URLs del log
  • --output-dir <ruta> — cambia el directorio de salida

En cualquier caso, en el repositorio de GitHub está todo mejor explicado.

Un saludo!