# How to fix SecurityScorecard CSP findings (/en/blog/fix-securityscorecard-csp-findings)



A [SecurityScorecard](https://securityscorecard.com/) 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 [#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](/en/blog/fix-bitsight-csp-findings). This post stays on the SecurityScorecard side. The remediation header is the same; only the scorecard naming differs.

## The fix workflow [#the-fix-workflow]

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

```mermaid
flowchart LR
  A["Report-Only"] --> B["Collect reports"]
  B --> C["Tighten"]
  C --> D["Enforce"]
```

### 1. Deploy a strict policy in report-only [#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.

```http
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 [#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.

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

You can stand up the endpoint and a dashboard for the reports with [CentralCSP](/register), 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 [#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 [#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.

```diff
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'`](/en/docs/web-security/policies/content-security-policy/values/csp-keywords) 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](/en/blog/unsafe-inline-csp).

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

```html
<!-- before: inline handler needs 'unsafe-inline' -->
<button onclick="startCheckout()">Buy</button>
```

```html
<!-- after: external script, no inline handler -->
<button id="buy">Buy</button>
<script src="/js/checkout.js"></script>
```

```javascript
// /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.

```http
Content-Security-Policy: script-src 'self' 'nonce-r4nd0m'
```

```html
<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.

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

### 5. Enforce [#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.

```http
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 [#check-the-policy-before-you-flip-it]

Before enforcing, run the draft through the [CSP evaluator](/tools/csp-evaluator) to catch any remaining `'unsafe-inline'`, broad wildcards, or bare schemes in sensitive directives, and use the [CSP scanner](/tools/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](/tools/security-headers) gives you the full before-and-after list. The [CSP suite](/platform/csp-builder) ties the reporting, scanning, and policy tracking together once you are past the first fix.

## Where this leaves you [#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](/register) to collect the reports, see them grouped by directive and resource, and track the policy over time as you tighten it.

## Sources [#sources]

* [SecurityScorecard help center](https://support.securityscorecard.com/)
* [MDN, Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP)

## Related [#related]

* [How to fix BitSight Content Security Policy findings](/en/blog/fix-bitsight-csp-findings)
* [Why you should never use unsafe-inline in CSP](/en/blog/unsafe-inline-csp)
* [CSP keywords reference](/en/docs/web-security/policies/content-security-policy/values/csp-keywords)
