Security Safety Net Weakened by Permissive Script Settings
Your website has a security header called a Content Security Policy (CSP) — think of it like a bouncer that controls which scripts are allowed to run on your pages. Right now, two settings in that policy ('unsafe-inline' and 'unsafe-eval') are telling the bouncer to let almost anyone in, which largely defeats the purpose of having one. This is a defence layer that isn't doing its job properly, not an active attack.
Business Impact And Actions
medium urgencyBusiness Impact
A weakened CSP is a missing safety net, not an open door. Its main consequence is that if a separate code-injection flaw were ever found in your app, attackers would have an easier time exploiting it — potentially manipulating page content or stealing session data. For compliance-conscious customers or enterprise prospects, a weak CSP can also flag during security reviews and slow down deals.
What To Do
- Ask your developer to review which inline scripts and eval() calls your site actually needs — many can be moved to external files with a small amount of refactoring.
- Ask your developer to replace 'unsafe-inline' with a nonce- or hash-based approach. This is a targeted fix that keeps legitimate scripts working while restoring the safety net.
- If a full fix isn't possible right now, ask your developer to at least switch the CSP to 'report-only' mode so you can monitor what would be blocked before enforcing stricter rules.
- Add this to your next sprint or quarterly security backlog — it's not an emergency, but it's worth scheduling.
Weak Content-Security-Policy: 'unsafe-inline' and 'unsafe-eval' Negate XSS Protection
medium severity CVSS 5.0-6.5Vulnerability Explanation
The Content-Security-Policy header is present but includes both 'unsafe-inline' and 'unsafe-eval' in its script-src (or default-src) directive. 'unsafe-inline' permits execution of all inline <script> blocks, inline event handlers (onclick, etc.), and javascript: URIs. 'unsafe-eval' permits string-to-code execution via eval(), Function(), setTimeout(string), and similar APIs. Together, these two directives eliminate the two most impactful protections CSP provides against cross-site scripting: blocking injected inline scripts and blocking dynamic code evaluation. An attacker who finds any XSS injection point can exploit it freely, as the browser will not block the injected code.
Root Cause
These directives are typically added because the application relies on inline scripts (e.g., legacy jQuery event handlers, server-rendered script blocks, third-party tag managers) or libraries that use eval() internally. Rather than refactoring the code or using nonces/hashes, developers take the shortcut of permitting all inline execution globally.
Technical Impact
If an XSS vulnerability exists anywhere in the application, the weakened CSP provides no additional barrier. An attacker can execute arbitrary JavaScript in a victim's browser context: stealing session cookies or tokens, performing actions on behalf of the user, exfiltrating sensitive data visible on the page, or redirecting users to phishing pages. The CSP header exists but provides negligible XSS protection in its current state.
Severity Justification
This is a defence-in-depth control failure, not a standalone exploitable vulnerability. Exploitability depends entirely on the presence of a separate XSS flaw. CVSS is moderate because the weakened CSP amplifies the impact of other vulnerabilities rather than being directly exploitable on its own.
Affected Components
Content-Security-Policy header — all versions using 'unsafe-inline' or 'unsafe-eval' in script-src or default-src
Remediation Steps
- Start in report-only mode: rename the header to Content-Security-Policy-Report-Only and add a report-uri or report-to endpoint. This lets you observe what would be blocked without breaking anything.
- Audit your codebase for inline scripts, inline event handlers (onclick=, onload=, etc.), javascript: URLs, and eval() / Function() / setTimeout(string) calls. Move inline scripts to external .js files where possible.
- Replace 'unsafe-inline' with a per-request nonce for server-rendered pages: generate a cryptographically random nonce on each response, add it to the CSP header as 'nonce-{RANDOM}', and add the matching nonce attribute to every legitimate <script> tag. For static/SPA sites, use SHA-256 hashes instead.
- Replace 'unsafe-eval' by refactoring eval() usages. For third-party libraries that require eval (e.g., some older bundlers or template engines), consider upgrading to eval-free versions or using the Trusted Types API as a safer alternative.
- Add 'strict-dynamic' alongside your nonce or hash to allow trusted scripts to load dynamic child scripts without re-listing every source.
- Once report-only monitoring shows no unexpected violations, promote the header to enforcing mode: Content-Security-Policy.
Verification Steps
- Run: curl -I https://your-site.com and confirm the Content-Security-Policy response header no longer contains 'unsafe-inline' or 'unsafe-eval'.
- Open Chrome DevTools → Console and load your application. Look for any CSP violation errors (red messages starting with 'Refused to execute'). Fix each violation before enforcing.
- Paste your final policy into Google's CSP Evaluator at https://csp-evaluator.withgoogle.com/ and confirm it passes without high-severity warnings.
- Run Lighthouse (v7.3.0+) with --preset=experimental and check the Best Practices audit for CSP strength.
Code Examples (nginx)
# Nginx — current weak policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval';";
# Express/Node — current weak policy
app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'");
next();
});
# Nginx — nonce-based strict policy (nonce injected by app layer)
add_header Content-Security-Policy "script-src 'nonce-$request_id' 'strict-dynamic'; object-src 'none'; base-uri 'none';";
# Express/Node — nonce-based strict policy
import { randomBytes } from 'crypto';
app.use((req, res, next) => {
const nonce = randomBytes(16).toString('base64');
res.locals.cspNonce = nonce;
res.setHeader(
'Content-Security-Policy',
`script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`
);
next();
});
// In your HTML template, add the nonce to every <script> tag:
// <script nonce="<%= cspNonce %>">...</script>
# Hash-based alternative for static/SPA sites
# Content-Security-Policy: script-src 'sha256-{BASE64_HASH_OF_SCRIPT}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Best Practices
- Never use 'unsafe-inline' or 'unsafe-eval' as a permanent fix — use nonces or hashes to allow specific trusted scripts instead.
- Generate a fresh cryptographic nonce for every HTTP response; a static or predictable nonce provides no protection.
- Keep CSP in report-only mode during rollout and monitor violation reports before switching to enforcement.
- Treat CSP as a second layer of defence — it does not replace proper input validation and output encoding in your application code.
Found this in your infrastructure?
VulWall scans for this and dozens of other issues automatically.
Scan Your Domain Free