The problem
A Content Security Policy (CSP) tells the browser which sources of scripts, styles, images, fonts and frames a page may use. A strict policy is one of the most effective defences against injected content: if an attacker manages to place markup on a page, the browser still refuses to run or apply anything the policy does not allow.
The difficulty is that modern front-end code tends to rely on exactly what a strict policy forbids. React components set style={{ … }} attributes, animation libraries write inline styles, UI kits insert <style> elements, and third-party widgets inject both scripts and styles. Many sites end up adding 'unsafe-inline' to style-src (and sometimes script-src), which removes much of the protection.
The PrajnaBytes website is a server-rendered React application (Laravel, Inertia and React). We wanted a policy with no 'unsafe-inline' anywhere.
The approach
Every response from the website carries this policy (the Cloudflare address is added only while the Turnstile form check is switched on):
default-src 'self';
script-src 'self' [https://challenges.cloudflare.com];
style-src 'self' 'nonce-<random per request>';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-src 'self' [https://challenges.cloudflare.com];
form-action 'self';
frame-ancestors 'self';
base-uri 'self';
object-src 'none'
Four rules make it work:
- Styles come from stylesheets. Components use class names from the compiled stylesheet, never
styleattributes. An automated test fails if a public page component containsstyle={. - One nonce for library styles. A few UI primitives (for example the dialog's scroll lock) insert their own
<style>element. A random nonce, generated for each request, is placed on those elements and instyle-src, so they are allowed and nothing else inline is. - Dynamic values through the CSSOM. Where a value is only known in the browser, such as the pointer position for a hover effect, the code sets a CSS custom property with
element.style.setProperty('--x', value)and the stylesheet usesvar(--x). MDN's documentation ofstyle-srcnotes that setting thestyleattribute orcssTextis blocked under such a policy, while properties set directly on an element'sstyleobject are not. - No external origins. Fonts are self-hosted, scripts are bundled, and images come from the site itself (or
data:URIs for small decorations), so'self'is enough almost everywhere.
How it works in practice
Some patterns we used instead of inline styles:
- Bar charts in the administration console are native
<meter>elements styled in CSS, not<div>s with a computedwidth. - The page-load progress bar library is configured not to inject its own CSS; its styles are in the main stylesheet.
- Illustrations are CSS background images on classes generated into the stylesheet by a command, not
style="background-image:…". - Generated content for non-HTML documents, such as the human view of the XML sitemap, uses a static
.cssfile, because the policy would block a<style>element there too. - Structured data for search engines is written as
<script type="application/ld+json">. It is a data block that the browser does not execute, so it needs no exception inscript-src.
Our automated browser audit loads every public page and fails on console errors, which include CSP violations, so a regression shows up before release.
Trade-offs
- Discipline in every component. A single
style={…}in a new component breaks the page's appearance under the policy. The automated check catches this for page components; shared components rely on review. - Per-request nonces. A nonce must change on every response, so HTML pages that carry it should not be served as identical copies from a shared cache.
- Third-party widgets. Each external widget needs its own entries in the policy (here, Cloudflare Turnstile in
script-srcandframe-src). Some widgets require'unsafe-inline'and cannot be used without weakening the policy; we choose widgets with that in mind. - A policy is not a complete defence. CSP limits the damage of injected markup. It does not replace output escaping, input validation or keeping dependencies up to date, and a script from an allowed origin can still change the page.
Servicios de PrajnaBytes relacionados
We build web applications with a strict policy from the start (Software and Business Systems), and can review and harden the security headers of an existing site (Technology Operations).
- Software and Business Systems
Custom web and mobile applications and business systems that replace manual processes, disconnected spreadsheets and ageing software.
- Technology Operations
Hosting, domains, certificates, maintenance, monitoring, backup and security support, so that business systems stay available and maintained.
Solicitar una revisión de sus sistemas
Would a strict policy break your website today? We can review its headers and the scripts it loads.
La lectura es libre y no requiere ningún dato. Solo pedimos datos de contacto si decide escribirnos.
Sobre esta publicación
Cómo se elaboró. Drafted with AI assistance from our engineering records (the security-header middleware, front-end code and tests), then edited.
Revisión. Checked on 5 November 2026 against the policy the live website sends, its source code and automated tests, and the MDN documentation cited below.
Referencias
- Content Security Policy (CSP). MDN Web Docs. Accessed 5 November 2026.
- Content-Security-Policy: style-src directive. MDN Web Docs. Accessed 5 November 2026.
- Content Security Policy Cheat Sheet. OWASP Cheat Sheet Series. Accessed 5 November 2026.