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 quería mejorar la experiencia y simplificar el proceso.

Sin clsniff, primero tenía que levantar el contenedor de mitmproxy:

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 web_password=P@ssw0rd `
  --scripts /home/mitmproxy/.mitmproxy/logger.py
import json
import datetime
from mitmproxy import http

HOSTS = [
    "anthropic.com",
    "claude.ai",
    "figma.com"
]

# https://docs.mitmproxy.org/stable/api/events.html#HTTPEvents.response
def response(flow: http.HTTPFlow):
    host = flow.request.host
    if not any(h in host for h in HOSTS):
        return
   
    try:
        req_body = flow.request.get_text()
    except:
        req_body = ""
   
    try:
        res_body = flow.response.get_text()
    except:
        res_body = ""

    now = datetime.datetime.now(datetime.timezone.utc)
    record = {
        "timestamp": now.isoformat(),
        "host": host,
        "path": flow.request.path,
        "method": flow.request.method,
        "request_headers": dict(flow.request.headers),
        "request": req_body,
        "status": flow.response.status_code,
        "response_headers": dict(flow.response.headers),
        "response": res_body,
    }

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

La primera vez había además que confiar en el certificado mitmproxy-ca-cert.cer que se generó.

Después y por cada sesión del terminal, añadir las variables de entorno para que el tráfico pasase 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 usa mitmdump (el modo headless de mitmproxy) y hace que todo el tráfico del proceso hijo pase por él. El proceso envuelto no se entera de nada: su stdin, stdout y stderr se redirigen directamente al terminal.

Para usarlo bastaría con lo siguiente:

npx -y clsniff@latest claude

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 --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": "***",
      "content-type": "application/json"
    },
    "body": "..."
  },
  "response": {
    "statusCode": 200,
    "headers": { ... },
    "body": "..."
  }
}

Y para que sea más sencillo analizar el tráfico, hay una awesómica opción --viewer que levanta una interfaz web para visualizar las peticiones y respuestas de forma amigable (incluido una pestaña Claude que se activa cuando las peticiones son a https://api.anthropic.com/v1/messages).

npx -y clsniff@latest --viewer claude

clsniff gif

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

Por último, si pasa que algun programa no funciona bien con clsniff, podríamos rescatar la idea original de usar mitmproxy con Docker, pero con el logger personalizado de clsniff. Así, podríamos levantar mitmproxy en Docker, capturar tráfico y después (o durante) levantar clsniff con la opción --viewer para analizarlo.

El certificado que creará mitmproxy en el contenedor se guardará en la carpeta data de nuestro host y hay que confiar en él.

Invoke-WebRequest -Uri "https://raw.githubusercontent.com/panicoenlaxbox/clsniff/main/logger.py" -OutFile "$PWD\data\logger.py"

docker run -d `
  -p 8080:8080 -p 8081:8081 `
  -v "${PWD}\data:/home/mitmproxy/.mitmproxy" `
  -v "${PWD}\logs:/logs" `
  -e CLSNIFF_SESSION_DIR=/logs/clsniff `
  mitmproxy/mitmproxy mitmweb `
  --listen-host 0.0.0.0 `
  --web-host 0.0.0.0 `
  --set web_password=P@ssw0rd `
  --scripts /home/mitmproxy/.mitmproxy/logger.py

npx -y clsniff@latest --viewer --output-dir "${PWD}\logs"

Un saludo!