Skip to content

Build with -sDYNAMIC_EXECUTION=0 so the solver initialises under a strict CSP - #12

Open
MakerViking wants to merge 1 commit into
Salusoft89:mainfrom
MakerViking:fix/csp-no-dynamic-execution
Open

Build with -sDYNAMIC_EXECUTION=0 so the solver initialises under a strict CSP#12
MakerViking wants to merge 1 commit into
Salusoft89:mainfrom
MakerViking:fix/csp-no-dynamic-execution

Conversation

@MakerViking

Copy link
Copy Markdown

The problem

planegcs cannot initialise in any page whose Content Security Policy does not grant 'unsafe-eval'. 'wasm-unsafe-eval' is not sufficient, because the failure is not WebAssembly compilation.

The glue contains embind's new_ helper, closure-compiled so its constructor is the global Function:

function qb(b){var a=Function; ... b=a.apply(c,b); ...}   // planegcs_dist/planegcs.js

a.apply(c, b) where a === Function is a Function-constructor call, reached from craftInvokerFunction, which assembles each invoker from a source string. 'wasm-unsafe-eval' permits wasm compilation only; the Function constructor needs 'unsafe-eval'.

Serving a page that imports the module under script-src 'self' 'wasm-unsafe-eval' gives:

EvalError: Evaluating a string as JavaScript violates the following Content Security
Policy directive because 'unsafe-eval' is not an allowed source of script:
script-src 'self' 'wasm-unsafe-eval'

The wasm itself compiles fine. This is not engine specific: I reproduced it on Chromium 151 and had a field report of the same failure on WebKitGTK.

Affects 1.1.7 and 1.2.0, whose planegcs.js are byte-identical.

The change

One flag, added to LINK_FLAGS in planegcs/CMakeLists.txt:

-s DYNAMIC_EXECUTION=0

Emscripten stops emitting eval() and new Function(), and embind falls back to a non-codegen path. That fallback is a supported, gated path rather than an accident: src/lib/libembind.js carries all three branches (DYNAMIC_EXECUTION && !EMBIND_AOT, EMBIND_AOT, and DYNAMIC_EXECUTION == 0 && !EMBIND_AOT).

Verification

Built both ways in the repo's own Docker image (emscripten/emsdk:3.1.45), after npm run build:bindings.

  • My baseline build is byte-identical to the published 1.1.7 planegcs.js (28493 bytes), so the only variable is the flag.
  • With the flag, the Function-constructor sink is gone and the glue is slightly smaller: 28493 to 27526 bytes.
  • planegcs.wasm is byte-identical between the two builds (508141 bytes). This is a JS glue change only and carries no solver risk.
  • npm test passes identically on both: 8 files, 59 tests.
  • Loading both builds side by side on one page under script-src 'self' 'wasm-unsafe-eval': baseline throws EvalError, the patched build initialises.

Cost

The fallback is documented as slower, so I measured it rather than guessing. Same wasm on both sides, so this is purely invoker overhead. Node 22, best of 5 runs of 200 solves, a chain of horizontally-constrained lines:

sketch baseline DYNAMIC_EXECUTION=0 ratio
20 lines 23.7 us/solve 35.8 us/solve 1.51x
50 lines 56.5 us/solve 83.9 us/solve 1.48x
100 lines 109.6 us/solve 166.2 us/solve 1.52x

An isolated microbenchmark of the boundary alone (200k push_p_param + get_p_param calls) is 6.7 ms vs 41.5 ms, so the invoker layer really is about 6x. Real solves are dominated by solver math inside the wasm, which dilutes it to a steady ~1.5x. In absolute terms a 100-line solve goes from 0.11 ms to 0.17 ms, about 1% of a 60fps frame.

If that cost is unwelcome, -s EMBIND_AOT=1 generates the invokers at compile time and removes it. EMBIND_AOT does not exist in emscripten 3.1.45, which the Dockerfile pins. I checked src/settings.js across tags and it first appears in 3.1.51, so pairing the two depends on #8. DYNAMIC_EXECUTION=0 alone works on the current pin, which is why this PR is just the one flag.

Why it matters

I ship planegcs in a desktop CAD app. This silently disabled all sketch constraints, dimensions and point dragging for every user on every platform, and it was hard to find because a CSP only applies to packaged builds, never to a dev server. Users were told to update their browser runtime, which was never the problem.

Happy to add the EMBIND_AOT flag to this PR instead if you would rather bump emscripten first.

…ct CSP

embind builds each invoker by passing a source string to the Function
constructor (craftInvokerFunction, reached through new_), and that requires
'unsafe-eval'. Granting 'wasm-unsafe-eval' is not enough, because the
failure is not WebAssembly compilation: the wasm compiles fine and
initialisation then dies constructing the bindings.

-s DYNAMIC_EXECUTION=0 makes emscripten stop emitting eval() and
new Function(). embind falls back to a non-codegen path, which emscripten
documents and gates in src/lib/libembind.js.

The wasm is byte-identical with and without the flag, so this changes the
JS glue only and carries no solver risk. All 59 tests pass on both builds.
cyberman pushed a commit to Be-Quiet-Home/SindriCAD that referenced this pull request Aug 13, 2026
planegcs is an emscripten/embind module, and embind builds each invoker by
handing a source string to the Function constructor. script-src carried
'wasm-unsafe-eval', which permits WebAssembly compilation but not the
Function constructor, so the wasm compiled fine and initialisation then
died constructing the bindings.

Every packaged build since the solver shipped has had no working
constraints, dimensions or sketch point dragging. It never reproduced
locally because tauri dev serves from vite, which sends no CSP.

Reported four times, eec3752a (0.1.73), ffff5144 (0.1.109), 9042ea56
(0.1.117) and cdf4c0f7 (0.1.123), plus once on Linux. The message sent all
of them to update a webview runtime that was never the problem and one
reinstalled WebView2 for nothing, so it now blames the build instead.

'unsafe-eval' is a real loosening. The only eval sink in the app is this
vendored glue: src/params/parse.ts and eval.ts are a hand-written parser
and must stay that way while the token is in the policy. Upstream fix
proposed as Salusoft89/planegcs#12, which removes the need for it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant