Skip to content

fix(pow): support strict CSP without unsafe-eval - #32

Open
edgepillar wants to merge 2 commits into
digitalSloth:mainfrom
edgepillar:codex/csp-safe-pow
Open

fix(pow): support strict CSP without unsafe-eval#32
edgepillar wants to merge 2 commits into
digitalSloth:mainfrom
edgepillar:codex/csp-safe-pow

Conversation

@edgepillar

@edgepillar edgepillar commented Aug 25, 2026

Copy link
Copy Markdown

Summary

  • rebuild the PoW glue with dynamic JavaScript execution disabled and Embind AOT enabled
  • replace the Node new Function(...import...) shim and browser inline module script with native dynamic imports
  • pin the PoW source and Emscripten version for reproducible assets
  • add regression coverage and document the exact browser CSP requirements

Scope

This is limited to the existing PoW loader, generated JS/WASM assets, build script, tests, and documentation. It does not change the PoW algorithm, wire format, or public transaction API.

The resulting browser policy for the built-in worker, when pow.js and
pow.wasm are served from the same origin, can use:

script-src 'self' 'wasm-unsafe-eval';
worker-src 'self' blob:;
connect-src 'self';

The generated PoW loader no longer requires 'unsafe-eval'. The narrower
'wasm-unsafe-eval' permission is still required for WebAssembly compilation
in Chromium.

The native browser import() path intentionally removes the previous
SDK-managed 10-second polling timeout; loading now follows the platform's
module and fetch promise behavior.

Reproducibility

  • PoW source: zenon-network/znn-pow-links-cpp@9c63abdcd4e6bd642a81476cbff2f5190efabe95
  • Emscripten: 6.0.8
  • flags: DYNAMIC_EXECUTION=0, EMBIND_AOT=1
  • the build now fails if generated pow.js contains eval() or new Function()

Validation

  • npm run build:wasm
  • npm test — 649 passing locally; GitHub Actions passed on Node 20 and 22
  • npm run lint — 0 errors
  • npm run cover — passed in GitHub Actions on Node 20
  • npm run build
  • npm pack --dry-run --ignore-scripts
  • compiled Node PoW initialization and nonce generation
  • Chromium runtime checks for direct and SDK-integrated main-thread and worker paths, with zero CSP violations
  • negative Chromium control without 'wasm-unsafe-eval', which failed only at WebAssembly compilation as expected

@digitalSloth

Copy link
Copy Markdown
Owner

@edgepillar Nice fix for the core problem — replacing the embind new Function invoker and the inline <script> interpolation with a native import() is the right shape, and I rebuilt the wasm/js pair locally against the pinned Emscripten version and upstream commit and got a byte-for-byte match with what's committed here, so no concerns on artifact provenance.

Two things I'd want addressed before merge:

  1. The documented CSP (README.md, docs/build-wasm.md) is missing connect-src 'self'. The browser glue loads pow.wasm via fetch(), which connect-src governs, not script-src — under the policy exactly as documented, the wasm fetch still gets blocked.
  2. test/pow/powCsp.spec.ts mostly asserts source/script text rather than behavior (e.g. checking literal strings inside build-wasm.sh, which the test never runs). Worth keeping the "no eval/new Function in the shipped glue" check and replacing the rest with a nonce-validity assertion so the test actually catches a bad or swapped artifact.

Minor nits, not blocking:

  • The regenerated glue drops the wasmBinary handling that src/pow/pow.ts still relies on — readFileSync'd bytes are read but never passed through; harmless today since the files sit side by side, but the code and its comment are now misleading.
  • scripts/build-wasm.sh's Emscripten version check (*"6.0.8"*) is an unanchored substring match against the whole emcc --version banner.
  • The #znn-pow-module hash suffix on the module URL (src/pow/pow.ts:50-51) looks unnecessary — couldn't reproduce a loader that needs it.
  • The browser import() path lost the old 10s load timeout; worth a one-line mention in the PR description if intentional.

Once the CSP docs and the test file are tightened up this looks good to me.

@digitalSloth
digitalSloth marked this pull request as ready for review September 2, 2026 21:38
@edgepillar

Copy link
Copy Markdown
Author

Thanks for the detailed review and for independently reproducing the generated artifacts.

I addressed the two requested changes in beff10e:

  • added connect-src 'self' to the same-origin CSP guidance in both README.md and docs/build-wasm.md;
  • replaced the loader/build-script string assertions with real PoW generation and an independent SHA3-256 nonce-validity check using the canonical little-endian threshold comparison.

I also updated the PR description with the complete CSP example and documented the intentional removal of the previous SDK-managed 10-second polling timeout. The fresh Node 20, Node 22, and coverage checks are green.

For the remaining non-blocking notes, I left the unused wasmBinary path, Emscripten version-match tightening, and module-URL fragment unchanged to keep this follow-up limited to the requested blockers. I’m happy to address any of those here if you would prefer.

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.

2 participants