All posts

How to fix SecurityScorecard CSP findings

CentralCSP Team ·

Last update:

A SecurityScorecard finding about your Content Security Policy (CSP) is fixable without breaking the site. SecurityScorecard reads the response headers a live page returns and grades the CSP it finds, so each finding maps to a specific weakness in the header string. The reliable fix for every one of them is the same shape: deploy the policy you want in report-only, watch real traffic, tighten from the evidence, then enforce. This post takes each SecurityScorecard CSP finding and shows the concrete header change that clears it.

SecurityScorecard reports these CSP findings under the Application Security factor. It loads your domain, captures the response, and checks the security headers it sees, the CSP among them. It does not send crafted attacks; it reads what the page already serves. That has one consequence worth stating up front: the scanner grades the enforced header on your live site, so run an enforced Content-Security-Policy header, not report-only alone, to clear a missing or weak CSP finding. Report-only is how you build the policy safely before you enforce it.

The SecurityScorecard CSP findings and what each means

SecurityScorecard reports these findings under the Application Security factor. The exact wording on your scorecard may differ, but they fall into a few recognizable groups.

  • A missing Content Security Policy. SecurityScorecard flags a missing CSP as a high-severity Application Security issue on the assessed host.
  • Content Security Policy contains broad directives. A directive uses a wildcard or an over-broad source such as *, a bare scheme, 'unsafe-inline', or 'unsafe-eval'.
  • Site Does Not Use Best Practices Against Embedding of Malicious Content. No framing control on the page, that is, a missing frame-ancestors directive or X-Frame-Options header.

If you also watch a BitSight rating, the same underlying weaknesses surface there under different names and a different scoring model. The BitSight specifics, the RAU25 change, the directive severity tiers, live in how to fix BitSight Content Security Policy findings. This post stays on the SecurityScorecard side. The remediation header is the same; only the scorecard naming differs.

The fix workflow

Every finding above clears with one sequence: deploy strict in report-only, collect violation reports, tighten, then enforce.

1. Deploy a strict policy in report-only

Start with the policy you want to end up with, but send it on the Content-Security-Policy-Report-Only header. The browser parses the policy and reports what it would block, but blocks nothing, so you can roll a strict policy onto live traffic without risking a broken page.

Content-Security-Policy-Report-Only:
    default-src 'self';
    script-src 'self';
    style-src 'self';
    img-src 'self';
    font-src 'self';
    connect-src 'self';
    object-src 'none';
    base-uri 'none';
    form-action 'self';
    frame-ancestors 'none';
    report-to csp-endpoint

2. Point reports at an endpoint

Reports only help if they are delivered somewhere. Declare a reporting endpoint with the Reporting-Endpoints response header, then reference its name from the report-to directive in the policy.

Reporting-Endpoints: csp-endpoint="https://<Endpoint-ID>.report.centralcsp.com"

You can stand up the endpoint and a dashboard for the reports with CentralCSP, which ingests CSP violation reports and turns them into a script inventory and a per-directive view instead of raw JSON.

3. Collect the violation reports

Let the report-only policy run across real traffic. Sort each blocked resource into two piles: legitimate (something the page needs) and unwanted (an injected or stale resource you do not want). You allow the first pile and leave the second blocked. Collect long enough to capture genuine usage before you trust the list.

4. Tighten the policy, one finding at a time

This is where you remove the patterns SecurityScorecard flags, using the report evidence. Each finding maps to a concrete edit.

A missing Content Security Policy. Add the policy. Move it from Content-Security-Policy-Report-Only to the enforcing Content-Security-Policy header once the report stream is quiet. The scanner reads the enforced header on your live site, so an enforced policy, not report-only alone, is what clears this finding.

Content Security Policy contains broad directives. Replace any lone * and any bare http: scheme source with the specific hosts your reports show you actually load. A wildcard source defeats the directive, and the reports tell you exactly which origins to list instead.

Content-Security-Policy-Report-Only:
-    script-src *;
+    script-src 'self' https://cdn.example.com

'unsafe-inline' and 'unsafe-eval' fall under this same broad-directives finding, so remove them here too. Inline scripts and styles are a primary cross-site scripting (XSS) vector, which is why 'unsafe-inline' is flagged. Give each legitimate inline script a nonce or a hash instead. When script-src contains a nonce, a hash, or 'strict-dynamic', the browser ignores 'unsafe-inline' entirely, so dropping the keyword changes nothing for modern browsers and only strengthens the policy. The full migration is in why you should never use unsafe-inline in CSP.

Externalize an inline event handler so it no longer needs the unsafe keyword:

<!-- before: inline handler needs 'unsafe-inline' -->
<button onclick="startCheckout()">Buy</button>
<!-- after: external script, no inline handler -->
<button id="buy">Buy</button>
<script src="/js/checkout.js"></script>
// /js/checkout.js
document.getElementById('buy').addEventListener('click', startCheckout);

For an inline block you cannot externalize, attach a nonce. The nonce must be at least 128 bits of entropy, base64, generated by a cryptographically secure random number generator, and fresh on every response.

Content-Security-Policy: script-src 'self' 'nonce-r4nd0m'
<script nonce="r4nd0m">
  initWidget();
</script>

Site Does Not Use Best Practices Against Embedding of Malicious Content. This is about framing control. Add frame-ancestors 'none' (or the specific origins allowed to frame you) to control who can embed the page, and set X-Frame-Options as the legacy backstop for older clients. Keep object-src 'none' and base-uri 'none' to close the classic injection paths too. These are the directives a scanner expects on a hardened policy.

Content-Security-Policy:
    object-src 'none';
    base-uri 'none';
    frame-ancestors 'none'

5. Enforce

When the report-only stream is quiet, that is, when the only remaining reports are resources you have deliberately chosen not to allow, move the same validated policy from Content-Security-Policy-Report-Only to the enforcing Content-Security-Policy header.

Content-Security-Policy:
    script-src 'nonce-{RANDOM}' 'strict-dynamic';
    style-src 'self';
    img-src 'self';
    connect-src 'self';
    object-src 'none';
    base-uri 'none';
    form-action 'self';
    frame-ancestors 'none';
    report-to csp-endpoint

Keep reporting on after you enforce. Any new report now means a real block in production, which is your early warning that a change broke something or that someone is probing the page.

Check the policy before you flip it

Before enforcing, run the draft through the CSP evaluator to catch any remaining 'unsafe-inline', broad wildcards, or bare schemes in sensitive directives, and use the CSP scanner to confirm what your live host currently sends. To see every security header SecurityScorecard reads, not just the CSP, the security headers scanner gives you the full before-and-after list. The CSP suite ties the reporting, scanning, and policy tracking together once you are past the first fix.

Where this leaves you

A SecurityScorecard CSP finding is a scoring read of your header string, so the fix is a header change you can make safely: deploy strict in report-only, collect real violation reports, tighten by replacing unsafe keywords and wildcards with nonces or hashes, then enforce. The same enforcing header that clears the finding is also genuinely better protection against XSS and resource injection.

If raw report JSON is more than you want to triage by hand, start a free CentralCSP account to collect the reports, see them grouped by directive and resource, and track the policy over time as you tighten it.

Sources