Update: CSP Level 3 — findings from a real implementation
Reproduction repo: eagle-head/drink_water
mainbranch — CSP Level 3 with'unsafe-inline'instyle-src(working)feat/csp-level3-strictbranch — strict CSP (no'unsafe-inline') with violation reporting enabled. Runmix phx.serverand open/dashboardto see violations inlogs/csp_violations.log.
Following the feedback from @steffend and @LostKobrakai, I implemented CSP Level 3 (enforcing, nonce + strict-dynamic) on this project and collected CSP violation reports. Here’s what I found.
What works with CSP Level 3
script-srcwith nonce +strict-dynamic— works perfectly. All scripts execute correctly with nonce attributes.strict-dynamicpropagates trust as expected.- Nonce propagation to LiveView — works via
:sessionoption onlive_session(thanks @LostKobrakai). <meta name="csp-nonce">tag — JavaScript can read the nonce for client-side use.- CSP violation reporting — implemented a
securitypolicyviolationevent listener that sends reports to a Phoenix endpoint, sincereport-to/report-uridon’t work on localhost (Chrome requires HTTPS and a real domain for the Reporting API).
What breaks: style-src without 'unsafe-inline'
When I set style-src 'self' 'nonce-...' (no 'unsafe-inline'), I got 30+ violations per page load on /dashboard. All violations are style-src-attr (inline style attributes). Three distinct sources were identified.
Before diving in, here’s the key insight from the MDN style-src documentation:
“Styles properties that are set directly on the element’s
styleproperty will not be blocked, allowing users to safely manipulate styles via JavaScript.”
This means the CSP spec distinguishes between:
| Method | Blocked by style-src? |
Reference |
|---|---|---|
element.style.property = "value" |
No — always allowed | MDN: style-src |
element.style.setProperty("prop", "val") |
No — always allowed | Same DOM API as above |
element.setAttribute("style", "...") |
Yes — blocked | MDN: style-src |
element.style.cssText = "..." |
Yes — blocked | MDN: style-src |
<style> tag without nonce |
Yes — blocked | W3C CSP3 spec |
style="..." attribute in HTML |
Yes — blocked | W3C CSP3 spec |
Important: a nonce on a <script> tag does not grant that script permission to use setAttribute("style") or cssText. These are controlled by style-src, not script-src. The only CSP-safe way for JavaScript to modify styles is via the DOM style API (element.style.property or element.style.setProperty()). See MDN: style-src and W3C CSP3 §8.3.
1. morphdom in Phoenix LiveView (critical blocker)
LiveView’s morphdom uses setAttribute("style", ...) in the morphAttrs function during DOM patching:
// morphdom's morphAttrs function
fromNode.setAttribute(attrName, attrValue); // when attrName === "style" → CSP violation
setAttribute("style", ...) is blocked by CSP style-src without 'unsafe-inline' (MDN reference). This is inherent to how morphdom works — every server-sent patch that changes a style attribute triggers a violation. This means LiveView itself is incompatible with strict style-src.
The fix would be to have morphdom treat style as a special case. Instead of setAttribute("style", value), parse the style string and apply property-by-property via the DOM API:
// CSP-safe approach — parse and apply per-property
function applyStyleCSPSafe(el, styleString) {
// Remove properties no longer present
while (el.style.length > 0) {
el.style.removeProperty(el.style[0]);
}
// Parse and apply new properties
const temp = document.createElement("div");
temp.style.cssText = styleString; // off-DOM, no CSP violation
for (const prop of temp.style) {
el.style.setProperty(
prop,
temp.style.getPropertyValue(prop),
temp.style.getPropertyPriority(prop),
);
}
}
Note: my original proposal suggested el.style.cssText = value as a fix, but MDN confirms that cssText is also blocked by CSP. The only safe path is element.style.setProperty() or direct property assignment.
2. DaisyUI components (countdown, radial-progress)
DaisyUI uses style="--value:X" as its data API for countdown and radial-progress components (DaisyUI radial-progress docs, DaisyUI countdown docs). No alternative (data-* attributes, CSS classes) is provided. This is a DaisyUI issue, not Phoenix.
I investigated the DaisyUI source code — only these 2 components out of 50+ require user-provided style= attributes. All other components use CSS classes to set custom properties internally.
3. topbar.js
Uses .style.property = value (direct property assignment). Per the MDN documentation, this should NOT be blocked by CSP. The violations reported from topbar are likely caused by morphdom re-applying the style attribute after DOM patches, not by topbar itself.
Summary
| Layer | Issue | Who needs to fix it | Reference |
|---|---|---|---|
| morphdom (LiveView) | setAttribute("style") blocked by CSP |
phoenix_live_view — patch morphdom to apply styles per-property via el.style.setProperty() |
MDN: style-src |
| DaisyUI | style="--value:X" as component API |
daisyui — library-wide CSP support | DaisyUI source |
| topbar | Likely false positive from morphdom | Verify after morphdom fix | MDN: style-src |
What this means for the CSP documentation
As @steffend suggested, the default should be enforcing. Based on these findings, the documentation should be honest:
script-src: CSP Level 3 works perfectly with nonce +strict-dynamic. No caveats.style-src:'unsafe-inline'is currently required when using LiveView, due to morphdom’s use ofsetAttribute("style")(MDN confirms this is blocked). This is a known limitation, not a design choice.
I’d like to contribute fixes
I’m willing to:
- Open an issue + PR on
phoenix_live_view— patch morphdom’smorphAttrsto apply styles per-property viael.style.setProperty()instead ofsetAttribute("style"), which is the only CSP-safe approach per the W3C CSP3 spec - Open an issue + PR on DaisyUI — not a workaround for specific components, but a robust, library-wide solution that enables CSP compliance for any existing or future component that relies on inline styles or JavaScript. The goal is to make CSP Level 3 a first-class concern in DaisyUI’s architecture.
- Write the Phoenix CSP guide — documenting the current state, the workaround (
'unsafe-inline'for style-src), and how to achieve full CSP Level 3 once the morphdom fix lands
Before opening the issues/PRs, I wanted to check with the community:
- Does the morphdom approach (
el.style.setProperty()per-property instead ofsetAttribute("style")) seem right? Are there edge cases I’m missing? - Is there a reason morphdom uses
setAttributefor style instead of the DOM style API? - Has anyone else hit this wall with CSP + LiveView?
References
- MDN: Content-Security-Policy style-src — definitive reference on what is and isn’t blocked
- W3C CSP Level 3 Specification — formal spec
- W3C CSP3 §8.3 — Should element’s inline type behavior be blocked? — spec section on inline style blocking
- MDN: style-src-attr — controls inline
styleattributes specifically - MDN: style-src-elem — controls
<style>elements and stylesheets - MDN: Reporting API — why
report-todoesn’t work on localhost - MDN: securitypolicyviolation event — client-side violation capture
- morphdom source (GitHub) — the DOM diffing library used by LiveView
- DaisyUI radial-progress source — uses
var(--value)set viastyle=attribute - DaisyUI countdown source — same pattern
- Dan Schultzer’s blog post on CSP with LiveView — community reference on CSP + LiveView






















