Security
Cloudflare WAF-audit voor chat agents: checklist vóór livegang
We zagen twee chat-pilots stilletjes verkeer verliezen via Cloudflare's managed challenge. Dit is de WAF- en bot management-audit die we nu draaien vóór één bericht productie raakt.

De 14% die we niet zagen
Het was een dinsdagmiddag toen de marketinglead van een klant ons aantikte. De chat-widget op hun checkout-pagina deed het prima in tests. Gesprekken vanuit interne QA werkten. Het pilot-dashboard liet stabiel volume zien. Maar de checkout-conversie zakte een fractie en niemand kon uitleggen waarom.
We trokken de Cloudflare-analytics erbij. Van de circa 11.400 unieke bezoekers die dag kregen er 1.610 stilletjes een managed challenge voorgeschoteld vóórdat de chat-widget überhaupt laadde. De meesten kwamen er doorheen. Sommigen niet. Wie het niet haalde, zag de widget nooit. Wist niet eens dat hij bestond. Vanuit het dashboard van de chat-agent leek alles in orde.
Dit was de tweede pilot in twee maanden waar we hetzelfde patroon zagen. Andere branche, andere stack, zelfde oorzaak: een WAF-instelling afgestemd op de oude site filterde nu stilletjes het chat-pad. We schreven op wat we vooraf hadden moeten checken. Dit is die lijst.
Wat de audit echt afdekt
Cloudflare is geen product. Het is een stapel beveiligingslagen die elkaar overlappen, en elke laag kan een chat-agent op een eigen manier hinderen. De audit loopt er zes door, in volgorde, vóórdat we ook maar één token op productieverkeer richten.
Bot Fight Mode en zijn grote broer
Het eerste wat we bekijken: staat Bot Fight Mode of Super Bot Fight Mode aan. Bot Fight Mode is het botte instrument van de gratis tier. Het blokkeert alles wat scoort als waarschijnlijk geautomatiseerd. Het probleem: in die bak zitten een hoop legitieme dingen. Server-to-server webhooks vanuit je eigen backend, gestreamde responses van een LLM-provider die je via Workers proxy't, de fetch van de chat-widget zelf naar een stats-endpoint.
Super Bot Fight Mode (Pro-pakket en hoger) voegt knoppen toe. Je kunt onderscheid maken tussen Definitely automated, Likely automated, Verified bots en Static resource protection. Voor een chat-agent zetten we doorgaans de eerste op Block, de tweede op Managed Challenge, maar alleen op auth-gerelateerde paden, en we whitelisten expliciet onze eigen user agent op het streaming-endpoint.
Staat de echte Bot Management aan (de Enterprise-feature met de 1-99 score), dan samplen we de daadwerkelijke cf.bot_management.score-verdeling op het chat-endpoint over een paar uur echt verkeer voordat we live gaan. De docs van Cloudflare beschrijven de score, maar ze vertellen je niet hoe jouw specifieke widget eruitziet vanuit die optiek.
WebSockets en Server-Sent Events
De meeste moderne chat agents streamen tokens via WebSockets of SSE. Cloudflare ondersteunt beide, maar de defaults bijten.
Bij WebSockets kan de upgrade-request onderworpen worden aan de Browser Integrity Check, die headers inspecteert vóórdat de upgrade doorgaat. Connect je widget vanaf een iets oudere mobiele browser, dan kan de check afgaan en krijg je een 403 op de connection. De widget valt dan terug op polling, wat het streaminggevoel verpest en je tokenkosten per sessie verdrievoudigt.
Bij SSE zit het probleem in buffering. De edge van Cloudflare buffert responses om een complete chunk te verzamelen vóór doorsturen, met als gevolg dat tokens in klompjes binnenkomen om de paar seconden in plaats van één voor één. De oplossing: zet Cache-Control: no-cache, no-transform en X-Accel-Buffering: no op de response, en verifieer met een curl buiten het Cloudflare-netwerk om.
curl -N -H "Accept: text/event-stream" \
-H "Authorization: Bearer $TOKEN" \
https://chat.example.com/api/streamKomen tokens regel per regel binnen op het moment dat ze geproduceerd worden, dan zit je goed. Komen ze in batches, dan zit er ergens op het pad een buffer.
Rate limiting-regels
Rate limiting is de op één na grootste bron van stille failures. De default-regels die een Cloudflare-account meekrijgt zijn afgestemd op HTTP-pageloads, niet op chat-sessies waar één gebruiker in een minuut twintig kleine POST-requests kan afvuren.
We kijken naar drie dingen. Eén: zijn er account-level rate-limiting-regels die /api/* of welke namespace je chat ook gebruikt afdekken. Twee: wat is de threshold en wat het venster. Drie: wat doet de regel bij een hit, loggen, challengen of blokkeren.
Een chat-agent die een typing indicator stuurt, een bericht verzendt, polled voor tool-call-resultaten en daarna een follow-up vraagt, kan zonder moeite een regel van dertig requests per minuut per IP overschrijden vanaf een corporate NAT waar veertig medewerkers één egress delen. De fix is meestal: scope de regel op session cookie in plaats van IP, of sluit het chat-pad uit van de globale regel en schrijf een chat-specifieke regel met een hoger plafond.
# Cloudflare rate limiting rule expression
(http.request.uri.path matches "^/api/chat/"
and not cf.client.bot)
# Threshold: 120 requests per 60s, per session cookieManaged challenges op de widget-origin
De widget zelf laadt meestal vanaf een aparte origin. Een subdomein als chat.example.com, een Cloudflare Pages-deployment, of de CDN van je vendor. We checken de WAF-regels op de origin waar het widget-script vandaan komt, los van de hoofdsite.
Hier zat de 14% van de eerste pilot. De chat-widget werd geserveerd vanaf een chat.-subdomein met een agressievere WAF-instelling dan de hoofdsite. Wie op een stale mobiele browser, een tracking-protection-extensie of een corporate VPN zat die JavaScript stript, kreeg een managed challenge voorgeschoteld vóór de widget laadde. Ze sloten het tabblad. Het dashboard zag niets, want de widget had het bezoek nooit geregistreerd.
Een managed challenge is onzichtbaar voor je analytics. Als je chat-dashboard sessies pas telt zodra de widget initialiseert, zie je de gebruikers die gechallenged werden vóórdat de widget ooit laadde niet. Cross-reference altijd Cloudflare's Security Events met de sessietellingen van de widget over hetzelfde venster.
Cache-regels en de POST-val
Cloudflare cachet POST-requests standaard niet, maar de response op een GET met hetzelfde pad als een POST-endpoint wel. Doet je widget een GET /api/chat/session om te bootstrappen en daarna POST /api/chat/session om een bericht te sturen, dan kan een agressieve Cache Rule een stale bootstrap teruggeven aan een nieuwe gebruiker, inclusief het token van een andere sessie.
We checken elke Cache Rule, Page Rule en Tiered Cache-configuratie die de chat-namespace raakt. Alles wat Cache Everything zegt op een pad dat auth of session state afhandelt, wordt herschreven of weggehaald vóór de launch.
We checken ook Transform Rules. Heeft het team een regel remove sensitive headers aan de response-kant, dan strip die mogelijk X-Accel-Buffering: no en haalt zo stilletjes het SSE-bufferprobleem hierboven terug.
Logs die je daadwerkelijk kunt lezen
De laatste stap: verifiëren dat we, zodra het verkeer live staat, kunnen zien wat er gebeurt. Dat betekent Logpush aanzetten (of op zijn minst bevestigen dat het team toegang heeft tot Security Events voor de relevante zone), filteren op de chat-hostname, en een alert opzetten op de verhouding challenges versus schone requests.
Onze regel: blijft het challenge-volume op het chat-pad langer dan vijftien minuten boven twee procent van de totale requests, dan gaat er iemand uit bed. Daaronder behandelen we het als achtergrondruis. Daarboven is er ergens een regel die niet klopt.
De pre-launch checklist zelf
We draaien dit als een letterlijke checklist in een gedeelde doc, afgevinkt met naam erbij vóórdat we DNS omzetten of de widget op het live endpoint richten.
- Bot Fight Mode- en Super Bot Fight Mode-instellingen gedocumenteerd en getest tegen het chat-pad.
- WebSocket-upgrade geverifieerd vanaf minstens twee browsers (één mobile Safari, één desktop Firefox) en één curl.
- SSE-endpoint streamt zonder buffer, bevestigd met
curl -Nvan buiten Cloudflare. - Rate limiting-regels dekken het chat-pad expliciet af, scoped op session cookie, met thresholds die passen bij het verkeerspatroon van de agent.
- Volume aan managed challenges op de widget-origin gemeten over een venster van 24 uur vóór livegang, met een schatting van het uitvalpercentage onder sessies die een challenge kregen.
- Cache-regels doorgelicht, geen Cache Everything-regel die de chat-namespace raakt.
- Toegang tot Logpush of Security Events bevestigd, met een alert op de challenge-ratio.
Na livegang: de 48-uurs wacht
De audit is niet het einde. De eerste 48 uur nadat je live verkeer op de agent richt, daar vind je de regels die in staging prima leken en in productie breken. We houden iemand parallel op het chat-dashboard en op Cloudflare Security Events tijdens die twee dagen. Ongeveer de helft van de keren passen we een regel aan waarvan we dachten dat hij veilig was.
De twee patronen die we het vaakst zien: een Verified bots-toestemming die de IP's van je LLM-provider toch niet blijkt af te dekken (zodat de widget bij de eerste webhook-validatie wordt geblokkeerd), en een managed challenge die uitgedeeld wordt aan authenticated gebruikers uit een specifieke regio omdat hun IP-blok een lage Cloudflare-reputatiescore heeft. Beide los je op in vijf minuten, zodra je ze ziet.
Het kleinste wat je vandaag kunt doen
Heb je al een chat-widget in productie achter Cloudflare en heb je deze audit nog niet gedaan, open dan Security Events voor je widget-hostname, filter op de laatste 24 uur, en bekijk de verhouding managed challenges versus schone requests op het pad waar de widget vandaan laadt. Zit je boven twee procent, dan laat je echte gesprekken liggen.
Toen we eerder dit jaar de chat-agent voor een Nederlandse retailer via Cloudflare aansloten, liepen we tegen een Bot Fight Mode-regel aan die het vorige bureau een jaar lang had laten staan. We hebben dat opgelost door de chat-namespace naar een eigen subdomein te verhuizen met een schone WAF-instelling en de protecties één regel tegelijk weer aan te zetten, het soort werk dat onder onze AI-agents-praktijk valt.
Kern
Een Cloudflare WAF-instelling die op je oude site is afgestemd, filtert stilletjes je chat-agent. Doe een audit van bot-regels, WebSocket-upgrades en challenge-ratio's vóór livegang, niet erna.
FAQ
Waarom breekt Cloudflare's managed challenge chat-widgets?
Hij laadt vóór het widget-script en vraagt de browser om een JavaScript-puzzel op te lossen. Oudere mobiele browsers, tracking-protection-extensies en corporate VPN's die JS strippen kunnen er stilletjes op falen.
Kan ik Bot Fight Mode aan laten staan met een chat-agent?
Alleen Super Bot Fight Mode met expliciete allow-regels voor je chat-pad en de webhook-IP's van je LLM-provider. Bot Fight Mode op de gratis tier is te bot voor streaming chat.
Hoe test ik of SSE-responses gebufferd worden?
Draai curl met de -N flag en een Accept: text/event-stream header tegen het streaming-endpoint vanaf buiten het Cloudflare-netwerk. Tokens horen regel voor regel binnen te komen zodra ze geproduceerd worden.
Wat is een veilige rate limit voor een chat-endpoint?
Scope op session cookie, niet op IP. De meeste agents hebben 90 tot 150 requests per minuut per sessie nodig om typing indicators, tool calls en follow-ups te verwerken zonder false positives op corporate NAT.