Terug naar blog
Implementatie20 augustus 20268 min leestijdBijgewerkt 30 augustus 2026

Content Security Policy voor website-chatbots: Widget, API, afbeeldingen en streaming veilig toestaan

Een praktische CSP voor website-chatbots staat alleen de echt benodigde scripts, API-verbindingen, streams en afbeeldingen toe - zonder onnodige wildcards.

Een volwassen evenemententechnicus controleert op een zomers buitenpodium een beveiligde aansluitmodule voor een chatbot-widget.

Een website-chatbot bestaat in de browser zelden uit slechts één enkel JavaScript-bestand. Een loader opent de widget, een API ontvangt berichten, antwoorden komen terug als stream en profielafbeeldingen of media staan mogelijk op een ander domein. Een Content Security Policy (CSP) maakt deze routes zichtbaar en beperkt ze: de browser laadt of verbindt alleen wat de website uitdrukkelijk toestaat.

Dat is een belangrijke tweede beschermingslaag tegen Cross-Site Scripting en onverwachte inhoud van derden. Een CSP herstelt echter geen onveilige API, ontbrekende authenticatie, gebrekkige invoervalidatie of prompt injection. Het vermindert de mogelijkheden van geïnjecteerde code en beperkt de impact van een fout. Beslissend is daarom een zo klein mogelijke, geteste policy in plaats van een lange lijst met algemeen vrijgegeven domeinen.

Waarom chatbot-widgets speciale CSP-regels nodig hebben

Bij een klassieke pagina met inhoud zijn bronnen van de eigen herkomst vaak voldoende. Een chatbot blijft daarentegen communiceren na het laden. connect-src regelt onder andere fetch(), XMLHttpRequest, EventSource, WebSocket en sendBeacon(). Precies hier lopen berichten, streaming-antwoorden, feedback-gebeurtenissen en eventuele telemetrie. Ontbreekt de juiste herkomst, dan verschijnt de widget wel, maar kan deze niet antwoorden.

Andere onderdelen vallen onder eigen directieven. script-src beslist over de widget-loader, img-src over avatars en antwoordafbeeldingen, style-src over stylesheets en font-src over externe lettertypen. Een op iframe gebaseerde widget heeft aanvullend frame-src nodig. default-src dient als terugvaloptie voor veel niet expliciet genoemde bronntypen, maar vervangt geen bewuste inventarisatie.

Het belangrijkste voorbereidende werk vindt daarom niet plaats in de CSP-generator, maar in de browser: open een representatieve pagina, start een gesprek, laat een lang antwoord streamen, open bronnen, verstuur feedback en test fout- en handoff-situaties. In het netwerkpaneel ziet u de daadwerkelijk aangesproken origins. Documenteer voor elke host het doel, het brontype en een verantwoordelijke persoon.

De vier relevante dataroutes afzonderlijk vrijgeven

1. Widget-script en initialisatie

Haal de loader liefst van een stabiel, geversioneerd adres. Een vrijgave zoals script-src https: zou te breed zijn, omdat daarmee scripts van elk HTTPS-domein toegestaan worden. Sta in plaats daarvan de exacte CDN-origin toe of serveer de loader zelf. Als de integratie inline-code vereist, gebruik dan een nonce die per HTTP-antwoord nieuw wordt gegenereerd of een passende hash. 'unsafe-inline' moet geen snelle permanente oplossing worden.

Een nonce hoort alleen bij scripts die de server-side template zelf genereert. Middleware die blind aan elke bestaande script-tag dezelfde nonce toevoegt, zou ook vertrouwen geven aan geïnjecteerde tags. Voor een statisch, geversioneerd script van derden kan Subresource Integrity aanvullend helpen; bij vaak wisselende bestanden moet de hash echter gecontroleerd worden bijgewerkt.

2. API, Server-Sent Events en WebSocket

Normale POST-verzoeken en een via fetch() gestreamd antwoord vereisen de HTTPS API-origin in connect-src. Server-Sent Events via EventSource vallen hier eveneens onder. Voor een WebSocket voegt u de concrete wss://-origin expliciet toe. MDN wijst erop dat 'self' WebSocket-schema's niet in alle browsers automatisch omvat. Een eigen directief genaamd stream-src bestaat niet.

CSP en CORS lossen verschillende taken op. CSP bepaalt waar de pagina in de eerste plaats verbinding mee mag maken; CORS bepaalt aan de serverzijde welke origins een antwoord in de browser mogen lezen. Een CSP-vrijgave verhelpt daarom geen CORS-fout of een verlopen toegangstoken. Een same-origin proxy kan de policy vereenvoudigen, maar moet authenticatie, rate limits, timeouts en foutafhandeling nog steeds correct verwerken.

3. Afbeeldingen, avatars en gegenereerde media

Sta in img-src alleen de eigen origin en de daadwerkelijk gebruikte media-origin toe. data: is alleen nodig als de widget kleine ingebedde afbeeldingen gebruikt; blob: alleen als de browser afbeeldingen daadwerkelijk als blob-URL genereert. Elke extra bron vergroot het aanvalsoppervlak. Als een afbeelding eerst via fetch() wordt geladen en daarna wordt omgezet in een blob-URL, kunnen zowel connect-src als img-src betrokken zijn.

Test niet alleen de standaardavatar. Controleer voorbeeld-afbeeldingen, bron-screenshots, bijlagen, dark mode en de foutweergave voor niet-beschikbare media. URL-parameters kunnen vertrouwelijke informatie in CSP-rapporten overbrengen; reporting-eindpunten moeten rapporten daarom datazuinig verwerken en niet onbeperkt bewaren.

4. iframe, styles, fonts en optionele workers

Een widget die rechtstreeks in de DOM is ingebed, heeft meestal geen extern frame nodig. Dan kan frame-src 'none' blijven staan. Draait de chat daarentegen in een iframe, sta dan uitsluitend de exacte origin daarvan toe. Dit moet worden onderscheiden van frame-ancestors: dit directief bepaalt op de geleverde bron welke pagina's deze mogen inbedden. De widget-aanbieder moet dit daarom op zijn iframe-antwoord passend instellen.

Bij styles en fonts geldt hetzelfde principe. Sta concrete hosts toe en vermijd 'unsafe-inline' voor zover de integratie dat toelaat. Workers of audiofuncties worden alleen toegevoegd als het product ze echt gebruikt. Een voorzorgsvrijgave van blob:, gehele wildcard-domeinen of willekeurige mediabronnen bemoeilijkt latere audits.

Een realistisch CSP-voorbeeld voor een chatbot-widget

De volgende domeinen zijn bewust gereserveerde voorbeeld-domeinen. Vervang ze door de origins uit uw eigen netwerkanalyse. Het voorbeeld gaat uit van een externe loader, een HTTPS-API, een gescheiden WebSocket voor streaming en een media-host. Het gebruikt geen algemene wildcards:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.chat.example 'nonce-{RANDOM}';
  connect-src 'self' https://api.chat.example wss://stream.chat.example;
  img-src 'self' data: https://media.chat.example;
  style-src 'self' 'nonce-{RANDOM}';
  font-src 'self';
  frame-src 'none';
  worker-src 'self';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'self';
  form-action 'self';
  upgrade-insecure-requests;

{RANDOM} staat voor een sterke waarde die per antwoord opnieuw wordt gegenereerd en die in de header en op de toegestane script- respectievelijk style-elementen identiek is. Gebruikt uw widget een iframe, vervang dan frame-src 'none' door de precieze widget-origin. Gebruikt het uitsluitend HTTPS-streaming via fetch() of EventSource, dan vervalt de WebSocket-origin. Verwijder elke bron die na een volledige functietest niet nodig blijkt te zijn.

De policy is een praktisch uitgangspunt, geen universeel sjabloon. Een moderne strikte CSP kan scripts nog sterker regelen via nonces of hashes en 'strict-dynamic'. Of dat zonder compatibiliteitsproblemen mogelijk is, hangt af van de manier waarop de loader verdere scripts genereert. Verhelder dit proces met de aanbieder en test browsers, consent-modus en deployment-varianten.

Van Report-Only naar een afgedwongen policy

Schakel een nieuwe policy niet ongecontroleerd scherp. Het W3C-mechanisme Content-Security-Policy-Report-Only meldt overtredingen zonder bronnen te blokkeren. Zo ontdekt u vergeten afbeelding-hosts, een afwijkende streaming-origin of inline-code voordat gebruikers er last van hebben. OWASP beveelt de HTTP-header aan als de voorkeursroute voor levering; in tegenstelling tot het meta-element ondersteunt deze ook de volledige functionaliteit.

  1. Inventarisatie maken: Test het starten van de widget, het eerste bericht, een lang streaming-antwoord, bronnen, afbeeldingen, feedback, handoff en consent-wissels op meerdere paginatypes.
  2. Report-Only uitrollen: Begin met de geplande strikte policy en verzamel overtredingen gedurende een beperkte periode. Filter browser-extensies en andere niet-reproduceerbare stoorsignalen uit.
  3. Elke host onderbouwen: Breid de policy alleen uit als een concrete productfunctie de origin nodig heeft. Vermijd wildcards als reactie op afzonderlijke meldingen.
  4. Geautomatiseerd testen: Voeg end-to-end-tests toe die een bericht versturen, wachten op streaming en een afbeelding laden. Controleer tegelijkertijd de browserconsole op CSP-overtredingen.
  5. Afdwingen en monitoren: Activeer de Content-Security-Policy-header, houd parallel een nog strengere Report-Only-variant in de gaten en vergelijk foutpercentages.

Een gefaseerde uitrol past goed bij een chatbot in shadow mode. Voor streaming-specifieke meetwaarden helpt het artikel over latentiebudgetten en timeouts. CSP-overtredingen moeten daarbij als een afzonderlijk signaal gelden: een timeout en een geblokkeerde verbinding vereisen een verschillende oorzakenanalyse.

Typische foutconfiguraties

  • Te brede bronnenlijsten: *, https: of grote wildcard-domeinen maken de policy handig, maar zwak en moeilijk te auditeren.
  • Alleen de zichtbare start wordt getest: De widget opent zich, maar streaming, feedback, afbeeldingen of handoff mislukken pas later.
  • 'unsafe-inline' blijft permanent staan: Een tijdelijk compatibiliteitshulpmiddel wordt niet vervangen door nonces, hashes of externe code.
  • CSP wordt verward met toegangscontrole: De policy vervangt geen server-side rechten, geen sessiecontrole en geen bescherming tegen misbruik van tool-calls.
  • Rapporten bevatten te veel gegevens: Volledige URL's, queryparameters of gebruikerscontext belanden onnodig lang in de monitoring.
  • Staging en productie lopen uiteen: Verschillende CDN-, API- of WebSocket-hosts worden pas zichtbaar na de go-live.

Zelfs een strikte script-src maakt een toegestane derde partij niet automatisch veilig: de JavaScript ervan draait met de mogelijkheden die uw pagina eraan geeft. Controleer daarom wissels van leverancier, nieuwe subdomeinen en loader-updates net als andere beveiligingsrelevante afhankelijkheden. Het artikel over prompt injection-bescherming vult deze browsergrens aan met regels voor RAG, tools en data.

Checklist voor de go-live

  • Zijn alle benodigde origins uit echte browsersessies gedocumenteerd en inhoudelijk onderbouwd?
  • Staat script-src alleen de loader en gecontroleerde scripts toe, zonder algemeen 'unsafe-inline'?
  • Bevat connect-src de exacte HTTPS-, EventSource- en eventuele WSS-origins?
  • Zijn bronnen voor afbeeldingen, styles, fonts, frames en workers gescheiden en zo strikt mogelijk gedefinieerd?
  • Worden nonces per antwoord opnieuw gegenereerd en alleen op vertrouwde elementen geplaatst?
  • Zijn consent-wissels, lange streams, afbeeldingen, fouten, handoff evenals desktop en mobiele apparaten getest?
  • Is de policy eerst gemonitord in Report-Only en daarna als header afgedwongen?
  • Worden CSP-rapporten verwerkt zonder onnodige persoonsgegevens of vertrouwelijke URL-data?
  • Is er na widget- of infrastructuurupdates een geautomatiseerde regressietest?

Conclusie

Een goede CSP voor website-chatbots is geen verzameling uitzonderingen, maar een technische landkaart van toegestane browserroutes. Scheid loader, API, streaming, afbeeldingen en iframe-bronnen, sta exacte origins toe en voer de policy eerst in Report-Only-modus in. Zo blijft de widget functioneel, terwijl onverwachte scripts en verbindingen aanzienlijk minder speelruimte krijgen.

Bronnen

Zet websitebezoeken om in betere gesprekken

Lanceer een AI-chatbot die vanaf dag één van waarde is

Train ChatReact met uw website, documenten en goedgekeurde feiten zodat bezoekers sneller antwoord krijgen en uw team minder repetitieve verzoeken ontvangt.

Gerelateerde artikelen

Verder lezen