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.
خدمات PrajnaBytes ذات الصلة
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.
اطلب مراجعة لأنظمتك
Would a strict policy break your website today? We can review its headers and the scripts it loads.
القراءة مجانية ولا تتطلب أي بيانات. لا نطلب بيانات الاتصال إلا إذا قررت مراسلتنا.
عن هذا المنشور
كيف أُعدّ. Drafted with AI assistance from our engineering records (the security-header middleware, front-end code and tests), then edited.
المراجعة. Checked on 5 November 2026 against the policy the live website sends, its source code and automated tests, and the MDN documentation cited below.
المراجع
- 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.