Korzen
Bot Protection
Dans cette documentation

Mode first-party

Le tag, son beacon et la page de défi servis depuis votre propre domaine, par un SDK, un Cloudflare Worker ou les deux, pour les visiteurs dont le réseau bloque Korzen.

Facultatif. Un filtre DNS, un bloqueur de publicité ou un réseau qui bloque l'hôte de Korzen arrête le tag, son beacon et la page de défi. Ces visiteurs passent toujours, jugés sur ce que votre serveur envoie seul, mais sur une route qui exige le token du tag, ils ne peuvent pas passer. Le mode first-party sert les trois depuis un chemin de votre propre domaine, /kz sauf si vous en nommez un autre, et les transmet à Bot Protection avec l'adresse du visiteur. Rien ne bloque un chemin d'un site sans bloquer le site.

Il y a deux façons de servir le chemin, et vous pouvez utiliser les deux :

La façonCe que vous activezQui sert /kz
Le SDK seulfirstParty: true en Node, FirstParty: true en Govotre serveur
Le Worker seulKORZEN_FIRST_PARTY = "1" et une route pour /kz/*votre Cloudflare Worker
Le Worker et le SDKles deux, avec le même cheminle Worker ; votre serveur lui envoie les visiteurs défiés

Dans tous les cas, la page charge le tag depuis votre chemin au lieu de l'hôte de Korzen. Le tag prend son adresse de son propre src, donc chaque requête qu'il fait va elle aussi vers /kz :

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

Le mode existe dans chaque SDK : Node (et son Worker), Go, Python, PHP, Java et .NET, chacun avec son option dans son README. Un client écrit d'après un guide de framework n'a pas de forwarder : utilisez alors le SDK ou, derrière Cloudflare, le Worker seul, qui donne le mode à une origine dans n'importe quel langage.

Le SDK seul

Votre serveur sert le chemin. Donnez l'option au middleware qui interroge déjà Bot Protection, et donnez au chemin un handler à lui avec les mêmes options. Le client doit être créé par createClient, avec votre clé API : le forwarder l'envoie à Korzen avec chaque requête.

// 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))
  • Ce que vous activez. firstParty: true sur protect ou withKorzen (actif seulement quand il vaut true) ; en Go, Options.FirstParty. Ensuite, le src du tag sur vos pages devient /kz/tag.js. L'adresse du visiteur est lue comme la décision la lit : l'option ip, en Go Options.IP.
  • Une adresse que le visiteur ne peut pas écrire. Korzen fait confiance à l'adresse que le forwarder envoie avec votre clé : l'option ip doit donc en lire une que le visiteur ne peut pas fixer : ipFrom.vercel sur Vercel seulement, ipFrom.cloudflare (Go : korzen.CloudflareIP) seulement si votre serveur ne reçoit du trafic que de Cloudflare, ipFrom.lastForwarded derrière votre propre load balancer ou nginx. Un en-tête que le visiteur peut écrire laisse un script choisir l'adresse que Korzen juge.
  • Ce qui change pour le visiteur. Rien qu'il voie. Son navigateur charge le tag et envoie son beacon à votre domaine, donc un visiteur dont le réseau bloque Korzen est jugé avec les preuves du tag et peut faire une action qui exige son token. Les requêtes sous le chemin ne sont jamais jugées et n'atteignent jamais vos handlers ; tout autre chemin est jugé comme avant.
  • La page de défi. Un chargement de page défié va vers /kz/challenge?c=…&r=… sur votre domaine au lieu de celui de Korzen, et votre serveur transmet la page, ses fichiers et son envoi. Le même défi, et le visiteur revient à la page qu'il avait demandée.
  • Fail-open. Quand Bot Protection est lent, en panne ou injoignable depuis votre serveur, chaque requête sous le chemin répond 503 avec un corps vide, et le tag abandonne ce signal ; la page et vos autres routes n'attendent rien. Fail-open
  • Comment savoir que ça marche. curl -I https://shop.example/kz/tag.js répond 200, et le panneau réseau du navigateur montre les requêtes du tag vers votre propre domaine. Comment savoir que ça marche

Le Worker seul

Votre Cloudflare Worker sert le chemin, sans changement sur votre serveur, pour une origine dans n'importe quel langage. Mettez en place le Worker comme le montre Cloudflare Workers, puis activez le mode et ajoutez une route pour le chemin :

# 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
  • Ce que vous activez. KORZEN_FIRST_PARTY à 1 ou true (toute autre valeur que 0 ou false le laisse désactivé, avec un avertissement dans le log du Worker), une route pour shop.example/kz/*, et le src du tag sur vos pages en /kz/tag.js. L'adresse du visiteur est CF-Connecting-IP, que Cloudflare définit.
  • Ce qui change pour le visiteur. Rien qu'il voie. Le tag et son beacon vont vers votre domaine, servis à l'edge de Cloudflare ; votre origine ne voit jamais ces requêtes. Les routes du Worker sont jugées comme avant.
  • La page de défi. Un chargement de page que le Worker défie va vers /kz/challenge sur votre domaine, que le Worker sert.
  • Fail-open. Comme pour le SDK : un 503 avec un corps vide pour cette requête seule, et toutes les autres continuent vers votre origine. Fail-open
  • Comment savoir que ça marche. Comme ci-dessous ; et npx wrangler tail liste les requêtes sous /kz, tandis que le log d'accès de votre origine n'en montre aucune. Comment savoir que ça marche

Le Worker et le SDK

Quand un Worker est devant votre site et que le SDK tourne dans votre application (pour le context que seule votre application connaît), activez le mode dans les deux, avec le même chemin.

  • Ce que vous activez. Le Worker comme ci-dessus, avec sa route pour /kz/*, et firstParty: true (Go : FirstParty: true) sur le SDK derrière lui. Le Worker répond lui-même à /kz/*, donc le SDK ne voit jamais ces requêtes.
  • Ce qui change pour le visiteur. La même chose qu'avec le Worker seul : le tag, son beacon et la page de défi viennent de votre domaine, servis à l'edge.
  • La page de défi. Le firstParty du SDK envoie les visiteurs qu'il défie vers /kz/challenge au lieu de l'hôte de Korzen, et le Worker sert cette page. Sans lui, un visiteur que le SDK défie va vers l'hôte de Korzen, que son réseau peut bloquer.
  • Fail-open. Celui qui sert le chemin abandonne de lui-même et répond 503 ; aucun ne retient l'autre. Si les routes du Worker laissent /kz/* de côté, ou si le Worker est retiré, le SDK sert le chemin lui-même : derrière Cloudflare, donnez-lui alors ip: ipFrom.cloudflare (Go : korzen.CloudflareIP).
  • Comment savoir que ça marche. Comme ci-dessous ; npx wrangler tail liste les requêtes sous /kz, et le log de votre serveur n'en montre aucune.

Ce que le chemin sert

Le forwarder, le SDK ou le Worker, répond à celles-ci et à rien d'autre :

Sur votre domaineCe que c'est
GET /kz/tag.jsle tag, avec les en-têtes de cache de Korzen
GET /kz/n, POST /kz/sle nonce du tag et son beacon (un corps de 64 Kio au plus)
GET /kz/pingrépondu 204 par le forwarder lui-même, jamais transmis
GET et POST /kz/challenge, GET /kz/challenge/waitla page de défi et son envoi (16 Kio au plus)
GET /kz/static/…le script, le style et les polices de la page de défi

Tout autre chemin sous /kz est un 404, et un chemin connu avec une autre méthode un 405 ; aucun n'atteint Bot Protection, donc le chemin n'est jamais un proxy ouvert. À Korzen, le forwarder envoie les en-têtes du navigateur d'une courte liste (User-Agent, Accept, Accept-Language, Content-Type, Origin, Sec-Fetch-*, les client hints, If-None-Match), votre clé API comme bearer et l'adresse du visiteur. Jamais un cookie, l'Authorization du navigateur ni un X-Forwarded-*. Ce qui revient garde les en-têtes de cache et de sécurité de Korzen, jamais un Set-Cookie.

Korzen n'accepte l'adresse du visiteur que d'une requête sur ces routes qui porte votre clé API, et sur le beacon que pour votre propre site key ; tout le reste est refusé en 403. Votre clé n'atteint jamais le navigateur.

Le cookie _kz du tag et son stockage restent sur votre domaine, comme avant ; les données que Korzen reçoit sont les mêmes. Quelques preuves que Korzen ne lit que quand le navigateur se connecte directement à Korzen manquent à travers un forwarder ; le reste des preuves du tag arrive, et rien de ce qui manque ne compte contre un visiteur.

Un autre chemin

firstPartyPath (Go : Options.FirstPartyPath ; le Worker : KORZEN_FIRST_PARTY_PATH) nomme un autre chemin, par exemple quand une liste de blocage apprend /kz. Il commence par /, finit sans, et compte au plus 64 caractères en segments de A-Z a-z 0-9 . _ ~ -. Un chemin illisible est consigné une fois dans le log, et le mode reste désactivé. Utilisez le même chemin dans le src du tag, dans le SDK et dans le Worker.

Fail-open

Le chemin ne retient jamais votre page ni vos autres routes :

  • Chaque requête transmise abandonne d'elle-même : après 1 s pour un GET, 2,5 s pour un POST. Un timeout, une erreur réseau, une réponse de plus de 4 Mio ou une panne du forwarder donne un 503 avec un corps vide et Cache-Control: no-store ; les redirections ne sont pas suivies. Les 4xx et 5xx de Bot Protection lui-même passent tels quels.
  • Sans l'adresse du visiteur (l'option ip n'en lit aucune), le nonce, le beacon et le défi répondent 503 ; le tag et les fichiers de la page de défi sont toujours servis.
  • Le tag abandonne alors ce signal, comme quand l'hôte de Korzen est en panne. Un tag qui n'a rien envoyé n'est jamais une preuve de bot : le visiteur est jugé sur ce que votre serveur envoie.

Comment savoir que ça marche

  • curl -I https://shop.example/kz/tag.js répond 200, et curl -I https://shop.example/kz/ping répond 204.
  • Dans les outils de développement du navigateur, sur une page avec le tag, le panneau réseau montre /kz/tag.js, /kz/n, /kz/ping et /kz/s sur votre propre domaine, répondus 200 ou 204, et aucune requête vers l'hôte de Korzen.
  • Passez par une action protégée, puis cliquez sur Vérifier votre intégration sur la page Configuration : Le token du tag dans cet appel indique Vu. Vos décisions apparaissent comme avant sur la page Décisions du tableau de bord.
  • Avec une Content-Security-Policy, l'hôte de Korzen peut en sortir : script-src 'self'; connect-src 'self'; worker-src blob: suffit pour le tag. Le tag

Erreurs fréquentes

  • Le tag encore chargé depuis l'hôte de Korzen : le mode sert le chemin, mais seul le src du tag y envoie le navigateur.
  • Un body parser avant le forwarder dans Express : le corps du beacon est lu avant que le forwarder le reçoive. Montez-le avant tout parser, ou sur le chemin seul.
  • Le Worker avec KORZEN_FIRST_PARTY mais sans route pour /kz/* : les requêtes atteignent votre origine, qui répond 404 sauf si le SDK y sert le chemin.
  • Deux chemins : le Worker sur l'un et le SDK sur un autre. Le SDK envoie alors les visiteurs défiés vers un chemin que le Worker ne sert pas.
  • Appeler /v1/decide à la main : son challenge.url est toujours l'hôte de Korzen. Seuls les SDK et le Worker envoient le visiteur vers votre chemin.