La puntuación de riesgo mira cada reto que emite el borde, en la página del reto de navegador, en el modo bajo ataque y en el widget Verify, y le da una puntuación de 0 a 100. La puntuación sale de señales que la petición ya trae: la red de la que viene, si el navegador que dice ser cuadra y cómo se resolvió la prueba de trabajo. Sin huellas extra ni terceros: consulta Datos tratados.
La puntuación elige un nivel: la prueba de trabajo sola, una más dura, un paso de un clic «Verifica que eres humano» o un bloqueo. En el modo por defecto, log, los niveles solo se cuentan, así que ves en la analítica lo que harían antes de aplicarlos.
La puntuación de riesgo encarece la automatización; no demuestra que el visitante sea humano. Lee los límites antes de confiar en ella.
Niveles
La puntuación es la suma de los pesos de las señales que saltaron, siempre entre 0 y 100. Cada zona tiene tres umbrales; el nivel es el más alto que alcanza la puntuación.
| Nivel | Umbral por defecto | Página del reto | Widget Verify |
|---|---|---|---|
pass | por debajo de 30 | La prueba de trabajo de siempre. | La prueba de trabajo de siempre. |
harder | 30 | Una prueba de trabajo más larga: +1 bit de dificultad en el umbral, +1 más cada 15 puntos, como mucho +3 (cada bit duplica el trabajo esperado; el total nunca pasa de 24 bits). | El mismo trabajo extra en la caja. |
interactive | 60 | Tras la prueba de trabajo, el paso de un clic del modo bajo ataque. | Los widgets managed piden la misma interacción que a un navegador automatizado (consulta los modos); los non_interactive e invisible responden interaction-required. |
block | 90 | 403 con la página de bloqueo de la zona y OpenWAF-Mitigated: block. | 403 con {"error":"blocked"} (o {"success":false,"error":"blocked"} al comprobar la solución). |
Cuándo se aplica cada nivel:
- Página del reto. La puntuación se calcula al emitir la página, y ahí se aplican
harder,interactiveyblock. Cuando la solución llega a/__waf/verifyse puntúa otra vez con las señales de la resolución; un nivelblockresponde entonces403con{"ok":false,"error":"blocked"}en lugar del pase. Una resolución que solo llega ainteractiveno se convierte en un clic en ese momento: marca la red, y su siguiente reto empieza con más puntuación. - Widget Verify. En
/start(cuando la caja pide su reto),harderañade trabajo yblocklo rechaza. Al comprobar la solución,interactiveescala un widgetmanagedal clic (salvo que el visitante ya interactuara con la página) yblockla rechaza.
Un umbral 0 desactiva ese nivel. Los umbrales distintos de cero tienen que crecer en el orden harder < interactive < block, y al menos uno tiene que estar activado.
Señales
Cada señal tiene un peso fijo. Las señales de emisión se evalúan al emitir el reto; las de resolución, al comprobar su solución, junto con las de emisión del mismo reto.
| Señal | Peso | Se evalúa | Qué mira |
|---|---|---|---|
asn_hosting | 25 | emisión | La red del cliente (ASN) está en la lista de redes de hosting y centros de datos de OpenWAF: la bad-asn-list (enlace externo), con licencia MIT, más grandes nubes (AWS, Google Cloud, Azure, OVH, Hetzner, DigitalOcean y otras). Las redes de retransmisión por las que navegan personas, como iCloud Private Relay y Cloudflare WARP, nunca están en la lista. |
ip_feed | 35 | emisión | La dirección del cliente está en una de las listas de amenazas del borde, aunque la zona no bloquee las direcciones de las listas. |
ip_reputation | 20 | emisión | El nodo de borde ha visto a la dirección provocar suficientes detecciones del WAF como para darle mala reputación. |
net_velocity | 15 | emisión, resolución | 30 o más retos emitidos a la red del cliente (/24 IPv4, /64 IPv6) en el nodo en unos 10 minutos. |
net_failures | 25 | emisión, resolución | La red falló 5 o más resoluciones en unos 10 minutos, y al menos la mitad de sus intentos. |
asn_failures | 10 | emisión, resolución | El ASN hizo 50 o más intentos de resolución en unos 10 minutos y al menos el 60 % fallaron. |
net_replay | 30 | emisión, resolución | En unos 10 minutos la red envió dos veces la solución de un reto, o un token de Verify llegó dos veces a siteverify. |
tls_mismatch | 30 | emisión | El User-Agent dice ser un navegador reciente pero el ClientHello TLS de la conexión no viene de la pila TLS de ese navegador (ver Pilas TLS). No se evalúa cuando la petición llega al borde a través de otro proxy que termina TLS, ni para User-Agents que ya cuenta ua_automation. |
headers_missing | 15 | emisión | Un User-Agent de navegador sin Accept-Language, o un navegador actual (Chromium 80+, Firefox 90+, Safari 17+) sin Sec-Fetch-Mode. |
client_hints_mismatch | 20 | emisión | Los client hints de baja entropía que envía todo Chromium (Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform) contradicen el User-Agent: Firefox o Safari enviándolos, Chromium 90+ (que no sea un WebView de Android) sin ellos, móvil ?1 con un User-Agent de escritorio, o una plataforma Windows o macOS que no cuadra. |
ua_automation | 40 | emisión | El User-Agent de un navegador headless, una herramienta de automatización o una librería HTTP (HeadlessChrome, Puppeteer, Playwright, Selenium, curl, python-requests, Go-http-client y similares), o cualquier User-Agent que no empiece por Mozilla/, incluido uno vacío. |
webdriver | 30 | resolución | El widget Verify informó de navigator.webdriver (el navegador dice que está automatizado). |
solve_too_fast | 25 | resolución, luego emisión | Una prueba de trabajo de al menos 50 000 hashes resuelta más deprisa de lo que puede un navegador: más de 8 millones de hashes por segundo (4 millones con un User-Agent móvil), o sin tiempo medible desde que se emitió el reto. Cuando salta, los siguientes retos de la red en unos 10 minutos también la llevan. |
solve_timing_repeat | 20 | resolución, luego emisión | Las resoluciones de la red tardan siempre lo mismo: una de al menos 200 ms a menos de 3 ms de dos de sus resoluciones recientes. Sus siguientes retos en unos 10 minutos también la llevan. |
cdp_runtime | 35 | resolución | Comprobación en el navegador (Chromium): un cliente del protocolo DevTools ha activado el runtime de JavaScript de la página, como hacen los frameworks de automatización. Las DevTools abiertas también lo hacen. |
native_patched | 25 | resolución | Comprobación en el navegador: funciones o propiedades propias del navegador sustituidas por un script, como hacen los plugins «stealth». |
headless_env | 30 | resolución | Comprobación en el navegador: la ventana no es la de un navegador de escritorio: un Chromium headless, o una pantalla emulada al tamaño de la página. |
worker_mismatch | 25 | resolución | Comprobación en el navegador: el Web Worker de la prueba de trabajo describe otro navegador (user agent, plataforma, idiomas, núcleos del procesador) que la página. |
click_dynamics | 15 | resolución, con clic | Comprobación en el navegador sobre cómo se activó la casilla: entradas que una persona no produce, como pulsar en el centro exacto de la casilla o soltar una tecla casi al instante. |
client_tampered | 40 | resolución | Falta la respuesta a las comprobaciones del navegador o no encaja con ese reto: un solver sin navegador, o una respuesta grabada de otro reto o inventada. |
pat_valid | −20 | resolución | El dispositivo presentó un Private Access Token válido. Solo baja la puntuación, nunca por debajo de 0, y nunca deja pasar un reto por sí solo. Aún no está activado: no hay ningún emisor de tokens configurado, porque los emisores que aceptan los dispositivos de Apple necesitan un acuerdo con OpenWAF antes de emitir para nosotros. |
Las señales de tiempo no miran los retos que piden un clic, porque entonces el tiempo lo marca el ritmo del visitante. Los contadores por red y por ASN se guardan en cada nodo de borde unos 10 minutos y no se comparten entre nodos.
Las comprobaciones del navegador se ejecutan en la página del reto y en el widget; cada una es un sí o un no que no sale de la página de ninguna otra forma. Su respuesta se codifica distinta en cada reto, así que una respuesta grabada no vale para otro, y las funciones que inspeccionan cambian de un reto a otro. Su total se limita al umbral interactivo salvo que también salte una señal del servidor: por sí solas pueden pedir un clic, nunca bloquear. El nombre cdp_input está reservado y nunca salta.
Pilas TLS
El borde calcula la huella JA4 (enlace externo) de cada conexión TLS que termina, a partir del ClientHello. JA4 cambia con cada versión del navegador, así que tls_mismatch no compara cadenas JA4 exactas. Comprueba que el ClientHello ofrece TLS 1.3 con h2 como primer valor de ALPN y que tiene los rasgos estables del navegador que nombra el User-Agent:
| El User-Agent dice ser | Pila TLS | Rasgos que debe mostrar |
|---|---|---|
| Chromium 100+ (Chrome, Edge, Opera, Brave…) en cualquier sistema salvo iOS | BoringSSL | Valores GREASE y la extensión application_settings (ALPS) |
| Firefox 100+, salvo en iOS | NSS | Sin valores GREASE y con la extensión record_size_limit |
| Safari 16+ en macOS, cualquier navegador en iOS | La pila de red de Apple | Valores GREASE, ni ALPS ni record_size_limit, y 17 o más cipher suites |
Los navegadores más antiguos mantienen una comprobación más gruesa: TLS 1.3 y h2 (Chromium 70+, Firefox 70+, Safari 14+), y GREASE para Chromium 70–99.
Por ejemplo, Chrome 156 envía t13d1518h2_8daaf6152771_e2d80978ab2e, curl envía t13d4907h2_… y requests de Python envía t13d1712h1_…. Así se detecta un Chromium automatizado cuyo User-Agent dice ser Safari o Firefox, y un script que copia el User-Agent de un navegador. Una herramienta de automatización basada en Firefox con User-Agent de Firefox tiene el TLS de Firefox, así que no salta.
Private Access Tokens
Un Private Access Token (RFC 9577 (enlace externo)) permite a un dispositivo demostrar que pasó las comprobaciones de su fabricante (en los de Apple, un dispositivo auténtico con una cuenta iniciada) sin decir quién es. Con el scoring de riesgo activo y al menos un emisor de tokens de confianza, la página del reto y el widget piden al borde un reto de token ligado a ese reto y al host del sitio. Un dispositivo compatible responde con un token, que el borde comprueba sin conexión con la clave pública del emisor. Un token válido suma pat_valid al resolver el reto.
El token no lleva identidad y no se puede relacionar con otras visitas del visitante; el borde lo comprueba y lo descarta, y la analítica solo cuenta pat_valid como señal. Los dispositivos sin tokens no pierden nada: su puntuación es la misma que sin esta función.
Estado: no activado. No hay ningún emisor configurado, porque emitir tokens que los dispositivos de Apple acepten para OpenWAF necesita un acuerdo con uno de esos emisores. Hasta entonces el borde nunca pide un token y pat_valid nunca salta.
Modo y umbrales
La puntuación de riesgo es el objeto challenge.risk de la zona. Se aplica a todos los nombres de la zona; un nombre no puede cambiarla (hosts.<name>.challenge.risk se rechaza).
| Campo | Valores | Por defecto | Significado |
|---|---|---|---|
mode | off, log, enforce | log | off: los retos no se puntúan. log: se puntúan y se cuentan en la analítica, no se aplica nada. enforce: se aplica cada nivel. |
thresholds.harder | 0–100 | 30 | Puntuación desde la que la prueba de trabajo se endurece. 0 = desactivado. |
thresholds.interactive | 0–100 | 60 | Puntuación desde la que el visitante tiene que confirmar con un clic. 0 = desactivado. |
thresholds.block | 0–100 | 90 | Puntuación desde la que el reto se rechaza. 0 = desactivado. |
Los campos se fusionan uno a uno: los umbrales que no envías conservan su valor, y "risk": null vuelve a los valores por defecto. GET …/edge muestra siempre el objeto efectivo. Cada cambio queda versionado, necesita un motivo y se puede revertir.
Dominio → Seguridad → Verificación → Puntuación de riesgo: el modo (Desactivado, Solo registro, Aplicar) y los tres umbrales (Prueba de trabajo más difícil desde, Pedir un clic desde, Bloquear desde). La tarjeta muestra las franjas de puntuación de tus umbrales; Volver a los valores por defecto vuelve a solo registro, 30, 60 y 90.
openwaf-cloud ctl zones set example.com challenge.risk.mode=enforce challenge.risk.thresholds.block=95 \
--reason "Aplicar la puntuación de riesgo"
openwaf-cloud ctl zones set example.com challenge.risk:=null --reason "Puntuación de riesgo por defecto"curl -s -X PATCH https://dash.openwaf.dev/v1/orgs/$ORG/zones/$ZONE/edge \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Change-Reason: Aplicar la puntuación de riesgo" \
-d '{"challenge": {"risk": {"mode": "enforce", "thresholds": {"block": 95}}}}'Añade ?dry_run=1 para ver el cambio sin aplicarlo.
update_settings con {"zone": "example.com", "settings": {"challenge": {"risk": {"mode": "enforce", "thresholds": {"block": 95}}}}, "reason": "Aplicar la puntuación de riesgo"}, primero confirm=false y después confirm=true con expected_version.
De log a enforce
- Déjala en modo log (el de por defecto) unos días de tráfico normal, y durante un ataque si puedes.
- Lee el informe (abajo): cuántos retos caerían en cada nivel, la distribución de puntuaciones y las señales que más saltan. Fíjate en la parte de
interactiveyblock: esos visitantes recibirían un clic o un403. - Ajusta los umbrales si una señal que provocan tus visitantes reales (por ejemplo
asn_hostingen un público B2B con VPN corporativas o escritorios en la nube) sube a demasiados. Subir un umbral, o ponerharderointeractivea0, es más seguro que desactivar la puntuación. - Pasa a enforce y sigue mirando el informe: en modo enforce,
escalatedyblockedcuentan lo aplicado.
Analítica
Dominio → Seguridad → Verificación muestra, en la sección Riesgo de la actividad, el modo y los umbrales, los retos puntuados, el reparto por nivel, la distribución de puntuaciones, cuántos tuvieron que hacer clic tras resolver y cuántos se bloquearon, y las señales que más saltaron. En modo log indica que solo se registra y no se aplica nada, y los recuentos son lo que habría hecho enforce.
Los mismos datos están en el objeto risk de GET /v1/orgs/{org}/zones/{zone}/challenge/analytics?days=7, de openwaf-cloud ctl challenge report example.com --days 7 (líneas Riesgo, Niveles, un histograma de puntuaciones y Señales) y de la herramienta MCP challenge_report:
"risk": {
"mode": "log",
"thresholds": {"harder": 30, "interactive": 60, "block": 90},
"evaluated": 1230,
"levels": {"pass": 1100, "harder": 90, "interactive": 32, "block": 8},
"scores": [900, 120, 80, 50, 30, 20, 15, 7, 0, 5, 3],
"top_signals": [{"signal": "asn_hosting", "count": 140}, {"signal": "headers_missing", "count": 60}],
"escalated": 4,
"blocked": 8
}| Campo | Significado |
|---|---|
mode, thresholds | El ajuste actual de la zona. |
evaluated | Retos puntuados al emitirse. |
levels | Su reparto por nivel al emitirse. |
scores | Los mismos retos por puntuación: 0–9, 10–19, … 90–99 y 100. |
top_signals | Hasta 10 señales y cuántas veces saltó cada una (en la emisión y en la resolución). |
escalated | Resoluciones del widget a las que se pidió un clic por su puntuación (en modo log: a las que se habría pedido). |
blocked | Retos bloqueados en la emisión o en la resolución (en modo log: que se habrían bloqueado). |
Cada fila de la tabla por origen (gate, rate_rule, under_attack, cada widget…) lleva sus propios risk_levels. La analítica conserva los agregados 30 días.
Lo que nunca se puntúa
La puntuación de riesgo solo puntúa retos que el borde iba a emitir de todos modos; nunca reta una petición por sí misma. Las peticiones que se saltan el reto se saltan también la puntuación:
- direcciones y redes con una regla
allowde IP o ASN; - rastreadores verificados (verificación de bots);
- las
exempt_pathsdel modo bajo ataque y las rutas/__waf/del propio borde; - las peticiones que no son navegaciones del navegador: reciben la respuesta habitual, no un reto;
- los visitantes que ya tienen un pase válido.
El modo bajo ataque se puntúa como cualquier otro reto. Su propio clic (interactive, activado por defecto) no depende de la puntuación; en modo enforce una puntuación alta puede igualmente endurecer el trabajo o bloquear.
Datos tratados
Lo que lee el motor de riesgo, todo ello ya presente en la petición o en la resolución:
- La dirección del cliente, para buscar su ASN, las listas de amenazas y la reputación, en la memoria del nodo de borde.
- La huella JA4 del ClientHello TLS de la conexión (ver Pilas TLS). El borde lee el ClientHello una vez para calcularla y descarta los bytes originales.
- Cabeceras de baja entropía que envía todo navegador:
User-Agent,Accept-Language,Sec-Fetch-Mode,Sec-CH-UA,Sec-CH-UA-Mobile,Sec-CH-UA-Platform. - Un Private Access Token, solo si el dispositivo lo ofrece y la función está activada (aún no lo está). Se comprueba y se descarta.
navigator.webdriver, que el widget Verify ya enviaba.- La respuesta de las comprobaciones del navegador: un sí o un no por comprobación, codificado para el reto. La página compara, sin enviarlos: si el runtime de JavaScript se está serializando para un depurador, si unas pocas funciones propias del navegador siguen siendo las suyas, los tamaños de la ventana y de la pantalla, el user agent, la plataforma, los idiomas y los núcleos del Web Worker frente a los de la página y, en la casilla, en qué punto cayó la pulsación y cuánto tiempo se mantuvo la tecla.
- El tiempo de la prueba de trabajo: cuándo se emitió el reto, cuándo llegó la solución y el trabajo que supone.
Lo que guarda: contadores por red (con un hash de la /24 IPv4 o la /64 IPv6 como clave) y por ASN, unos 10 minutos en la memoria del nodo de borde. La analítica recibe solo niveles, tramos de puntuación y nombres de señal, por sitio y por minuto, sin ninguna dirección IP; se conservan 30 días. Los eventos de seguridad de una petición guardan su cadena JA4 en tls_fingerprint (vacía cuando otro proxy terminó TLS, o en HTTP sin cifrar), nunca el ClientHello original.
Lo que nunca recoge: huellas de canvas, WebGL, audio o fuentes, plugins ni client hints de alta entropía. El borde nunca envía Accept-CH para pedir más hints.
Todo lo que trata Verify, cuánto tiempo y dónde, los papeles según el contrato de encargo y un párrafo para tu política de privacidad: Datos que trata OpenWAF Verify (enlace externo) (borrador pendiente de revisión legal).
Límites
- Coste, no prueba. La puntuación de riesgo hace la automatización más lenta y más cara. Una automatización que maneja un navegador real a través del sistema operativo o del protocolo DevTools, desde direcciones residenciales o móviles, envía las mismas cabeceras y el mismo TLS que una persona y puede pasar igual. Las comprobaciones del navegador cazan el comportamiento por defecto de los frameworks habituales; uno ajustado para imitar la entrada de una persona, o la entrada inyectada por el sistema operativo, las pasa.
- Las comprobaciones del navegador son pistas. Un visitante con las DevTools abiertas, o que usa control por voz o acceso por interruptor que pulsa en el centro exacto de la casilla, puede hacer saltar una; por eso nunca bloquean solas.
- Comprobación TLS.
tls_mismatchusa la huella JA4 y rasgos estables de la pila TLS de cada navegador, no una lista de huellas exactas, así que una herramienta que reproduce exactamente el TLS de un navegador la pasa. No se evalúa cuando hay otro proxy que termina TLS delante del borde, porque entonces el handshake es del proxy. - Hosting o no. La lista de ASN solo distingue redes de hosting y centros de datos del resto; no hay clasificación de redes móviles ni residenciales.
- Memoria por nodo. Los contadores por red y por ASN viven en cada nodo de borde unos 10 minutos, así que el tráfico repartido entre varios nodos los acumula más despacio.
- Sin clic tras la resolución de la página. En la página del reto,
interactivese aplica al emitirla; la puntuación de la resolución solo puede bloquear o marcar la red para su siguiente reto. - Siteverify sin cambios. Siteverify sigue respondiendo
metadata.riskcomolow, ohighcuando se pidió interacción; no lleva la puntuación ni las señales. - El modo log no aplica nada. En modo log los niveles solo aparecen en la analítica.