material-web icon indicating copy to clipboard operation
material-web copied to clipboard

Using a strict CSP throws errors for some components

Open kimtaa opened this issue 2 years ago • 11 comments

What is affected?

Component

Description

When using a strict CSP (i.e with a nonce or without 'unsafe-inline'), I would except no errors to be thrown in the console. However, some components are including styles in a way that is incompatible with a strict CSP.

In the console I get the following error:

lit-html.ts:1836 Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'nonce-random' 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-EDKRS+IvBWVSFJYjnWOymFAGVOawal4UoghutFq1ngI='), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.

The source seems to be adding a style attribute directly to an element. For example, main/textfield/internal/text-field.ts adds style=${styleMap(style)} to the textarea- and input-elements.

Reproduction

An error is thrown in the console about chrome refusing to apply an inline style because it violates the CSP. I've added a simple example in the lit.dev playground: https://lit.dev/playground/#project=W3sibmFtZSI6Im1hdGVyaWFsLWltcG9ydHMuanMiLCJjb250ZW50IjoiaW1wb3J0IFwiQG1hdGVyaWFsL3dlYi90ZXh0ZmllbGQvb3V0bGluZWQtdGV4dC1maWVsZC5qc1wiOyJ9LHsibmFtZSI6ImluZGV4Lmh0bWwiLCJjb250ZW50IjoiPCFET0NUWVBFIGh0bWw-XG5cbjxoZWFkPlxuICA8bWV0YSBodHRwLWVxdWl2PVwiQ29udGVudC1TZWN1cml0eS1Qb2xpY3lcIiBjb250ZW50PVwic3R5bGUtc3JjICdzZWxmJ1wiPlxuPC9oZWFkPlxuXG48c2NyaXB0IHR5cGU9XCJtb2R1bGVcIiBzcmM9XCIuL21hdGVyaWFsLWltcG9ydHMuanNcIj48L3NjcmlwdD5cblxuPG1kLW91dGxpbmVkLXRleHQtZmllbGQgbGFiZWw9XCJTb21lIGxhYmVsXCI-PC9tZC1vdXRsaW5lZC10ZXh0LWZpZWxkPlxuIn0seyJuYW1lIjoicGFja2FnZS5qc29uIiwiY29udGVudCI6IntcbiAgXCJkZXBlbmRlbmNpZXNcIjoge1xuICAgIFwibGl0XCI6IFwiXjIuMC4wXCIsXG4gICAgXCJAbGl0L3JlYWN0aXZlLWVsZW1lbnRcIjogXCJeMS4wLjBcIixcbiAgICBcImxpdC1lbGVtZW50XCI6IFwiXjMuMC4wXCIsXG4gICAgXCJsaXQtaHRtbFwiOiBcIl4yLjAuMFwiXG4gIH1cbn0iLCJoaWRkZW4iOnRydWV9XQ

I have only tested the text-field component, but other components that also add styles directly to elements are: linear-progress, slider and menu. I would guess the issue also affects those components as well.

Workaround

I have not found a workaround

Is this a regression?

No or unsure. This never worked, or I haven't tried before.

Affected versions

@material/[email protected]

Browser/OS/Node environment

Browser: Google Chrome 118.0.5993.88 OS: MacOS 13.5.2 Node: v20.8.0

kimtaa avatar Oct 27 '23 07:10 kimtaa

Interesting this is happening and I haven't noticed even though I use these in Chrome extensions (which doesn't allow unsafe-inline). In your example, the component still works and I don't know why they use inline CSS for certain things instead of constructed stylesheet like the others.

datvm avatar Oct 30 '23 22:10 datvm

The component works despite the error - might it be because it is rendered a second time? But without text direction style since it is not set?

According to the documentation for styleMap: On subsequent renders, any previously set style properties that are undefined or null are removed (set to null)..

kimtaa avatar Oct 31 '23 08:10 kimtaa

I don't know why they use inline CSS for certain things instead of constructed stylesheet like the others.

The component works despite the error

Because all the style does is set the direction, which needs to be dynamic.

KTibow avatar Nov 02 '23 02:11 KTibow

I see the need for dynamic styles - how does one add styles dynamically without using the style attribute?

One could use the classMap() utility to solve this particular problem, but it's not a general, dynamic, solution.

textfield/internal/_shared.scss:

.direction-ltr { 
    direction: ltr; 
}
.direction-rtl {
    direction: rtl;
}

textfield/internal/text-field.ts:

const classes = {
  'direction-rtl': this.textDirection === "rtl",
  'direction-ltr': this.textDirection === "ltr"
};

And then add the classes to the elements with ${classMap(classes)}

kimtaa avatar Nov 02 '23 08:11 kimtaa

That may work for text field, but there are other components that need to bind the style attribute for things that cannot be pre-calculated, like slider.

asyncliz avatar Nov 02 '23 17:11 asyncliz

Yes - it would be much better to find a general solution on how to dynamically set styles, while still playing nice with strict CSPs.

What about using css variables, and updating them through update()?

For example, the linear progress indicator:

  • Add static style with variable: internal/_linear_progress.scss (omitted indeterminate/buffer/max for brevity):
  .primary-bar {
    transform: scaleX(var(--_progress_value));
  }
  • Remove the use of styleMap, and add updating of progress: progress/internal/linear-progress.ts:
  override update(changedProperties: PropertyValueMap<any>) {
    super.update(changedProperties);
    if (changedProperties.has('value')) {
      this.style.setProperty('--_progress_value', this.value.toString());
    }
  }

kimtaa avatar Nov 03 '23 10:11 kimtaa

this.style.setProperty('--_progress_value', this.value.toString());

Now I don't know much about CSPs, but if you can do that why not just do element.style.transform = scaleX(${this.value})`?

KTibow avatar Nov 03 '23 13:11 KTibow

Now I don't know much about CSPs, but if you can do that why not just do element.style.transform = scaleX(${this.value})`?

I don't know much about CSPs either, but that seems to work as well :)

I assume there are cases when a css variable makes more sense, and other cases when setting the style directly is the cleaner option.

In any case - are these two ways to set dynamic styles sound? And not working around the security the CSP is trying to apply?

kimtaa avatar Nov 03 '23 13:11 kimtaa

I don't know enough about CSP, do any of the Lit folks have advice for this? CC @rictic

asyncliz avatar Nov 03 '23 17:11 asyncliz