helmetjs / helmet

Help secure Express apps with various HTTP headers

Home Page:https://helmetjs.github.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Helmet at nodejs project. Content Security Policy directive

eugenestepanyuk opened this issue · comments

Hi! I'm trying to add to the electronic signature project helmet. The widget is connected via iframe.
.use(helmet())
And after that there is a mistake of this kind
1

After that I tried to add directives to contentSecurityPolicy:

.use(helmet())
.use(
    helmet.contentSecurityPolicy({
    directives: {
       ...helmet.contentSecurityPolicy.getDefaultDirectives(),
       "script-src": ["'self'", "'unsafe-inline'", "http://localhost:80/"],
       "script-src-attr": ["'none'", "'unsafe-inline'", "http://localhost:80/"],
       "connect-src": ["'self'", "http://localhost:80/"],
       },
   })
)

But such a mistake arises, the solution of which I did not find, maybe there will be some advice
2

I use the script directly on the HTML page and read the information that helmet does not like it and I need to put the script into the module and connect it. Right?
<script type="module" src="..."></script>
I did it, but the error remains.
3

I tried many options, the question arose, how safe is it if you completely disable contentSecurityPolicy?

I will be grateful in helping this problem

It looks like the Content-Security-Policy header is not being updated after you make changes. Can you do a hard refresh in your browser?

Yes, sure, I tried a hard refresh, the mistake remains

Update
I put the script into the module and the need for use of "script-src" and "script-src-attr" headers disappeared. Changed the resolution to "connect-src" to localhost:3030, and the first part of the script worked, but an error arose after trying to load the widget
1

After that, I added a resolution of the "frame-src" header

.use(
   helmet.contentSecurityPolicy({
      directives: {
          ...helmet.contentSecurityPolicy.getDefaultDirectives(),
          "connect-src": ["'self'", "http://localhost:3030/"],
          "frame-src": ["'self'", "https://eu.msn.com.ua/"],
       },
   })
)

There are no more errors of the headlines, but the iframe widget Does not open the site eu.msn.com.ua does not allow you to install the connection. Without Helmet, the widget opens. What could be the reason in this case?

Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests;connect-src 'self' http://localhost:3030/;frame-src 'self' https://eu.msn.com.ua/

I'm not sure if this is a problem with Content Security Policy or with a different part of Helmet.

Do you see different behavior between these two snippets?

// Just use Helmet's Content-Security-Policy middleware.
// `helmet()` should not be used elsewhere.
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      ...helmet.contentSecurityPolicy.getDefaultDirectives(),
      "connect-src": ["'self'", "http://localhost:3030/"],
      "frame-src": ["'self'", "https://eu.msn.com.ua/"],
    },
  }),
);
// Use all of Helmet, but configure the Content-Security-Policy middleware.
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        ...helmet.contentSecurityPolicy.getDefaultDirectives(),
        "connect-src": ["'self'", "http://localhost:3030/"],
        "frame-src": ["'self'", "https://eu.msn.com.ua/"],
      },
    },
  }),
);

found this warning in Network tab
Request URL: https://eu.msn.com.ua/sign-widget/v20200811/?address=http://localhost&formType=1&debug=false
1

Could this be the reason for blocking the widget?
I changed the code to:

.use(
        helmet({
          contentSecurityPolicy: {
            directives: {
              ...helmet.contentSecurityPolicy.getDefaultDirectives(),
              "connect-src": ["'self'", "http://localhost:3030/"],
              "frame-src": ["'self'", "https://eu.msn.com.ua/"],
            },
          },
          crossOriginResourcePolicy: { policy: "cross-origin" }
        })
      )

but it did not help

You're embedding an iframe from another origin, triggering the document's "cross-origin embedder policy". This policy, controlled by the Cross-Origin-Embedder-Policy header, has three modes:

  1. unsafe-none, the default value. Your page can load cross-origin resources without permission from the remote origin.

    For example, your page on example.com would be able to load <img src="https://example.com/foo.png"> with no issues.

  2. require-corp, Helmet's default value. Your page can load cross-origin resources, but the remote origin has to give explicit permission. It can do this with the Cross-Origin-Resource-Policy header or by using CORS.

    For example, your page on example.com can load <img src="https://example.com/foo.png"> only if uilogos.co allows it. They'd need to do some work to support this.

  3. credentialless. Your page can load cross-origin resources, but they can't include credentials like cookies. This isn't supported by all browsers.

I would recommend trying the credentialless policy to see if that works for you.

app.use(
  helmet({
    // ...
    crossOriginEmbedderPolicy: { policy: 'credentialless' }
  })
)

adding credentialless policy didn't help, the warning remains for some reason

What if you disable it?

app.use(
  helmet({
    // ...
    crossOriginEmbedderPolicy: false
  })
)

didn't help either

tried setting for iframe as stated in the article, did not help
https://web.dev/coop-coep/?utm_source=devtools
image

in Application tab, chapter Frames
image

To be clear: did you set the crossorigin attribute on the iframe?

This is how the iframe element is created in the signature library file

var iframe = document.createElement("iframe");
iframe.setAttribute("src", src + srcParams);
iframe.setAttribute("id", id);
iframe.setAttribute("frameborder", "0");
iframe.setAttribute("allowtransparency", "true");
iframe.setAttribute("width", "100%");
iframe.setAttribute("height", "100%");
iframe.setAttribute("crossorigin", "anonymous");
document.getElementById(parentId).appendChild(iframe);

I added the line iframe.setAttribute("crossorigin", "anonymous"); but it did not help
Access is denied, as I understand it, on the line adding an iframe element to the page document.getElementById(parentId).appendChild(iframe);

The issue still persists, is there any other solution?

Sorry for my slow response.

I don't know why this is happening, but some part of Helmet seems to be causing it.

The top-level helmet function is a wrapper around 14 smaller middlewares. I would start by disabling all of them and slowly re-introducing them, one by one,

For example, start with this:

app.use(helmet({
  contentSecurityPolicy: false,
  crossOriginEmbedderPolicy: false,
  crossOriginOpenerPolicy: false,
  crossOriginResourcePolicy: false,
  dnsPrefetchControl: false,
  frameguard: false,
  hidePoweredBy: false,
  hsts: false,
  ieNoOpen: false,
  noSniff: false,
  originAgentCluster: false,
  permittedCrossDomainPolicies: false,
  referrerPolicy: false,
  xssFilter: false,
});

This won't do anything at all!

Then just try reintroducing CSP:

app.use(helmet({
  contentSecurityPolicy: { /* ... */ },
  crossOriginEmbedderPolicy: false,
  crossOriginOpenerPolicy: false,
  // ...
});

Then try introducing the next one:

app.use(helmet({
  contentSecurityPolicy: { /* ... */ },
  crossOriginEmbedderPolicy: true,
  crossOriginOpenerPolicy: false,
  // ...
});

Eventually, I hope this will help you find the culprit.

I tested middlewares

.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        ...helmet.contentSecurityPolicy.getDefaultDirectives(),
        "connect-src": ["'self'", "http://localhost:3030/"],
        "frame-src": ["'self'", "https://eu.msn.com.ua/"],
      },
    },
    crossOriginEmbedderPolicy: false,
    crossOriginOpenerPolicy: true,
    crossOriginResourcePolicy: true,
    dnsPrefetchControl: true,
    frameguard: true,
    hidePoweredBy: true,
    hsts: true,
    ieNoOpen: true,
    noSniff: true,
    originAgentCluster: true,
    permittedCrossDomainPolicies: true,
    referrerPolicy: true,
    xssFilter: true,
  })
)

in this format the widget opens, mby problem in crossOriginEmbedderPolicy, { policy: "credentialless" } dosen't help

In that case, it sounds like disabling that one middleware solves your problem. I would recommend doing that.

Does that help?

yes, with crossOriginEmbedderPolicy: false the widget works.
Please tell me, will this affect safety? I read, this may leave my app vulnerable to certain types of attacks.

And read info
If you are experiencing an access error for the iframe widget when crossOriginEmbedderPolicy is set to true, it is likely because the iframe is trying to access a resource that is restricted by the policy. You will need to ensure that the iframe is not trying to access any restricted resources, or you can consider adding a crossOriginOpenerPolicy header to your server response, which can provide a more lenient policy for cross-origin iframe loading.

What do you say about this?

I tried set headers

crossOriginResourcePolicy: { policy: "same-origin" },
crossOriginOpenerPolicy: { policy: "same-origin"}

but that doesn't help either

image
this error doesn't go away

tl;dr: You're probably safe to disable it.

First, there's the embedder and the resource. You control the embedder—it's your website. You don't control the resource, en.msn.com.ua, which is what you're putting in an iframe.

Cross-Origin-Embedder-Policy is a header for the embedder. It says how the browser should embed cross-origin resources.

If it's set to unsafe-none (or is unset), the browser can embed whatever resource it wants. If it's set to require-corp, the browser can only embed resources that support CORS or set the Cross-Origin-Resource-Policy header. (There's a third option, credentialless, which doesn't work in all browsers.)

There are some side-channel vulnerabilities when embedding an iframe in the same address space as your site. An iframe on another site might be able to read the memory of your site! That's bad! But there's good news: modern browsers protect against this even without any special headers. You need these headers for certain advanced things, like SharedArrayBuffer, but most people don't need this. Because of this, I plan to disable this header (by default) in future versions of Helmet.

As you might expect, you have no control over en.msn.com.ua, so you can't make them set any headers. That's why you're seeing those errors despite setting the Cross-Origin-Resource-Policy header yourself: the error is coming from the resource and has nothing to do with the header you're setting.

Given that you don't control the resource, you have a few options:

  1. Disable the Cross-Origin-Embedder-Policy header. This will let you embed whatever resources you want.
  2. Get en.msn.com.ua to set the Cross-Origin-Resource-Policy header. This probably involves emailing someone and hoping they'll make your change.
  3. Give up on this entirely, and stop trying to embed the iframe.

Given all that, I would recommend the first option—just disable the header on your site.

I'm going to close this issue because I think I've resolved your problem.

thank you for your great help!