Server-driven UI breaks under a strict CSP — here’s the proof
If your UI is generated from strings at runtime, a no-unsafe-eval Content-Security-Policy turns it off. Why code-from-strings and a strict CSP are structurally at odds — with a running demo and the test that proves it.
Server-driven UI has an easy promise: describe a screen on the server, ship it to any client, change it without a deploy. The trouble shows up the first time that description needs to do something — a visibility rule, a computed label, a conditional. The fast way to add behavior to a JSON payload is to evaluate a little expression language at runtime. And runtime expression evaluation is exactly what a strict Content-Security-Policy exists to forbid.
The failure, reproduced
You don’t have to theorize about this. A Builder.io user shipped a page whose data bindings worked perfectly in the editor, deployed it behind a platform with a strict policy, and got this:
// data binding that renders fine in the editor… Failed code evaluation: Code generation from strings disallowed for this context
The binding was compiled from a string at runtime — the platform ran the moral equivalent of new Function("return state.username") to resolve it. That’s the reported issue, closed by the vendor in March 2025, and it is not a one-off: any engine that turns strings into executable code hits the same wall the moment the runtime refuses to generate code from strings.
Why a strict CSP turns it off
A Content-Security-Policy without 'unsafe-eval' disables the whole family of string-to-code APIs: eval(), the Function constructor, and setTimeout("…") with a string body. That is the recommended posture for any page that handles sensitive input.
Content-Security-Policy: script-src 'self' // no 'unsafe-eval' → eval() and new Function() throw
So this isn’t a bug you patch. Code-from-strings and a no-unsafe-eval policy are structurally at odds. If your UI layer needs one, it cannot run under the other — and the pages where the policy is strictest (checkout, onboarding, anything touching account data) are precisely the ones you’d most want to drive from the server.
The proof: watch it refuse
The way out is to draw a hard line: the contract describes structure, not behavior. It carries no expression language, so there is nothing to evaluate — real logic stays in typed, reviewable code on your side, never in the payload. That claim is only worth anything if you can check it, so we do, in a test that swaps eval and the Function constructor for traps and validates a real contract:
it('validates a contract without ever invoking new Function or eval', () => { globalThis.Function = () => { throw new Error('new Function was called') } // eval trapped the same way validate(contract) // passes — nothing is generated from strings })
And it isn’t just a unit test asserting an absence — the whole thing renders live under a strict CSP, with the policy visible in the response headers, on the demo page. Watch it run under a strict CSP
This doesn’t prove every server-driven-UI tool evaluates strings — some don’t. It proves something narrower and more useful: any tool that does cannot pass a strict CSP, and you can’t always tell which camp a tool is in until you deploy. The only durable guarantee is a contract with no expression language to evaluate in the first place — and a test that fails the build if one ever sneaks in.
Why this lands hardest on a payment page
Since 31 March 2025, PCI DSS 4.0 requires every script on a payment page to be inventoried, justified, and monitored for tampering (§6.4.3 and §11.6.1). A UI layer that generates code from strings at runtime is the opposite of that: code that didn’t exist when the page was reviewed, materializing in the browser. A contract that ships zero eval — and whose every change is a reviewable diff — turns that standing finding into a non-issue. See the regulated case
Server-driven UI is worth doing. Shipping an interpreter to the browser to get it is not. Keep the contract describing structure, keep the behavior in code you review, and a strict CSP has nothing left to reject.