Korzen
Bot Protection
En esta documentación

Modo first-party

El tag, su beacon y la página de desafío servidos desde tu propio dominio, por un SDK, un Cloudflare Worker o ambos, para visitantes cuya red bloquea Korzen.

Opcional. Un filtro DNS, un bloqueador de anuncios o una red que bloquea el host de Korzen detiene el tag, su beacon y la página de desafío. Esos visitantes siguen pasando, juzgados solo por lo que envía tu servidor, pero en una ruta que exige el token del tag no pueden pasar. El modo first-party sirve los tres desde una ruta de tu propio dominio, /kz si no eliges otra, y los reenvía a Bot Protection con la dirección del visitante. Nada bloquea una ruta de un sitio sin bloquear el sitio.

Hay dos formas de servir la ruta, y puedes usar las dos:

La formaQué activasQuién sirve /kz
Solo el SDKfirstParty: true en Node, FirstParty: true en Gotu servidor
Solo el WorkerKORZEN_FIRST_PARTY = "1" y una ruta para /kz/*tu Cloudflare Worker
El Worker y el SDKambos, con la misma rutael Worker; tu servidor le envía los visitantes desafiados

En todas las formas, la página carga el tag desde tu ruta en lugar del host de Korzen. El tag toma su dirección de su propio src, así que cada solicitud que hace también va a /kz:

<script src="/kz/tag.js" data-site-key="YOUR_SITE_KEY" async></script>

El modo está en todos los SDK: Node (y su Worker), Go, Python, PHP, Java y .NET, cada uno con su opción en su README. Un cliente escrito a partir de una guía de framework no tiene forwarder: ahí usa el SDK o, detrás de Cloudflare, el Worker solo, que da el modo a un origen en cualquier lenguaje.

Solo el SDK

Tu servidor sirve la ruta. Pasa la opción al middleware que ya consulta a Bot Protection, y dale a la ruta un handler propio con las mismas opciones. El client debe ser uno creado con createClient, con tu clave de API: el reenviador la envía a Korzen con cada solicitud.

// Express, with korzen.js as the Express guide sets it up
const options = { client: korzen, ip: (_get, req) => req.ip, firstParty: true };

app.use("/kz", protect(options)); // before any body parser: it answers /kz/... itself
app.use(express.urlencoded({ extended: false }));
app.post("/signup", protect(options), signup);

// Next.js: app/kz/[...path]/route.js serves the path
const kz = withKorzen(() => new Response(null, { status: 404 }), { client, ip: ipFrom.vercel, firstParty: true });
export { kz as GET, kz as POST };
o := korzen.Options{IP: korzen.RemoteIP, FirstParty: true}

mux.Handle("/kz/", bot.Protect(http.NotFoundHandler(), o)) // the path: Protect answers it itself
mux.Handle("POST /signup", bot.Protect(http.HandlerFunc(signup), o))
  • Qué activas. firstParty: true en protect o withKorzen (activo solo cuando es true); en Go, Options.FirstParty. Luego el src del tag en tus páginas pasa a ser /kz/tag.js. La dirección del visitante se lee como la lee la decisión: la opción ip, en Go Options.IP.
  • Una dirección que el visitante no puede escribir. Korzen confía en la dirección que el forwarder envía con tu clave, así que la opción ip debe leer una que el visitante no pueda fijar: ipFrom.vercel solo en Vercel, ipFrom.cloudflare (Go: korzen.CloudflareIP) solo si tu servidor recibe tráfico solo de Cloudflare, ipFrom.lastForwarded detrás de tu propio load balancer o nginx. Un header que el visitante puede escribir deja que un script elija la dirección que Korzen juzga.
  • Qué cambia para el visitante. Nada que vea. Su navegador carga el tag y envía su beacon a tu dominio, así que un visitante cuya red bloquea Korzen se juzga con la evidencia del tag y puede hacer una acción que exige su token. Las solicitudes bajo la ruta nunca se juzgan ni llegan a tus handlers; cualquier otra ruta se juzga como antes.
  • La página de desafío. Una carga de página desafiada va a /kz/challenge?c=…&r=… en tu dominio en lugar del de Korzen, y tu servidor reenvía la página, sus archivos y su envío. El mismo desafío, y el visitante vuelve a la página que pidió.
  • Fail open. Cuando Bot Protection está lento, caído o inalcanzable desde tu servidor, cada solicitud bajo la ruta responde 503 con el cuerpo vacío, y el tag descarta esa señal; la página y tus otras rutas no esperan nada. Fail open
  • Cómo saber que funciona. curl -I https://shop.example/kz/tag.js responde 200, y el panel de red del navegador muestra las solicitudes del tag yendo a tu propio dominio. Cómo saber que funciona

Solo el Worker

Tu Cloudflare Worker sirve la ruta, sin cambios en tu servidor, para un origen en cualquier lenguaje. Configura el Worker como muestra Cloudflare Workers, luego activa el modo y añade /kz/* a las rutas del Worker:

# wrangler.toml
routes = [
  { pattern = "shop.example/kz/*", zone_name = "shop.example" },
  { pattern = "shop.example/signup*", zone_name = "shop.example" },
]

[vars]
KORZEN_URL = "https://korzen.dev"
KORZEN_FIRST_PARTY = "1"
# KORZEN_FIRST_PARTY_PATH = "/kz"   # the path, /kz unless set
  • Qué activas. KORZEN_FIRST_PARTY con 1 o true (cualquier otro valor salvo 0 o false lo deja apagado, con un aviso en el log del Worker), una ruta para shop.example/kz/*, y el src del tag en tus páginas como /kz/tag.js. La dirección del visitante es CF-Connecting-IP, que pone Cloudflare.
  • Qué cambia para el visitante. Nada que vea. El tag y su beacon van a tu dominio, respondidos en el edge de Cloudflare; tu origen nunca ve esas solicitudes. Las rutas del Worker se juzgan como antes.
  • La página de desafío. Una carga de página que el Worker desafía va a /kz/challenge en tu dominio, que sirve el Worker.
  • Fail open. Igual que con el SDK: un 503 con el cuerpo vacío solo para esa solicitud, y todas las demás siguen hacia tu origen. Fail open
  • Cómo saber que funciona. Como abajo; y npx wrangler tail lista las solicitudes bajo /kz, mientras el log de acceso de tu origen no muestra ninguna. Cómo saber que funciona

El Worker y el SDK

Cuando hay un Worker delante de tu sitio y el SDK corre en tu aplicación (para el context que solo tu aplicación conoce), activa el modo en los dos, con la misma ruta.

  • Qué activas. El Worker como arriba, con su ruta para /kz/*, y firstParty: true (Go: FirstParty: true) en el SDK detrás de él. El Worker responde /kz/* por sí mismo, así que el SDK nunca ve esas solicitudes.
  • Qué cambia para el visitante. Lo mismo que con el Worker solo: el tag, su beacon y la página de desafío vienen de tu dominio, respondidos en el edge.
  • La página de desafío. El firstParty del SDK envía a los visitantes que desafía a /kz/challenge en lugar del host de Korzen, y el Worker sirve esa página. Sin él, un visitante que el SDK desafía va al host de Korzen, que su red puede bloquear.
  • Fail open. El que sirve la ruta se rinde por su cuenta y responde 503; ninguno retiene al otro. Si las rutas del Worker dejan fuera /kz/*, o se quita el Worker, el SDK sirve la ruta él mismo: detrás de Cloudflare, dale entonces ip: ipFrom.cloudflare (Go: korzen.CloudflareIP).
  • Cómo saber que funciona. Como abajo; npx wrangler tail lista las solicitudes bajo /kz, y el log de tu servidor no muestra ninguna.

Qué sirve la ruta

El reenviador, el SDK o el Worker, responde a estas y a nada más:

En tu dominioQué es
GET /kz/tag.jsel tag, con los headers de caché de Korzen
GET /kz/n, POST /kz/sel nonce del tag y su beacon (un cuerpo de 64 KiB como máximo)
GET /kz/pingrespondido 204 por el propio reenviador, nunca reenviado
GET y POST /kz/challenge, GET /kz/challenge/waitla página de desafío y su envío (16 KiB como máximo)
GET /kz/static/…el script, el estilo y las fuentes de la página de desafío

Cualquier otra ruta bajo /kz es un 404, y una conocida con otro método un 405; ninguna llega a Bot Protection, así que la ruta nunca es un proxy abierto. A Korzen el reenviador envía los headers del propio navegador de una lista corta (User-Agent, Accept, Accept-Language, Content-Type, Origin, Sec-Fetch-*, los client hints, If-None-Match), tu clave de API como bearer y la dirección del visitante. Nunca una cookie, el Authorization del navegador ni un X-Forwarded-*. Lo que vuelve conserva los headers de caché y de seguridad de Korzen, nunca un Set-Cookie.

Korzen solo acepta la dirección del visitante de una solicitud en estas rutas que lleve tu clave de API, y en el beacon solo para tu propia site key; cualquier otra cosa se rechaza con 403. Tu clave nunca llega al navegador.

La cookie _kz del tag y su almacenamiento siguen en tu dominio, como antes; los datos que recibe Korzen son los mismos. Algunas evidencias que Korzen solo lee cuando el navegador se conecta directamente a Korzen no están a través de un reenviador; el resto de la evidencia del tag llega, y nada de lo que falta cuenta contra un visitante.

Otra ruta

firstPartyPath (Go: Options.FirstPartyPath; el Worker: KORZEN_FIRST_PARTY_PATH) nombra otra ruta, por ejemplo cuando una lista de bloqueo aprenda /kz. Empieza con /, termina sin ella, y tiene como máximo 64 caracteres en segmentos de A-Z a-z 0-9 . _ ~ -. Una que no se lee se registra una vez en el log, y el modo queda apagado. Usa la misma ruta en el src del tag, en el SDK y en el Worker.

Fail open

La ruta nunca retiene tu página ni tus otras rutas:

  • Cada solicitud reenviada se rinde por su cuenta: tras 1 s en un GET, 2,5 s en un POST. Un timeout, un error de red, una respuesta de más de 4 MiB o un fallo del reenviador es un 503 con el cuerpo vacío y Cache-Control: no-store; las redirecciones no se siguen. Los 4xx y 5xx del propio Bot Protection pasan tal cual.
  • Sin la dirección del visitante (la opción ip no lee ninguna), el nonce, el beacon y el desafío responden 503; el tag y los archivos de la página de desafío se siguen sirviendo.
  • El tag descarta entonces esa señal, como hace cuando el host de Korzen está caído. Un tag que no informó nunca es evidencia de un bot: el visitante se juzga por lo que envía tu servidor.

Cómo saber que funciona

  • curl -I https://shop.example/kz/tag.js responde 200, y curl -I https://shop.example/kz/ping responde 204.
  • En las herramientas de desarrollo del navegador, en una página con el tag, el panel de red muestra /kz/tag.js, /kz/n, /kz/ping y /kz/s en tu propio dominio, respondidos 200 o 204, y ninguna solicitud al host de Korzen.
  • Haz una acción protegida y después pulsa Comprueba tu integración en la página Configuración: El token del tag en esa llamada indica Visto. Tus decisiones aparecen como antes en la página Decisiones del panel.
  • Con una Content-Security-Policy, el host de Korzen puede salir de ella: script-src 'self'; connect-src 'self'; worker-src blob: basta para el tag. El tag

Errores comunes

  • El tag aún cargado desde el host de Korzen: el modo sirve la ruta, pero solo el src del tag lleva allí al navegador.
  • Un body parser antes del reenviador en Express: el cuerpo del beacon se lee antes de que el reenviador lo reciba. Móntalo antes de cualquier parser, o solo en la ruta.
  • El Worker con KORZEN_FIRST_PARTY pero sin ruta para /kz/*: las solicitudes llegan a tu origen, que responde 404 salvo que el SDK sirva la ruta allí.
  • Dos rutas: el Worker en una y el SDK en otra. El SDK envía entonces a los visitantes desafiados a una ruta que el Worker no sirve.
  • Llamar a /v1/decide a mano: su challenge.url es siempre el host de Korzen. Solo los SDKs y el Worker envían al visitante a tu ruta.