# The data scheme in Content Security Policy (/en/blog/csp-data-scheme)



The `data:` scheme lets a URL carry its own content inline, so `data:image/png;base64,...` is a whole image with no server to fetch it from. In a Content Security Policy (CSP) you can allow that scheme per directive. For images and fonts it is convenient and common. For scripts it hands an attacker a way to run code with no external host at all.

This post shows where `data:` belongs in a policy, where it does not, and why the difference matters.

## What the data scheme looks like in CSP [#what-the-data-scheme-looks-like-in-csp]

`data:` is a [scheme source](/en/docs/web-security/policies/content-security-policy/values/csp-scheme-source). The grammar is the scheme name followed by a colon, so you write the bare `data:` with a trailing colon and no quotes, unlike keyword sources such as `'self'`.

The common, reasonable use is inline images and fonts:

```http
Content-Security-Policy: img-src 'self' data:; font-src 'self' data:
```

That allows [`img-src`](/en/docs/web-security/policies/content-security-policy/directives/img-src) and [`font-src`](/en/docs/web-security/policies/content-security-policy/directives/font-src) to load resources from your own origin and from inline data URIs, which is how base64 icons, tiny placeholder images, and embedded fonts get rendered.

Each directive is independent. There is no `data:` on a directive unless you write it there, and it does not arrive through a fallback or a wildcard (more on the wildcard below).

## Why data is safe for images but dangerous for scripts [#why-data-is-safe-for-images-but-dangerous-for-scripts]

A `data:` image cannot run code. It is bytes the browser decodes and paints. A `data:` script is different: the URL carries the script body, so the entire payload travels inside the source expression.

If [`script-src`](/en/docs/web-security/policies/content-security-policy/directives/script-src) allows the scheme, an attacker who can inject a single `<script>` tag does not need to host anything. The code goes straight in the URL:

```html
<script src="data:text/javascript,alert(document.domain)"></script>
<script src="data:;base64,YWxlcnQoMSk="></script>
```

Current [Chrome](https://developer.chrome.com/docs), [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox), and [Safari](https://developer.apple.com/documentation/safari-release-notes) execute external `data:` scripts, so this is a working execution vector today, not a legacy quirk. The risk write-ups frame `data:` in `script-src` as a CSP bypass; the CSP specification itself only defines the scheme matching and does not single it out as an XSS warning, so treat the danger as a hardening finding rather than a spec rule.

One distinction keeps this honest. Browsers block top-level navigation to a `data:` URL, an older anti-phishing change, but that does not block `data:` subresources like `<script src="data:...">` or `<img src="data:...">`. The script loads as a subresource inside the page's own origin, so the navigation block does not apply.

## The wildcard does not cover data [#the-wildcard-does-not-cover-data]

A natural assumption is that `script-src *` allows everything, including `data:`. It does not. In CSP Level 3, a single `*` matches a URL only when the URL uses an HTTP or HTTPS scheme (plus the same scheme as the protected document). The `data:`, `blob:`, and `filesystem:` schemes are excluded and must be listed explicitly.

```http
Content-Security-Policy: script-src *
```

That policy does not permit `data:` scripts. You would have to add `data:` yourself for them to run, which is exactly why you should not.

This exclusion is a deliberate security fix. Browsers later restricted `data:` matching after allowing it through a wildcard proved to be a real bypass, and the behavior is consistent across current browsers.

## Where to allow data, and where not to [#where-to-allow-data-and-where-not-to]

Decide per directive, based on whether the directive can lead to code running:

* [`img-src`](/en/docs/web-security/policies/content-security-policy/directives/img-src), `font-src`, and [`media-src`](/en/docs/web-security/policies/content-security-policy/directives/media-src): allowing `data:` is legitimate and widely used for inline base64 assets.
* [`script-src`](/en/docs/web-security/policies/content-security-policy/directives/script-src): do not allow `data:`. It is an XSS execution vector as shown above.
* [`object-src`](/en/docs/web-security/policies/content-security-policy/directives/object-src) and `frame-src`: also risky, since `data:` plugins or frames can carry active content.
* `style-src`: discouraged. An attacker-controlled `data:` stylesheet loads as a subresource and applies, which is a real CSS-based injection and data-exfiltration vector (though not code execution).
* [`default-src`](/en/docs/web-security/policies/content-security-policy/directives/default-src): especially bad. When no `script-src` is present, `default-src` governs scripts, so `default-src data:` effectively permits `data:` scripts.

A large share of sites historically allowed `data:` under `script-src`, `frame-src`, or `object-src` (or `default-src` when `script-src` was absent), which is why this stays a common finding.

## The safer alternative for scripts [#the-safer-alternative-for-scripts]

The fix is not to find a careful way to allow `data:` in `script-src`. It is to stop allowing scripts by scheme or host at all and to allow them by [nonce or hash](/en/docs/web-security/policies/content-security-policy/values/csp-hashes-nonce) instead, with [`'strict-dynamic'`](/en/blog/strict-dynamic-csp).

Chrome and Google guidance is explicit that host and scheme allowlists give little protection, and that a nonce or hash plus `'strict-dynamic'` is the model to use. A minimal strict policy looks like this:

```http
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic';
    object-src 'none';
    base-uri 'none'
```

With a nonce in place, the browser trusts only the scripts carrying that nonce and the scripts those trusted scripts load. A `data:` script with no matching nonce is rejected, so the bypass closes even if an attacker injects a tag. For the wider case against opening the policy this way, see [why removing `'unsafe-inline'` matters](/en/blog/unsafe-inline-csp) and the broader walkthrough in [how to build a strong CSP](/en/blog/how-to-build-a-strong-csp).

If you need a few inline base64 images or fonts, keep `data:` scoped to `img-src` and `font-src` and leave it out of everything that touches script or style.

## Find data in your own policy [#find-data-in-your-own-policy]

Roll out changes the safe way: put a candidate policy on the [`Content-Security-Policy-Report-Only`](/en/docs/web-security/policies/content-security-policy/report-only) header first, watch the reports, and only enforce once they are quiet. To see where your live policy stands today, run it through the [CSP evaluator](/tools/csp-evaluator), which flags `data:` in `script-src` and other weak sources so you know what to tighten before they become a way in.

The evaluator reads the policy you already ship. What it cannot tell you is which `data:` URIs your pages actually load, because that only shows up at runtime. CentralCSP collects the csp-violation reports from real visits and groups them by blocked URI, so when you remove `data:` from `script-src` you can see exactly which inline payloads stop loading, and whether any of them were yours.

## Related [#related]

* [The blob scheme in Content Security Policy](/en/blog/csp-blob-scheme)
* [Why you should never use unsafe-inline in CSP](/en/blog/unsafe-inline-csp)
