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

How to handle Scripts that are coming from Google Tag Manager #31

Closed
themudassarhassan opened this issue May 25, 2022 · 9 comments · Fixed by #36
Closed

How to handle Scripts that are coming from Google Tag Manager #31

themudassarhassan opened this issue May 25, 2022 · 9 comments · Fixed by #36
Labels
question Further information is requested

Comments

@themudassarhassan
Copy link

themudassarhassan commented May 25, 2022

I am trying to implement strict CSP in an application and found this package. It looks like this package will handle both cases where either page is rendered on the server side(using nonce implementation) or page is statically generated at the build time( hash based implementation). My understanding is that hash based implementation will generate hash for scripts available at build time and it will not work for scripts that will be fetched via Google Tag Manager(when the website will be loaded on client side) and GTM adds scripts dynamically based on some user actions so I think those scripts will get blocked in hash based implementation. For that I was using nonce based approach but nonce based approach does not work for statically generated pages.

The application in which I'm working I was able to setup strict CSP for server rendered pages using nonce based approach and was able to allow custom GTM scripts to load using this technique and I need a way to setup strict CSP for statically generated pages as well.
Any suggestions will be warmly welcomed. :)

@nibtime
Copy link
Owner

nibtime commented May 25, 2022

Hi @themudassarhassan

if you use the package with Next 12 and the drop-in components for next/document it should work with GTM. With 'strict-dynamic', trust propagation is transitive. If you use the following setup it should work, the components decide per page whether to use Nonce-based approach (page with getServerSideProps) or Hash-based approach (page with getStaticProps).

In pages/_middleware.js

import { chain, nextSafe, strictDynamic } from '@next-safe/middleware';

const isDev = process.env.NODE_ENV === 'development';

export default chain(nextSafe({ isDev }), strictDynamic());

In pages/_document.js

import Document, { provideComponents } from '@next-safe/middleware/dist/document';
import { Html, Main } from 'next/document';
import React from 'react';

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    return initialProps
  }

  render() {
    // those components are automagically wired with strictDynamic
    const { Head, NextScript } = provideComponents(this.props);
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

in pages/_app.js

import Script from 'next/script';

const gtmInlineScript = `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');
`

function MyApp({ Component, pageProps }) {
  return (
    <>
      <Script
        id="gtm"
        strategy="beforeInteractive"
      >
        {gtmInlineScript}
      </Script>
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;

Please let me know if this works for you or if you have problems or further questions.

@themudassarhassan
Copy link
Author

Hi @nibtime

Thanks for the detailed explanation. I'll try to setup this package in the application today and will see how things go with GTM. My only concern is whether hash based implementation will work for custom GTM tags or not, if it works that will be great!

@nibtime
Copy link
Owner

nibtime commented May 26, 2022

Hi @themudassarhassan

it will work from the CSP side of thing, both for Hash-based and Nonce-based and the package makes the decision for you per page.

Why it will work, see https://content-security-policy.com/strict-dynamic/

I made the package primarily for my own use cases, and those include Tag Manager and also Consent Tools. They both are heavy in dynamic script insertion.

The transitive trust propagation with GTM works as such:

  1. GTM inline script is inserted with next/script strategy beforeInteractive. It will be hashed on static pages or get the nonce on dynamic pages. It is trusted by CSP

  2. GTM inserts custom tag scripts dynamically in non-"parser-inserted" fashion. Because GTM is trusted by CSP and custom tag scripts are added in non-"parser-inserted" fashion, the custom tag scripts are also trusted by CSP, even though their Hash isn't included in CSP

Please let me know if that works for your specific use case or if I;ve been missing something, so I can fix it.

@themudassarhassan
Copy link
Author

That perfectly makes sense @nibtime. I'll let you know about the result. Thanks again for jumping!

@themudassarhassan
Copy link
Author

Hi @nibtime
How would you disable some of the CSP directives? For example I want to disable font-src style-src image-src.
Setting these to false is not working. Thanks!

@nibtime nibtime added the question Further information is requested label May 27, 2022
@nibtime
Copy link
Owner

nibtime commented May 27, 2022

Hi @themudassarhassan

the middleware for CSP base config behaves exactly like https://trezy.gitbook.io/next-safe/usage/configuration. Unfortunately, is doesn't seem to support disabling single directive, only whole CSP with false.

Fortunately, this package has utils for building middleware around CSPs so you can accomplish what you want quite easy with them. I forked the demo project for the package and made a setup that accomplishes what you want:

https://stackblitz.com/edit/nextjs-vg1ozj?file=pages%2F_middleware.js

Hope this solves your problem.

@themudassarhassan
Copy link
Author

Hi @nibtime
Thanks for the example to demonstrate the work around to disable some directives. I'm able to disable any directive accept style-src which gets included for pages with getServerSideProps in the production build of the app. Can you help with this?

@nibtime
Copy link
Owner

nibtime commented Jun 4, 2022

Hi @themudassarhassan

that is indeed a problem of the lib. For 0.6.0 I experimented a lot with what's possible with respect to avoid unsafe-inline in style-src. I already realized, that Document.getinitialProps is loaded with too much stuff and I should split it up and modularize to accommodate different use cases better (#29).

But there is a simple workaround you can do in the meantime, by using Document.getInitialProps from next/document and connecting it with the nonce from middleware with a few lines of code yourself. Apart from the inline style stuff, that's all Document.getInitialProps from @next-safe/middleware/dist/document does.

How to do that, see here: https://stackblitz.com/edit/nextjs-vg1ozj?file=pages%2F_middleware.js,pages%2F_document.js

@nibtime
Copy link
Owner

nibtime commented Jul 9, 2022

In 0.8.0 and 0.7.0 I created a more versatile getCspInitialProps, provided a minimalistic csp middleware with full IntelliSense + exported utils for conveniently doing custom logic on CSP.

Those should solve all configuration issues mentioned here.

@nibtime nibtime pinned this issue Jul 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
2 participants