---
title: "Content Security Policy Support"
description: "LemonadeJS under a strict Content Security Policy: no eval, no inline handlers, styles through the CSSOM and constructed stylesheets. No nonce, no config."
source: https://lemonadejs.com/docs/csp/
---

# Content Security Policy

A [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
restricts where scripts and styles may come from. The strictest policies
forbid `'unsafe-inline'` and `'unsafe-eval'` entirely. LemonadeJS runs
under such a policy **with no configuration**: no nonce, no global, no
opt-in.

The short version:

- **`script-src` needs no exceptions.** The engine never calls `eval` or
  `new Function`, and never writes inline `on*` handler attributes.
- **`style-src` needs no exceptions.** Bound `style="…"` values and
  component-owned `<style>` are both applied through the **CSSOM**, which
  CSP does not govern.

So a policy as tight as `default-src 'self'` runs the engine, your
components, and Studio blocks unchanged.

No `eval`, no `new Function`, no inline handler strings: interpolated values are text, never markup.

<!--example-->

```js
import { html } from 'lemonadejs';

const App = (props, { state }) => {
    const input = state('<img src=x onerror="alert(1)">');
    return html`<div>
        <input bind="${input}" style="width:100%" />
        <p>Rendered as text: <code>${input}</code></p>
        <p><small>The handler below is a function reference, not an attribute string, so it works under
        <b>script-src 'self'</b> without <b>'unsafe-inline'</b> or <b>'unsafe-eval'</b>.</small></p>
        <button onclick="${() => (input.value = '<b>still text</b>')}">set another value</button>
    </div>`;
};
```

## Scripts

Nothing requires `'unsafe-eval'`: there is no `eval`, no `new Function`,
no string-compiled templates. Tagged templates are parsed into a node
structure in plain JavaScript.

Nothing requires `'unsafe-inline'` for scripts either. Events you write in
a template, like this:

```javascript
html`<button onclick="${() => count.value++}">+</button>`
```

are **not** rendered as `onclick=""` attributes. The engine removes the
placeholder and binds the handler with `addEventListener`. The DOM a CSP
scanner sees has no inline script.

## Styles

The design choice matters here. CSP's `style-src` governs **markup**: a parsed `style` attribute
(`style-src-attr`) and `<style>` / `<link>` elements (`style-src-elem`).
It does **not** govern the CSSOM: styles applied through JavaScript DOM
APIs are trusted. LemonadeJS routes both kinds of styling through the
CSSOM.

### Bound styles

A bound `style` value is applied with `element.style`, never
`setAttribute('style', …)`:

```javascript
// what you write
html`<div style="${() => css({ width: w.value })}">`

// what the engine does (CSSOM, not the style attribute)
el.style.cssText = 'width: 240px';
```

`style-src-attr` blocks parsing a `style` *attribute*; the CSSOM write
sidesteps it. (One observable consequence: reading
`element.getAttribute('style')` back returns the browser-*normalized*
serialization (`width: 240px;`), not the exact string you wrote. Assert
against the CSSOM, e.g. `element.style.width`, not the raw attribute.)

### Component-owned `<style>`

A component can ship its CSS in its own source with a `<style>` tag inside
`` html`` `` (see [Styling](/docs/styling/)). The engine lifts that CSS and
injects it once per template, as a **constructed stylesheet** adopted via
`document.adoptedStyleSheets`:

```javascript
const sheet = new CSSStyleSheet();
sheet.replaceSync(componentCss);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
```

A constructed stylesheet is a CSSOM object, not a `<style>` element, so
`style-src-elem` does not apply. No nonce, no `'unsafe-inline'`.
`adoptedStyleSheets` is available in every evergreen browser since 2023.

## Images

Blocks that export a canvas to a data URL, the
[cropper](/docs/studio/) and color picker, produce `data:` image sources.
If you display them, allow it:

```text
img-src 'self' data:;
```

## A strict policy that works

```text
Content-Security-Policy:
    default-src 'self';
    script-src 'self';
    style-src 'self';
    img-src 'self' data:;
```

No `'unsafe-inline'`, no `'unsafe-eval'`, no `'nonce-…'` required for the
framework. Add directives for your own assets as usual.

## The one edge case

A browser old enough to lack constructable stylesheets (pre-2023) falls
back to a real `<style>` element for component CSS, and *that* element
would be blocked by a strict `style-src`. If you must support such
browsers under a strict policy, either add `'unsafe-inline'` to
`style-src` or ship the affected components' CSS as a static
`<link rel="stylesheet">` from `'self'`. On any current browser there is
nothing to configure.

## Verifying it yourself

The behavior is guarded by a real-browser test: a page served with
`style-src 'self'` (no `'unsafe-inline'`, no nonce) mounts a component and
asserts that a bound `style` value and a component `<style>` both take
effect, and that the component CSS arrived via `document.adoptedStyleSheets`.
If a change reintroduced `setAttribute('style')` or a raw `<style>`
element on the primary path, that check would fail.