We Do Dev Work
We Do Dev Work
DevOpsCase studyHostingWebsites
• 28 Oct 2026

Een Content-Security-Policy toevoegen zonder je site kapot te maken

Een Content-Security-Policy toevoegen zonder je site kapot te maken

Een beveiligingsscan, een Lighthouse-rapport of je hostingpartij meldt dat je site geen Content-Security-Policy heeft. De oplossing lijkt één header. Kopieer een beleid van een blog, zet het live, klaar. Daarna laadt het boekingsformulier niet meer, verdwijnt de chatwidget of, erger, breekt er zichtbaar niets en tellen je Google Ads geen conversies meer. Op onze eigen site deden we bijna precies dat.

Het korte antwoord: begin met Content-Security-Policy-Report-Only, niet met de echte header. Die meldt wat het beleid zou blokkeren, zonder te blokkeren. Verzamel die meldingen minstens een week van echte bezoekers, op elk type pagina, voeg toe wat legitiem is en schakel pas daarna over op afdwingen.

Wat een Content-Security-Policy doet

Het is een HTTP-header die de browser vertelt van welke bronnen een pagina scripts, stijlen, afbeeldingen, lettertypen, frames en verbindingen mag laden. Lukt het een aanvaller een script te injecteren (via een gehackte plugin, een gecompromitteerd script van een derde of een formulier dat invoer niet schoonmaakt), dan weigert de browser het uit te voeren of data naar een onbekende server te sturen. Het is een van de sterkste verdedigingen tegen cross-site scripting en kaartskimmers op checkouts.

Geen CSP hebben komt vaak voor. Toen we 1.482 webshops scanden vlak voor Black Friday, kreeg ongeveer een derde een melding voor een ontbrekende Content-Security-Policy.

Waarom "gewoon de header toevoegen" sites breekt

Een beleid is een whitelist. Alles wat je vergeet wordt geblokkeerd, onzichtbaar voor de bezoeker, met alleen een regel in de browserconsole. De gebruikelijke slachtoffers:

  • Analytics- en advertentietags. Google Analytics, Google Ads, Meta Pixel en Microsoft Clarity laden van meerdere hosts en sturen vaak door naar nog meer.
  • Embeds. Boekingstools (Calendly), kaarten, YouTube, chatwidgets en betaal-iframes hebben elk hun eigen frame- en scriptbronnen nodig.
  • Inline scripts. Veel frameworks en CMS'en zetten kleine scripts direct in de pagina. Een strikt beleid blokkeert ze, tenzij je 'unsafe-inline' toestaat of een nonce toevoegt (een willekeurige waarde per paginaweergave).
  • Pagina's die niemand test. De contactpagina, de bedankpagina na een formulier, de checkout. Op die pagina's laden vaak juist de conversie- en betaalscripts.

Wat er op onze eigen site gebeurde

Onze site draait sinds juli 2026 met een report-only beleid. De eerste versie bouwden we op basis van een volledige netwerktrace van productie, niet op een gok. Dit is wat de trace en de rapporten opvingen. Elk daarvan had iets gebroken als we vanaf dag één hadden afgedwongen:

  • De Google Ads-conversieping gaat alleen af op de contactpagina. Een check van de homepage ziet hem nooit. Afdwingen zonder die host had Ads onze belangrijkste conversie doen missen: een contactaanvraag.
  • Microsoft Clarity stuurt door naar een Bing-host. *.clarity.ms toestaan is niet genoeg; de ID-sync springt naar c.bing.com.
  • De Ads-remarketingpixel gebruikt het landendomein van de bezoeker, zoals google.be of google.co.th, niet google.com. CSP heeft daar geen wildcard voor, dus we noemen de landen waar we verkopen en accepteren een klein gat in de doelgroepdata voor de rest.
  • Op elke pagina verscheen een tweede Ads-host. Op 19 oktober 2026 liet een nieuwe controle van zes paginatypen zien dat pagead2.googlesyndication.com overal laadde. Die stond niet in de trace van juli. Onze eigen beveiligingsscan had gezegd dat het beleid er goed uitzag en dat we de header "gewoon konden hernoemen". Hadden we dat gedaan, dan waren de Ads-conversies naar nul gezakt zonder één zichtbare fout.

Twee kleinere lessen uit hetzelfde werk. Chrome negeert upgrade-insecure-requests in een report-only beleid en logt daar een consolefout over, die Lighthouse dan tegen je telt. Voeg die directive dus pas toe als je overschakelt op afdwingen. En ons framework (SvelteKit) schrijft een inline hydratiescript, terwijl onze hosting alleen vaste headers toestaat. Daarom blijft 'unsafe-inline' voor scripts staan tot we nonces per verzoek toevoegen. Een beleid met een bekend, gedocumenteerd compromis blokkeert nog altijd veel meer dan geen beleid.

Zo rol je een CSP uit zonder iets te breken

  1. Maak een lijst van wat je pagina's laden. Open de developer tools van de browser, tabblad Network, en klik door de homepage, een contentpagina, de contact- of boekingspagina, de checkout en de bedankpagina. Noteer elke host.
  2. Schrijf het beleid op basis van die lijst. Begin met default-src 'self' en voeg elke host toe aan de directive waar hij voor nodig is: script-src, connect-src, img-src, frame-src, style-src, font-src.
  3. Zet het live als report-only, met rapportage. Stuur het als Content-Security-Policy-Report-Only met zowel report-to (plus een Reporting-Endpoints-header) als het oudere report-uri. Niet elke browser ondersteunt report-to al, dus gebruik beide. Log alleen de directive, de geblokkeerde host en het pagina-pad: volledige URL's kunnen bezoekersdata bevatten.
  4. Lees de rapporten een week lang. Echte bezoekers gebruiken andere browsers, extensies, landen en pagina's dan jij. Voeg toe wat van jou of je tools is; negeer ruis van browserextensies.
  5. Controleer opnieuw na elke tagwijziging. Marketingtools veranderen van host zonder het te zeggen. Loop dezelfde paginatypen opnieuw langs na elke wijziging in de tags of het beleid.
  6. Schakel over op afdwingen. Hernoem de header naar Content-Security-Policy, houd de rapportage aan en kijk de volgende dagen naar de rapporten en je conversiecijfers.
Jouw situatieWat je doet
Scan zegt "geen CSP", site werkt primaBegin deze week met report-only; kopieer geen beleid van elders
Je hebt een CSP toegevoegd en iets werkt niet meerZet de header nu terug naar report-only en herstel het beleid aan de hand van de consolefouten
Je draait Google Ads of Meta-advertentiesTest de pagina's waar conversies afgaan en controleer de conversiecijfers een paar dagen na het afdwingen
Webshop met een checkoutTest een echte betaling (in testmodus) met het beleid actief; betaal-iframes hebben eigen bronnen nodig
WordPress met veel pluginsReken op een lange lijst; plugins die inline scripts injecteren kunnen voorlopig 'unsafe-inline' afdwingen

Hulp nodig?

Wij zetten een Content-Security-Policy op de veilige manier op: een trace van je echte pagina's, een report-only fase met logging en de overstap naar afdwingen zodra de rapporten schoon zijn. Je analytics en conversies controleren we ervoor en erna. Stuur ons je websiteadres →

Run je een bureau? Wij doen dit onder jouw naam, onder NDA. Zie hoe we met bureaus werken →

Gerelateerd

Bron: MDN, Content-Security-Policy-Report-Only.

Veelgestelde vragen

Wat is Content-Security-Policy-Report-Only?

Een versie van de CSP-header die alleen meldt wat het beleid zou blokkeren, zonder te blokkeren. Browsers sturen de overtredingen naar het rapportadres dat je instelt, zodat je het beleid kunt aanpassen voordat je het afdwingt.

Hoe lang moet een CSP in report-only blijven?

Minstens een week met echt verkeer en geen legitieme bronnen meer in de rapporten. Langer als je site pagina's heeft met weinig bezoekers, zoals een checkout of een bedankpagina.

Moet ik report-to of report-uri gebruiken?

Allebei. report-to is de nieuwere en heeft een Reporting-Endpoints-header nodig. report-uri is verouderd, maar nog nodig omdat report-to nog niet in elke browser werkt.

Kan een CSP de tracking van Google Ads of Analytics breken?

Ja. Ontbreken de hosts waar de tags van laden of waarheen ze data sturen, dan blokkeert de browser ze zonder zichtbare foutmelding. Conversies worden dan gewoon niet meer geteld.

Is een CSP met 'unsafe-inline' nutteloos?

Nee. Het beperkt nog steeds waar scripts, frames en verbindingen vandaan mogen komen en waarheen data mag gaan. 'unsafe-inline' vervangen door nonces is beter, maar een gedocumenteerd compromis is beter dan geen beleid.

Een developerprobleem?

Je hoeft niet precies te weten wat er mis is. Dat is juist het punt.

Stuur het signaal