Content Security Policy
A 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-srcneeds no exceptions. The engine never callsevalornew Function, and never writes inlineon*handler attributes.style-srcneeds no exceptions. Boundstyle="…"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.
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:
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', …):
// 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). The engine lifts that CSS and
injects it once per template, as a constructed stylesheet adopted via
document.adoptedStyleSheets:
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 and color picker, produce data: image sources.
If you display them, allow it:
img-src 'self' data:;
A strict policy that works
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.