decaporg / decap-cms

A Git-based CMS for Static Site Generators
https://decapcms.org
MIT License
17.93k stars 3.04k forks source link

CMS does not work with Content Security Policy (CSP). Requires unsafe-eval / unsafe-inline for script-src / style-src #2138

Open ghost opened 5 years ago

ghost commented 5 years ago

Describe the bug I have been using Netlify CMS for sites without a content security policy. I am now trying to increase the security of my sites by adding this. The CSP requires defining what sources are allowed for scripts, styles, frames, objects, etc. The CSP also blocks things like eval in any JS execution and also generally inline-scripts or styles. While it is possible to globally allow these things by setting unsafe-inline or unsafe-eval as permitted, this is not recommended, as it defeats the purpose of the CSP.

Here is the CSP I am applying to one of my sites via special file that Netlify recognises on deploy:

/*
  X-Frame-Options: sameorigin
  X-XSS-Protection: 1; mode=block
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Content-Security-Policy: default-src 'self'; font-src 'self' fonts.googleapis.com https://use.fontawesome.com https://fonts.gstatic.com; img-src 'self'; object-src 'self' https://www.netlify.com https://identity.netlify.com; script-src 'self' https://www.google.com https://www.googletagmanager.com https://www.gstatic.com https://identity.netlify.com https://cdn.jsdelivr.net; style-src 'self' https://fonts.googleapis.com https://use.fontawesome.com; frame-src https://www.google.com 

As you can see, I have already attempted to somehow make anything coming from Netlify permissible, but Firefox states (among other things):

Content Security Policy: The page’s settings blocked the loading of a resource at eval (“script-src”) Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”)

To Reproduce Deploy any site to Netlify with a Content Security Policy which does not permit unsafe-eval or unsafe-inline

Expected behavior Once Netlify has been added as permitted source for scripts, styles, etc. for this to allow loading the CMS. This may require a way to load the Netlify CMS without an inline-script tag, but I am not seeing this issue with other scripts, like google tag manager. Eval may be a bigger issue, as that would require a change to the CMS' code and eval maybe unavoidable.

Applicable Versions: Tested on:

CMS configuration not relevant for this, presumably

ghost commented 5 years ago

I should add that I was able to resolve some of the non-eval issues by using sha256 hashes in script-src (not the ones generated here, but only the ones Google Chrome shows when an element is blocked). The eval issue remains, however, and further inline-styles are required once you get past the initially blocked evals / inlines. It would appear that some of the initial issues were caused by the Netlify Identity widget, but once those are out of the way, further issues crop up when trying to load /admin/

edit:

Because I can't get very clear error messages from Firefox or Chrome, this issue is very hard to diagnose. It seems as though the issues start with the Netlify identity widget and then continue when trying to load the CMS. The identity widget throws an script-src eval-violation in Firefox, but not in Chrome. In Chrome, the identity widget throws a style-src unsafe-inline violation, which does not show in Firefox.

In Firefox, this is where the buck stops. In Chrome, I can advance to the /admin/ URL, where the CMS itself will provide the following error EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' 'sha256-Y1ZIa2N59dlCRGtBP67/dCOpq2eDNPEO+vGJU0pjvEw=' 'sha256-jILZY2UBo/ux6K6qGHntQonoJYNQZfxdLrszRmDpc/o=' 'sha256-v40OvYf1mF3VQM75141gVEb6xZk97Wd9tDERTFg3N38=' 'sha256-tphCtOfPA2HxpbQIjyGzEOxfajrm+crnavJGIhdlT7k=' https://www.google.com https://www.googletagmanager.com https://www.gstatic.com https://identity.netlify.com https://cdn.jsdelivr.net https://www.google-analytics.com".

Chrome also throws multiple inline-style errors and the eval error mentioned before. I believe that without unsafe-inline and unsafe-eval it won't be possible to run either the identity widget or the CMS.

For the style-src issues I think it's down to react components' usage of inline-styles. For eval, you have to look further as it's not just eval itself, but it's also related functions.

I found a discussion regarding react from 2016 and one from 2018 regarding this.

In a recent e-mail conversation, Netlify have confirmed that they have a feature request to fix this, but are not actively looking into it, so for now I shall give up. I'll leave this information here for someone to revisit one day.

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

callmemagnus commented 4 years ago

I tried to create my own local version of the script. It worked until I wanted to add cloudinary with triggers the same issues.

Is there a way around this ?

naton commented 3 years ago

I second the question, is there any way around having to add unsafe-* stuff in the CSP?

alexandrecz commented 3 years ago

Hi guys... any chance to ADMIN works without unsafe?

erezrokah commented 3 years ago

I think this might be related to one of the dependencies we're using https://github.com/netlify/netlify-cms/issues/4367#issuecomment-699884163

alexandrecz commented 3 years ago

Thanks for answer! Do you think we can use CMS without that dependency?

erezrokah commented 3 years ago

No sure actually, it requires some research. We'll be happy to get a contribution for it if someone does the research on it.

taras commented 3 years ago

It looks like the issue might be caused by AJV creating a function at runtime to compile JSON schema into executable code. It's described here https://github.com/ajv-validator/ajv/issues/406

One possible solution is to use standalone mode to compile schemas to code at build time.

taras commented 3 years ago

After a bit more digging, we might have a way to eliminate this problem by compiling schemas during build time.

On https://github.com/netlify/netlify-cms/blob/dbf2920254fb3682e12463a6df8ded4b94b55be0/packages/netlify-cms-core/src/constants/configSchema.js#L364, we'd need to use compiled schema.

To compile schema, we can use AJV Standalone to create executable code that we store as a JavaScript module. There is a Webpack loader that can be used to take care of some of this, but this loader is using ajv-pack which is deprecated.

It looks like the game plan here would be,

  1. Update https://github.com/downace/ajv-json-loader to use AJV 7 and Standalone mode
  2. Configure webpack config to use the updated loader
  3. Refactor getConfigSchema to read value from a module import
  4. Profit $$$
cowboyd commented 2 years ago

@erezrokah After doing some more digging, coding, and research, we're really close on this, but there is one final stumble that we're hitting.

We were able to completely extract the schema, and build the validateConfig function at build time. This works for our site, but there are cases where it will not work for everyone. Specifically, the schema itself can change dynamically based on widget schemas that are added via registerWidget() https://github.com/netlify/netlify-cms/blob/2877f7983e9b5f4ff0cb2cdaaf6549675af68913/packages/netlify-cms-core/src/constants/configSchema.js#L75

Unfortunately, registering widgets happens in the browser, and so it cannot be known at build-time what the complete schema will be including widget extensions.

It seems to me that the only way this can work correctly is to deprecate registration of pieces of schema along with widgets and instead make the schema for user-supplied widgets sufficiently broad to type-check against any widget config. That's a breaking change and would mean slightly less type-safety for config.yaml, but it also means that it can be deployed with a secure content policy because the entire schema would be known at build time.

We're investing so much effort in this because we really like Netlify CMS, but unsafe-eval is unfortunately a deal breaker for our security team. We'd love to find a way forward here, but not really sure what to do at this point.

erezrokah commented 2 years ago

Thanks for the additional information @cowboyd and effort you're putting into this. Is there a way to put this change behind an option? Then users should be able to opt-in to the breaking change.

We could always generate the static schema during build, the generate the dynamic schema by default, or let users opt out of the last part if they'd like.

I also added this to https://github.com/netlify/netlify-cms/issues/5652

cowboyd commented 2 years ago

A compromise option that might be viable is to only do dynamic validation if we detect that we need to because there are widgets registered with custom schemas. Widgets registered that do not have any additional schema would not require any dynamic validation.

// defined at build time
import { staticValidateConfig } from './config.schema.json';

export function validateConfig(config) {
  if (Object.values(getWidgetSchemas().find(schema => !!Object.keys(schema)))) {
    // there are custom widget schemas, we can't use the static validation
    // this is the current `validateConfig` function.
    // this will trigger a CSP error unless `unsafe-eval` is set.
    return dynamicValidateConfig(config)
  } else {
    return staticValidateConfig(config);
  }
}

It's a bit of a strange implicit opt-out, but it does have the advantage of being backwards compatible.

erezrokah commented 2 years ago

I like that idea @cowboyd 💡 !

dagda1 commented 2 years ago

@erezrokah I'm having a look at implementing @cowboyd 's suggestion.

There are a couple of places in the codebase where registerWidget is called.

One is in netlify-cms-app where a number of widgets are registered, and in netlify-cms-core where an UnknownControl is registered.

I wonder if you could help me understand if:

  1. The UnknownControl is always registered as a default editor control.
  2. Do we need to account for the widgets in netlify-cms-app to determine if the schema is a dynamic schema or not?

Thanks for your input

erezrokah commented 2 years ago

Hi 👋

The UnknownControl is always registered as a default editor control.

UnknownControl is always registered, and it's in the "core" since it renders as a fallback when the CMS doesn't find a widget (e.g. someone made a typo in the configuration). See https://github.com/netlify/netlify-cms/blob/dd8f0050c4e2d646e851da28056456c4b76a5738/packages/netlify-cms-core/src/lib/registry.js#L143

We can assume it will never have a schema.

Do we need to account for the widgets in netlify-cms-app to determine if the schema is a dynamic schema or not?

I think the schemas for those could be evaluated at build time, as netlify-cms-app should not reference widgets outside this monorepo.

taras commented 2 years ago

@erezrokah I created a PR that includes changes implemented by @dagda1 and has all of the tests passing. There is a blocking the PR that I'll describe in a comment.

taras commented 2 years ago

With help from @dagda1, I created an example repo that has CSP with Netlify. I created a version of Netlify build that works with CSP. You can find it here https://github.com/taras/create-react-app-csp-strict-example

taras commented 2 years ago

We still need https://github.com/iarna/iarna-toml/issues/45 to fully eliminate eval.

marius-mcfly commented 2 years ago

Any updates here?

afaryab commented 1 year ago

I have managed to solve above issue using exclude option and allowed Unsafe Eval. image