July 6, 2026
Content Security Policy in Practice: How to Reduce Risks in Modern Web Applications
When we talk about front-end security, many people first think about authentication, authorization, tokens, encryption, or back-end…

By Juliano Pereira de Souza
3 min read
When we talk about front-end security, many people first think about authentication, authorization, tokens, encryption, or back-end validations.
All of that is important. But there is another layer, often overlooked, that runs directly in the user's browser: Content Security Policy, or simply CSP.
CSP is a policy sent by the server to the browser to restrict where an application can load scripts, styles, images, fonts, iframes, connections, and other resources from. In practice, it reduces the attack surface and helps mitigate risks such as XSS, script injection, improper loading of external resources, and accidental use of untrusted dependencies.
The Problem
Modern web applications depend on many external resources:
Google Fonts, CDNs, analytics tools, marketing scripts, third-party libraries, APIs, remote images, iframes, widgets, integrations, and bundles generated by frameworks such as Angular, React, Vue, or Next.js.
Without a clear policy, the browser tends to accept much more than it should.
This means that if a vulnerability allows a malicious script to be injected, an attacker may try to execute code within the context of the application. Depending on the scenario, this could lead to session theft, interface manipulation, sensitive data capture, or unwanted redirects.
CSP does not replace good practices such as validation, sanitization, encoding, secure authentication, and dependency review. It works as defense in depth.
A Poor CSP Example
Many applications start with something like this:
Content-Security-Policy: default-src *; script-src * 'unsafe-inline' 'unsafe-eval';Content-Security-Policy: default-src *; script-src * 'unsafe-inline' 'unsafe-eval';This removes much of the value of the policy.
Using *, 'unsafe-inline', and 'unsafe-eval' allows broad script execution and makes protection against XSS much weaker.
A Safer Starting Point
A more controlled initial policy could look like this:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.yourcompany.com;
frame-ancestors 'none';
object-src 'none';
base-uri 'self';
form-action 'self';Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.yourcompany.com;
frame-ancestors 'none';
object-src 'none';
base-uri 'self';
form-action 'self';This policy already changes the security posture of the application.
It tells the browser to:
- load resources by default only from the same origin;
- allow scripts only from the application itself;
- limit external connections to expected APIs;
- block old plugin-based content with
object-src 'none'; - prevent the page from being embedded in unauthorized iframes with
frame-ancestors; - restrict forms and base URLs.
Modern CSP: Nonce, Hash, and Strict-Dynamic
In more complex applications, especially with dynamic rendering or server-side rendering, a stronger approach is to use nonces or hashes.
A nonce is a random value generated per request. The server includes this value in the CSP header and also in the scripts authorized for that response.
Example:
Content-Security-Policy:
script-src 'nonce-randomValueHere' 'strict-dynamic';
object-src 'none';
base-uri 'self';Content-Security-Policy:
script-src 'nonce-randomValueHere' 'strict-dynamic';
object-src 'none';
base-uri 'self';And in the HTML:
<script nonce="randomValueHere">
// authorized script
</script><script nonce="randomValueHere">
// authorized script
</script>The idea is simple: if an attacker injects a script without the correct nonce, the browser blocks it. The 'strict-dynamic' directive allows trust given to a nonce-based or hash-based script to be propagated to scripts loaded by it, reducing the need for long domain allowlists.
How to Apply CSP Without Breaking Everything
In practice, CSP should be implemented in stages.
The first step is to use report-only mode:
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
report-uri /csp-report;Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
report-uri /csp-report;In this mode, the browser does not block resources. It only reports violations. This makes it possible to understand what the application actually uses before enabling enforcement.
After that, the process usually follows this flow:
- Map the scripts, APIs, fonts, images, and iframes that are truly required.
- Remove unnecessary external resources.
- Replace inline scripts with versioned files or scripts using nonce/hash.
- Avoid
'unsafe-inline'and'unsafe-eval'whenever possible. - Test the policy in staging.
- Enable it gradually in production.
- Monitor violations and adjust carefully.
CSP in Angular, React, and SPAs
In single-page applications, an important point is to control:
- HTTP calls to APIs with
connect-src; - external images with
img-src; - fonts with
font-src; - iframes with
frame-src; - third-party scripts with
script-src; - styles generated by UI libraries with
style-src.
Modern frameworks help reduce some risks, but they do not eliminate the need for CSP. The problem usually appears in integrations: marketing tags, analytics, chat widgets, pixels, external libraries, manually added scripts, and temporary exceptions that are never removed.
Security does not fail only because of a lack of tools. Many times, it fails because permissiveness accumulates over time.
A Practical Checklist
Before deploying CSP to production, I usually think about these questions:
Does the application really need to load scripts from this domain?
Is this iframe necessary?
Can this external font be hosted internally?
Should this API be allowed in all environments?
Is there any inline script that can be removed?
Are we using Report-Only before enforcing the policy?
Do we have monitoring for CSP violations?
Is there a technical owner responsible for reviewing policy changes?
Conclusion
Content Security Policy is not a magic solution.
It does not fix vulnerable code, replace security reviews, or eliminate the need for good practices on both the back end and the front end.
But when applied well, it creates an important barrier between an exploitable flaw and a real incident.
In modern web applications, especially in corporate environments, CSP should be treated as part of the security architecture, not as a configuration detail.
Mature security is not only about fixing vulnerabilities after they appear.
It is about designing systems that reduce the impact when something goes wrong.