Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CSP] createCache nonce-value added to Cache not applied to inline-styles #38965

Open
Tracked by #39765
Mateo-P opened this issue Sep 13, 2023 · 5 comments
Open
Tracked by #39765
Assignees
Labels
package: styles Specific to @mui/styles. Legacy package, @material-ui/styled-engine is taking over in v5.

Comments

@Mateo-P
Copy link

Mateo-P commented Sep 13, 2023

Steps to reproduce 🕹

we are using remixjs+MUI and want to add CSP-Headers following these guides:
CSP-guide
remix-Styling

we added the nonce-value to the cache on CreateEmotionCache.tsx:

CreateEmotionCache.tsx
import createCache from '@emotion/cache';

import { cspScriptNonce } from '@/root';

export default function createEmotionCache() {
  return createCache({ key: 'css', nonce: cspScriptNonce });
}

here is how we have entry.server.tsx:

entry.server.tsx
import { CacheProvider } from '@emotion/react';
import createEmotionServer from '@emotion/server/create-instance';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import type { EntryContext } from '@remix-run/node';
import { Response } from '@remix-run/node';
import { RemixServer } from '@remix-run/react';
import { renderToString } from 'react-dom/server';

import { theme } from '@/styles/theme';
import createEmotionCache from '@/styles/utils/createEmotionCache';

import { StateManagerProvider } from '@/lib/state-manager';

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext,
): Response {
  const cache = createEmotionCache();
  const { extractCriticalToChunks } = createEmotionServer(cache);
  const html = renderToString(
    <StateManagerProvider>
      <CacheProvider value={cache}>
        <ThemeProvider theme={theme}>
          <CssBaseline />
          <RemixServer context={remixContext} url={request.url} />
        </ThemeProvider>
      </CacheProvider>
    </StateManagerProvider>,
  );

  const { styles } = extractCriticalToChunks(html);
  const loaderData = remixContext.staticHandlerContext.loaderData as {
    root: {
      cspScriptNonce: string | undefined;
    };
  };
  const nonce = loaderData.root.cspScriptNonce;
  let stylesHTML = '';
  styles.forEach(({ key, ids, css }) => {
    const emotionKey = `${key} ${ids.join(' ')}`;
    const newStyleTag = `<style nonce="${nonce}" data-emotion="${emotionKey}">${css}</style>`;
    stylesHTML = `${stylesHTML}${newStyleTag}`;
  });
  const markup = html.replace(
    /<meta(\s)*name="emotion-insertion-point"(\s)*content="emotion-insertion-point"(\s)*\/>/,
    `<meta name="emotion-insertion-point" content="emotion-insertion-point"/>${stylesHTML}`,
  );
  const { CSPolicy, CSPheaders } = createCSP(nonce!!);
  responseHeaders.set(CSPolicy, CSPheaders);
  responseHeaders.set('Content-Type', 'text/html');

  return new Response(`<!DOCTYPE html>${markup}`, {
    headers: responseHeaders,
    status: responseStatusCode,
  });
}

const createCSP = (nonce: string) => {
  const isDevelopment = process.env.NODE_ENV === 'development';
  let scriptSrc: string;
  if (typeof nonce === 'string' && nonce.length > 10) {
    scriptSrc = `'report-sample' 'nonce-${nonce}'`;
  } else if (isDevelopment) {
    // Allow the <LiveReload /> component to load without a nonce in the error pages
    scriptSrc = "'report-sample' 'unsafe-inline'";
  } else {
    scriptSrc = "'report-sample'";
  }

  const connectSrc = isDevelopment ? 'ws://localhost:*' : '';

  const CSPolicy = isDevelopment
    ? 'Content-Security-Policy-Report-Only'
    : 'Content-Security-Policy';

  const CSPheaders =
    "default-src 'self'; " +
    `script-src 'self' ${scriptSrc}; ` +
    `style-src 'self' ${scriptSrc}; ` +
    "img-src 'self' data: blob:; " +
    `connect-src 'self' ${connectSrc} data:; ` +
    "object-src 'none'; " +
    "worker-src 'self' blob:; " +
    "frame-ancestors 'none'; " +
    'block-all-mixed-content; ' +
    'report-to /reporting/csp';
  return { CSPolicy, CSPheaders };
};

Current behavior 😯

we added nonce-value to the <style/> tag in the entry.server.tsx but we are facing the errors:

Screenshot 2023-09-13 at 14 42 35

Expected behavior 🤔

nonce-value assigned to Cache added to <Style/> tags so that they are identified and nonce-values match

Context 🔦

we want to add CSP headers to harden security to our Remixjs app.

Your environment 🌎

`npx @mui/envinfo`
  Chrome, Safari, Firefox
 System:
    OS: macOS 13.5.2
  Binaries:
    Node: 18.16.1 - /usr/local/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 9.6.6 - /opt/homebrew/bin/npm
  Browsers:
    Chrome: 116.0.5845.187
    Edge: Not Found
    Safari: 16.6
  npmPackages:
    @emotion/react: 11.11.1 => 11.11.1 
    @emotion/styled: 11.11.0 => 11.11.0 
    @mui/base: 5.0.0-beta.14 => 5.0.0-beta.14 
    @mui/core-downloads-tracker:  5.14.8 
    @mui/material: 5.14.8 => 5.14.8 
    @mui/private-theming:  5.14.8 
    @mui/styled-engine:  5.14.8 
    @mui/system: 5.14.8 => 5.14.8 
    @mui/types:  7.2.4 
    @mui/utils:  5.14.8 
    @mui/x-data-grid:  6.13.0 
    @mui/x-data-grid-pro: 6.13.0 => 6.13.0 
    @mui/x-license-pro: 6.10.2 => 6.10.2 
    @types/react: 18.2.21 => 18.2.21 
    react: 18.2.0 => 18.2.0 
    react-dom: 18.2.0 => 18.2.0 
    typescript: 5.2.2 => 5.2.2 
@Mateo-P Mateo-P added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Sep 13, 2023
@zannager zannager added the package: styles Specific to @mui/styles. Legacy package, @material-ui/styled-engine is taking over in v5. label Sep 14, 2023
@Mateo-P
Copy link
Author

Mateo-P commented Sep 21, 2023

Hello @mnajdova 🤠
any updates regarding this?

@mnajdova
Copy link
Member

It's hard to know what may be miss-configured without a repo example to look into. Can you create a simpler reproduction and share link to the repository? Thanks!

@mnajdova mnajdova added status: waiting for author Issue with insufficient information and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Sep 27, 2023
@github-actions
Copy link

github-actions bot commented Oct 4, 2023

Since the issue is missing key information and has been inactive for 7 days, it has been automatically closed. If you wish to see the issue reopened, please provide the missing information.

@github-actions github-actions bot closed this as completed Oct 4, 2023
@Mateo-P
Copy link
Author

Mateo-P commented Oct 11, 2023

hi @mnajdova
Here is a repo to see the error:
https://github.com/Mateo-P/nonce-problem.git

my thought is that the inconsistency is on the createEmotionCache function.
why does it need to be executed on the client, entry.client.tsx?
this is causing the nonce to have different values in the server and client. for instance, when hardcoding a nonce value the erros go away

@github-actions github-actions bot removed the status: waiting for author Issue with insufficient information label Oct 11, 2023
@github-actions github-actions bot reopened this Oct 11, 2023
@oliviertassinari oliviertassinari changed the title [CSP-createCache] nonce-value added to Cache not applied to inline-styles [CSP] createCache nonce-value added to Cache not applied to inline-styles Sep 11, 2024
@mjulstein
Copy link

I have the same problem when applying nonce's described in the doc, but on a NextJS project. #40435
I think all cases where the style attribute is used should be replaced with a child style element. (This is done by the emotion dependency, but maybe it's not the case for the main source?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package: styles Specific to @mui/styles. Legacy package, @material-ui/styled-engine is taking over in v5.
Projects
None yet
Development

No branches or pull requests

4 participants