Skip to content

Dynamic loading and security limitations with CSP #391

@MrTango

Description

@MrTango

We are loading many resources dynamically via Webpack module federation and async imports.
This is done via inline style/scripts.

This is in direct conflict with CSP:

Some Info

To use Webpack Module Federation and dynamic imports (for both JavaScript and CSS) with a strong Content Security Policy (CSP) that does not allow unsafe-inline, and still benefit from production-grade “Static CSS Only” deployment, you need to take several coordinated steps.

1. JavaScript: Dynamic Imports & CSP

  • Webpack CSP Support: Webpack supports CSP by allowing you to specify a nonce on your script tags. This means dynamic chunks (JS loaded via import() or module federation) are loaded using ``. To use this:

    • Generate a unique nonce per page request on the server.
    • Inject this nonce both into the CSP header (e.g. script-src 'nonce-XYZ') and into the HTML template of your app.
    • Configure Webpack’s output.scriptNonce or use custom HTML/Webpack plugins to insert the nonce on all injected scripts[1][2].
  • Module Federation Considerations: Module federation’s dynamic loading of remote JS depends on runtime evaluation and dynamic `` injection. This is compatible with nonce-based CSP as long as all dynamically created scripts have the right nonce[3][2].

  • Strict Mode Caveats: Avoid development tools like 'eval' for chunk loading, as those require unsafe-eval (incompatible with strict CSP). Use devtool: 'source-map' or similar in production builds[3][4].

  • Trusted Types (optional): Webpack can also be used with Trusted Types for even stricter CSP environments, avoiding common DOM-based XSS vectors[1].

2. CSS: Static Extraction, Dynamic Loading, and CSP

  • Static CSS in Production: Use MiniCssExtractPlugin to extract CSS, which generates static .css files loaded via . These are fully compatible with CSP, as tags do not require unsafe-inline[5].

  • Dynamic CSS Loading:

    • If you must load CSS dynamically, use style-loader with injectType: 'lazyStyleTag' in development, but this injects `` tags and requires unsafe-inline.
    • The production best practice (and what you seem to like): Only use extracted .css files (via `` tags) in production, so you never need dynamic CSS insertion. With this approach, you only need to allow loading CSS from authorized domains, never unsafe-inline[5].
    • If you must occasionally load additional CSS on demand: use JS to inject `` elements—this is allowed under CSP (as long as loading from approved sources, and not using style tags with inline rules).

3. Putting It Together (Recipe)

  • CSP Policy example:

    Content-Security-Policy:
      default-src 'self';
      script-src 'self' 'nonce-XYZ' https://your-remotes.example.com;
      style-src 'self' https://your-cdn.example.com;
      object-src 'none';
      base-uri 'self';
    
  • HTML Template Setup (nonce injection):

      window.__webpack_nonce__ = 'XYZ';

    And ensure that all dynamic `` tags (loaded by Webpack runtime) have nonce="XYZ"[2].

  • Webpack config hints:

    • For JS: Use output.chunkLoadingGlobal and configure the runtime or HTML plugins to include your nonce.
    • For CSS: Use MiniCssExtractPlugin and only serve extracted CSS in production. Avoid dynamic style-tag injection except in development[5].
  • Module Federation Remotes:

    • For federated dynamic imports, make sure remote URLs are whitelisted in script-src.
    • Use a nonce for dynamic script tags, by customizing the runtime loading mechanism if needed (some plugins or runtime hooks may let you do this[3][2]).

4. Caveats

  • No support for unsafe-inline or unsafe-eval: Do not use loaders, plugins, or dev configurations that inject inline JS or rely on eval.
  • CSS Injection: Only use `` based loading for CSS in production to avoid running afoul of CSP.
  • Third-party modules: Any third-party library/module federation remote must comply with these CSP and nonce rules as well.

In summary:

  • Use nonce-based CSP for all JS, inject the nonce for both Webpack and federation-loaded JS.
  • Extract all CSS to real files, loaded via ``, not dynamically via style-loader except in dev.
  • Federated/dynamically loaded JS modules work as long as runtime script tags receive your nonce.
  • You never need unsafe-inline for this setup, either for JS or CSS, in production[1][2][5].

References used in answer:
[1][2][5]

[1] https://webpack.js.org/guides/csp/
[2] https://towardsdatascience.com/content-security-policy-how-to-create-an-iron-clad-nonce-based-csp3-policy-with-webpack-and-nginx-ce5a4605db90/
[3] module-federation/core#2631
[4] https://github.com/webpack/webpack/issues/[5](https://webpack.js.org/loaders/style-loader/)627
[5] https://webpack.js.org/loaders/style-loader/
[6] https://stackoverflow.com/questions/31211359/refused-to-load-the-script-because-it-violates-the-following-content-security-po
[7] https://stackoverflow.com/questions/41259838/is-there-any-way-to-dynamically-load-css-file-with-webpack
[8] https://module-federation.io/practice/frameworks/modern/dynamic-remote.html
[9] https://dev.to/omher/lets-dynamic-remote-modules-with-webpack-module-federation-2b9m
[10] https://stackoverflow.com/questions/69179460/code-structure-when-dynamically-load-a-micro-frontend-via-module-federation
[11] https://nextjs.org/docs/pages/guides/content-security-policy
[12] https://content-security-policy.com/strict-dynamic/
[13] https://www.fabrizioduroni.it/blog/post/[2](https://towardsdatascience.com/content-security-policy-how-to-create-an-iron-clad-nonce-based-csp3-policy-with-webpack-and-nginx-ce5a4605db90/)022/06/06/microfrontend-module-federation-dynamic-configuration
[14] https://webpack.js.org/concepts/module-federation/
[15] cssinjs/jss#559
[16] https://rsbuild.rs/guide/advanced/module-federation
[17] webpack/webpack#118
[18] module-federation/core#3550
[19] https://lawrencewhiteside.com/courses/webpack-beyond-the-basics/dynamic-css-chunk-loading/
[20] https://blog.logrocket.com/solving-micro-frontend-challenges-module-federation/

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions