Reactive pages with no unsafe-eval
Every reactive framework has to answer one question honestly: how does the
browser evaluate p.price < 50?
Through Darkmown 2.0.x the answer was new Function. A :computed, a
@loop … where, a .class when, and a :if comparison were all validated at
compile time against a closed whitelist, then emitted as a small JavaScript
fragment that the runtime turned back into a callable. That is safe from the
injection angle, because nothing an author writes reaches the fragment unless it
matched the grammar first. It is not safe from the deployment angle, because
new Function needs script-src 'unsafe-eval', and a security-minded adopter
who reads your CSP does not care how carefully you validated the input. They
care that the policy has a hole in it.
What changed
The compiler still validates the expression exactly as before. What it emits is
different: instead of a JavaScript fragment, it serializes the already-validated
expression into a compact AST and puts it in the data-wd-* attribute. The
runtime walks that AST with a closed evaluator. Readers, unary operators, and a
fixed arithmetic, comparison, and logical set. An unknown op tag is a hard
error, never a fallthrough.
No new Function. No eval. Nothing on the page turns text into code.
What it buys
The shipped policy is now byte-identical for static and reactive pages, across
dist/_headers, vercel.json, and the local serve layer:
script-src 'self'
No 'unsafe-inline', no 'unsafe-eval', on any page the framework emits. The
one inline script Darkmown ships (the transitions: true speculation-rules
block) is authorized by a build-time 'sha256-…' hash, and the inline state
seed is a JSON data block that CSP does not gate at all.
What it cost
136 bytes. The runtime went from 7,518 to 7,654 bytes gzipped, still under the 8 KB budget that CI enforces on every commit.
Semantics did not move. The interpreter applies the same JavaScript operators the generated fragments used to, and a parity battery compares the two evaluators across a wide matrix of expressions, states, and loop items, asserting identical results. The fuzz suite still proves that hostile text survives only as inert literal data.
The rule underneath
Compile-time validation is the security layer, and it always was. Dropping
eval did not make the language safer against a malicious author, because the
whitelist already did that. It made the language deployable somewhere a
strict CSP is not negotiable, which is most places worth deploying.