Por qué nuestros navegadores de agentes firman cada petición: Web Bot Auth en la práctica
Nuestros navegadores de agentes no se hacen pasar por personas: cada petición lleva una firma Web Bot Auth que el sitio puede comprobar. Cómo funciona, qué ve nuestro borde, dos huecos que encontramos en casa y lo que una firma no puede decirte.
Por el equipo de seguridad de OpenWAF
En resumen. Cada navegador que ejecuta OpenWAF Browsers firma sus peticiones con Web Bot Auth por defecto. Un sitio puede comprobar con una clave pública que le visita un agente y qué servicio lo ejecuta, en lugar de adivinarlo por el User-Agent.
- No vendemos el otro camino: ni parches de sigilo, ni proxies residenciales, ni resolución de CAPTCHAs.
- Al escribir este artículo encontramos dos huecos en nuestra propia casa. Nuestro borde no verificaba la forma de diccionario de
Signature-Agentque el último borrador pide a los firmantes, y no reconocía a nuestros propios navegadores. Los dos están arreglados. - Hasta el 30 de octubre una sola clave firmaba por todos los clientes. Ahora cada organización firma con su propia clave en el mismo directorio. Qué cambia.
- Una firma dice quién ejecuta el agente, no qué le permitió hacer el usuario. Para eso usamos permisos acotados y la aprobación de la acción exacta.
Declarado o disfrazado
Un agente que navega por la web tiene dos opciones. Puede hacerse pasar por una persona: parchear el navegador para que no se note la automatización, alquilar direcciones residenciales, pagar a un servicio que resuelva CAPTCHAs. O puede decir lo que es y dejar que cada sitio decida.
El primer camino es una carrera armamentística y cada vez sale más caro. Lo vemos desde el lado de quien defiende. En una ronda reciente de equipo rojo contra nuestro propio reto, OpenWAF Verify, un script que ejecutaba el código del reto en un DOM simulado pasó la casilla. Una comprobación de layout lo paró, y un navegador sin interfaz y sin entrada real del usuario ahora se queda en la casilla. Cada ronda le cuesta más trabajo al atacante, y la siguiente le costará más todavía. Contamos el panorama general en lo que los navegadores de sigilo ocultan y lo que no.
El segundo camino es barato y estable, siempre que el sitio pueda fiarse de la declaración. Un User-Agent no basta, porque cualquiera puede copiarlo. En openwaf.dev, entre el 27 y el 30 de octubre, nuestro borde vio peticiones que se presentaban como Twitterbot, PerplexityBot o Claude-User pero llegaban desde fuera de las redes conocidas de esos operadores. Una firma sí basta.
Cómo funciona Web Bot Auth
Web Bot Auth es un borrador de un grupo de trabajo del IETF, draft-ietf-webbotauth-httpsig-protocol, construido sobre HTTP Message Signatures (RFC 9421). Tiene tres piezas:
- Un directorio de claves. El operador publica sus claves públicas Ed25519 como un JSON Web Key Set en
/.well-known/http-message-signatures-directory. El nuestro está enhttps://browsers.openwaf.dev/.well-known/http-message-signatures-directory, y su respuesta va firmada a su vez con cada clave que lista. - Signature-Agent. Cada petición nombra la URL donde están las claves. Esa URL, cuando el verificador la ha descargado y ha encontrado la clave, es la identidad del agente.
- La firma.
Signature-Inputdice qué se firma: aquí el host de la petición (@authority) y la cabeceraSignature-Agent, con la hora de creación, una caducidad de 5 minutos, un nonce, el identificador de la clave y la etiquetaweb-bot-auth.Signaturelleva la firma Ed25519.
Esta es una petición real de uno de nuestros navegadores, tal como la recibió httpbin.org el 30 de octubre:
Signature-Agent: "https://browsers.openwaf.dev"
Signature-Input: sig1=("@authority" "signature-agent");created=1793349067;keyid="3Fv4pDBAod1k9Ru7dIA3ybaqELttB6XvGgaBod4pC0g";alg="ed25519";expires=1793349367;nonce="pkllfk2djUTdpjAU/KKSiYZnTzIe16BKU7iKhM86vkI=";tag="web-bot-auth"
Signature: sig1=:GMouYptYOoeo54tr6UIJpJ9INrt+64vqQoTMBY6wbKyApkCJAsdYmSN8NSRDQnCeZuAuc56i2ZAY6L66WQxPCQ==:
Las cabeceras propias de Chrome y su User-Agent no cambian. La firma se añade dentro del navegador, así que cubre también las peticiones HTTPS.
Comprobarla son unas pocas líneas. Esta función de Node trata exactamente la forma de arriba; una biblioteca trata además otros componentes, la forma nueva de la cabecera y el registro de nonces:
import { createPublicKey, verify } from "node:crypto";
// keys: el array "keys" del directorio del agente (descárgalo una vez y guárdalo en caché)
export function verifyWebBotAuth(req, keys, now = Date.now() / 1000) {
const agent = req.headers["signature-agent"]; // "https://browsers.openwaf.dev", con las comillas
const [, label, params] = req.headers["signature-input"]?.match(/^([a-z0-9_.*-]+)=(.*)$/) ?? [];
if (!params?.startsWith('("@authority" "signature-agent");')) return false;
const p = Object.fromEntries([...params.matchAll(/;(\w+)=("?)([^;"]*)\2/g)].map((m) => [m[1], m[3]]));
if (p.tag !== "web-bot-auth" || p.alg !== "ed25519" || +p.expires < now || +p.created > now + 60) return false;
const jwk = keys.find((k) => k.kid === p.keyid);
const sig = req.headers.signature?.match(new RegExp(`^${label}=:([A-Za-z0-9+/=]+):$`))?.[1];
if (!jwk || !sig) return false;
const base = `"@authority": ${req.headers.host.toLowerCase()}\n"signature-agent": ${agent}\n"@signature-params": ${params}`;
return verify(null, Buffer.from(base), createPublicKey({ key: jwk, format: "jwk" }), Buffer.from(sig, "base64"));
}
La probamos con la petición de arriba y la clave publicada: verifica dentro de los cinco minutos de la firma y falla después o con cualquier otro host. host tiene que ser el host que pidió el cliente, sin puerto por defecto; detrás de un proxy que lo reescribe, usa el original.
Lo que ve un sitio
openwaf.dev está detrás de nuestro propio borde, que clasifica cada petición antes de que llegue al sitio. Del 27 al 30 de octubre clasificó 23 643 peticiones:
| Clase | Peticiones |
|---|---|
| Bots conocidos, verificados por rango de IP, DNS inverso o firma (Googlebot, ClaudeBot, GPTBot, OAI-SearchBot, ChatGPT-User y otros) | 904 |
| El nombre de un bot conocido desde fuera de las redes conocidas de su operador (Twitterbot 6, PerplexityBot 1, Claude-User 1) | 8 |
| Automatización sin verificar, en su mayoría nuestra propia monitorización y herramientas | 11 253 |
| Navegadores que parecen personas | 11 478 |
Solo tres de esas peticiones eran de un operador que firma: ChatGPT-User, verificado por los rangos publicados de OpenAI y por Web Bot Auth. Firmar todavía es raro, y por eso un sitio sigue necesitando las demás comprobaciones.
El mismo informe nos enseñó un hueco propio. El catálogo de bots de nuestro borde incluía los directorios Web Bot Auth de ChatGPT y Browserbase, pero no el nuestro. Hasta el 30 de octubre, una petición firmada de OpenWAF Browsers llegaba a un sitio detrás de nuestro borde como la firma de una clave desconocida y contaba como automatización sin verificar. Añadimos nuestro directorio al catálogo, con un test hecho a partir de una petición real capturada. Después abrimos una sesión de navegador real contra openwaf.dev: el informe mostró sus 6 peticiones como OpenWAF Browsers, todas verificadas por Web Bot Auth.
La trampa de la interoperabilidad
La cabecera tiene dos formas. El borrador que documenta Cloudflare, y que envían hoy la mayoría de los firmantes, define Signature-Agent como una cadena entre comillas, firmada como el componente "signature-agent". Los borradores posteriores la cambiaron a un diccionario con la etiqueta de la firma como clave, firmado como un solo miembro:
Signature-Agent: sig1="https://browsers.openwaf.dev"
Signature-Input: sig1=("@authority" "signature-agent";key="sig1");…
El borrador actual, el -01 del 29 de octubre de 2026, dice que los firmantes deben enviar la forma de diccionario y que los verificadores pueden seguir aceptando la cadena. La documentación de Web Bot Auth de Cloudflare (actualizada el 8 de octubre de 2026) dice que su verificador implementa la forma de cadena y que no verifica un mensaje que use el diccionario o el parámetro de componente key. Hasta esta semana, el nuestro tampoco.
Lo que hicimos:
- Nuestro borde acepta ya las dos formas. Admite el parámetro
key(RFC 9421 §2.1.2) y atribuye una petición solo al miembro que cubre su firma. Los miembros sin firmar, los que llevan parámetros y unkeyque apunta a un miembro que no existe se ignoran o se rechazan. El vector de prueba del propio borrador forma parte de los tests. El cambio está desplegado en todos los nodos del borde. - Nuestros navegadores siguen enviando la cadena. Un sitio detrás de Cloudflare es el verificador con el que más probablemente se cruzarán los agentes de nuestros clientes, y una firma que falla ahí no ayuda a nadie. Cambiaremos cuando Cloudflare documente que admite el diccionario.
Si escribes un verificador, acepta las dos. Se distinguen fácilmente: una cadena empieza por comillas dobles y la clave de un diccionario no.
Una clave por organización
Hasta el 30 de octubre, todas las sesiones de OpenWAF Browsers, las iniciara el cliente que las iniciara, firmaban con la misma clave y la misma Signature-Agent; la petición capturada de arriba es una de ellas. Un sitio que confiaba en la firma confiaba en todo el servicio. No podía distinguir a los clientes, así que si uno abusaba de los navegadores, un sitio podía bloquearlos a todos.
Desde el 30 de octubre cada organización firma con su propia clave Ed25519, en el mismo directorio y con la misma Signature-Agent. El keyid de Signature-Input distingue los navegadores de una organización de los de otra sin nombrarla, así que un sitio puede limitar o bloquear a un cliente por su identificador de clave, y un aviso de abuso se puede atribuir a una organización. Una organización recién aprobada firma con la clave compartida unos diez minutos, mientras se publica la suya. Retirar el acceso a una organización quita su clave del directorio, y nuestro propio borde deja de aceptarla en menos de un minuto: en nuestra prueba, una petición firmada con la clave antes de retirarla se rechazó 51 segundos después.
Los controles de nuestro lado se mantienen:
- El acceso se aprueba por organización. Los navegadores están apagados hasta que los activamos para una organización.
- Cuotas. 20 horas de navegador y 5 GB de salida al mes por defecto, 2 navegadores a la vez por clave y 60 minutos por sesión.
- Un interruptor por clave y uno general. Desactivar una clave detiene sus navegadores y corta su salida en la siguiente petición; el interruptor general hace lo mismo con todo el servicio.
- Un registro de salida de cada petición (host, decisión, bytes y hora) para atender avisos de abuso, y una política de uso aceptable que prohíbe hacer pasar al agente por una persona, con firma o sin ella.
El borrador permite además otros dos diseños: una URL de directorio distinta por cliente, porque el identificador es la URL que resuelve el verificador, y dos firmas en la misma petición, en las que el agente del cliente firma con su propia clave y el navegador remoto firma también (apéndice D.2). No hemos construido ninguno de los dos.
Lo que una firma no resuelve
Una firma válida demuestra que quien tiene una clave publicada en esa URL firmó la petición. El propio borrador lo deja claro: no dice nada de si el agente es benigno ni de si la petición está autorizada. Autentica al agente, no la intención detrás de cada petición.
Esa pregunta salió en una conversación pública sobre agentes de navegador con IA que se hacen pasar por humanos, y creemos que se divide en tres partes:
- Quién ejecuta el agente. La firma lo responde, para el sitio.
- Qué le permitió hacer el usuario. Un permiso del usuario: acotado a ciertos sitios y acciones, y revocable.
- Si el usuario quiere esta acción concreta. Una confirmación de esa acción, no de una descripción vaga de ella.
OpenWAF Tasks implementa la última parte. Una tarea navega en solo lectura; cuando quiere escribir, propone una lista de pasos y una persona aprueba exactamente esos pasos. La aprobación lleva el SHA-256 de los pasos, así que si algo cambió después de que la persona lo viera, la aprobación se rechaza, y el ejecutor repite él mismo los pasos aprobados, sin el modelo. La documentación de aprobaciones describe todo el flujo.
Cómo empezar
- Si tienes un sitio: la página de Web Bot Auth enseña lo que envían nuestros navegadores y cómo verificarlo. Detrás de OpenWAF, el control de rastreadores IA ya lo hace, y nuestros navegadores aparecen como
openwaf-browsersen los informes y en las excepciones por bot. - Si construyes agentes: la guía rápida arranca un navegador en cinco minutos, y lo que no es explica lo que no haremos y por qué.
Preguntas frecuentes
¿Qué es Web Bot Auth?
Un borrador del IETF que permite a un bot o agente firmar cada petición HTTP con una clave que publica, usando HTTP Message Signatures (RFC 9421). El sitio comprueba la firma con el directorio de claves que nombra la cabecera Signature-Agent, en lugar de fiarse del User-Agent.
¿Puedo desactivar la firma en OpenWAF Browsers?
Sí, por navegador o como opción por defecto de la organización, aunque recomendamos dejarla activada. Desactivarla no vuelve sigiloso al navegador: sigue siendo Chrome tal cual, desde las direcciones de OpenWAF.
¿Qué forma de Signature-Agent debe aceptar un verificador?
Las dos. El borrador actual pide a los firmantes la forma de diccionario, mientras que el verificador de Cloudflare y la mayoría de los firmantes usan la cadena entre comillas. Nuestro borde acepta las dos; nuestros navegadores envían la cadena hasta que Cloudflare documente que admite el diccionario.
¿Una firma válida significa que la petición está autorizada?
No. Dice qué servicio ejecuta el agente. Lo que el usuario le permitió hacer necesita un permiso acotado y revocable, y una escritura puede necesitar que una persona apruebe la acción exacta, como en OpenWAF Tasks.
¿Puede un sitio distinguir a los clientes de OpenWAF?
Sí, por el keyid. Desde el 30 de octubre cada organización firma con su propia clave en el mismo directorio y con la misma Signature-Agent, así que un sitio puede limitar o bloquear a una organización sin afectar a las demás. Antes, una sola clave firmaba por todos los clientes.
Fuentes
- HTTP Message Signatures for automated traffic (draft-ietf-webbotauth-httpsig-protocol-01) IETF, 29 oct 2026
- RFC 9421: HTTP Message Signatures IETF
- Web Bot Auth Cloudflare, 8 oct 2026
- OpenWAF Browsers: Web Bot Auth OpenWAF
- OpenWAF Browsers: what it is not OpenWAF
- OpenWAF Browsers: limits and quotas OpenWAF
- OpenWAF Tasks: approvals OpenWAF
- AI Crawl Control: bot catalog OpenWAF
Temas
- Web Bot Auth
- HTTP Message Signatures
- RFC 9421
- Signature-Agent
- AI agents
- agent browsers
- bot verification
- Ed25519
- OpenWAF Browsers
- OpenWAF Tasks
- AI Crawl Control
- Cloudflare
Autor
OpenWAF