material-components / material-web

Material Design Web Components
https://material-web.dev
Apache License 2.0
9.2k stars 881 forks source link

Using a strict CSP throws errors for some components #5135

Open kimtaa opened 11 months ago

kimtaa commented 11 months ago

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/web@1.0.1

Browser/OS/Node environment

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

datvm commented 10 months ago

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.

kimtaa commented 10 months ago

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)..

KTibow commented 10 months ago

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.

kimtaa commented 10 months ago

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)}

asyncLiz commented 10 months ago

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.

kimtaa commented 10 months ago

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:

KTibow commented 10 months ago

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})`?

kimtaa commented 10 months ago

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?

asyncLiz commented 10 months ago

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

jlesquembre commented 1 month ago

Could we use adoptedStyleSheets to solve the problem?

rictic commented 1 month ago

Yeah, looks like fundamental issue with how we implemented styleMap. Filed in Lit core as https://github.com/lit/lit/issues/4719

If you don't need the ability to dynamically remove style properties from a style map, I think there's a a work around that could work: https://lit.dev/playground/#gist=0985f5699785c208bcc837e14e89664a