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

Consider allowing string values in CSSResults? #451

Closed
43081j opened this issue Jan 16, 2019 · 20 comments · Fixed by #474
Closed

Consider allowing string values in CSSResults? #451

43081j opened this issue Jan 16, 2019 · 20 comments · Fixed by #474

Comments

@43081j
Copy link
Contributor

43081j commented Jan 16, 2019

Currently css only accepts CSSResult values (other css calls) which means we can no longer do something like this:

static get styles() {
  return [css`
    :host {
      background-image: url('${rootPath}images/foo.png');
    }
  `];
}

This is a common pattern I have throughout my project to handle usages of import.meta so assets can have absolute paths.

If you can suggest a better way to handle passing in a path, fair enough, but otherwise it would be handy to be able to interpolate string values here.

@justinfagnani
Copy link
Contributor

This would be a potential security vulnerability.

@43081j
Copy link
Contributor Author

43081j commented Jan 16, 2019

What do you suggest for this use case? It seems like it should be a common one.. wanting to use the meta url in paths.

@justinfagnani
Copy link
Contributor

cc @rictic @mikesamuel

@sorvell sorvell self-assigned this Jan 17, 2019
@sorvell
Copy link
Member

sorvell commented Jan 17, 2019

This should work as long as you use css to define rootPath. This ensures it's a literal from script and addresses the security concern.

const rootPath = css`...`;

@LarsDenBakker
Copy link
Contributor

LarsDenBakker commented Jan 17, 2019

Since rootPath will likely be generated from a variable, its not possible to create it through a css tag. css${import.meta.url} throws an error.

@43081j
Copy link
Contributor Author

43081j commented Jan 17, 2019

^ exactly this @sorvell

its coming from import.meta, we don't define it. as people move more towards using import.meta i imagine this'll become a more common use case too.

FYI you can work around it with new CSSResult(import.meta.url)

@web-padawan
Copy link
Contributor

web-padawan commented Jan 17, 2019

Note: here is how I'm using the CSSResult at the moment to get data from <dom-module>:

import { DomModule } from '@polymer/polymer/lib/elements/dom-module.js';
import { stylesFromTemplate } from '@polymer/polymer/lib/utils/style-gather.js';
import { CSSResult } from 'lit-element';

export const getStyleModule = (id, cb) => {
  const template = DomModule.import(id, 'template');
  let cssText =
    template &&
    stylesFromTemplate(template)
      .map(style => style.textContent)
      .join(' ');
  // callback to process cssText
  // use case: rewrite tag name for slotted children:
  // example: vaadin-item -> lit-item
  if (cb) {
    cssText = cb(cssText);
  }
  return new CSSResult(cssText);
};

The result is added to the styles like this:

static get styles() {
  return [...super.styles, getStyleModule('lumo-button')];
}

See https://github.com/web-padawan/lit-components/tree/master/packages/polymer-style-module

I really hope the export for CSSResult won't be removed as it will break my code 😃

@rictic
Copy link
Contributor

rictic commented Jan 17, 2019

Hm. When running in secure mode, we could add a check that forbade strings but allowed TrustedResourceUrls. I'm not sure how you'd build a TrustedResourceUrl out of import.meta.url though. You might need a new path with compiler checks to validate it.

@jsilvermist
Copy link

Why is this an issue with css but we can interpolate strings in html?

@LarsDenBakker
Copy link
Contributor

Interpolated strings in html are set as textContent, not parsed as HTML. So there is no security risk there. With css there is, but it is unfortunately a big downside for developer experience :(

@justinfagnani
Copy link
Contributor

justinfagnani commented Jan 17, 2019

This could simply be a case where you need to use styles inline with the template for now until we figure out how to feed import.meta.url safely into CSS without opening a hole for arbitrary strings.

I hear there's work on trusted types in Chrome, so there may be a longer-term path forward there.

@bashmish
Copy link

In LitElement you define your styles via a static getter. This does not allow you to use element's instance properties which contain dynamic data which often comes from the untrusted user input. You compose you final styles once on a class level. I don't see where you will get untrusted user input there, you will likely always compose your static styles from other ES modules and just static values originating from your source code.

So I think the problem is exaggerated a lot. It certainly exists theoretically, but how often? I think the solution is unbalanced: it fixes the theoretical issue while breaking a very frequent use case.

@tlouisse
Copy link
Contributor

tlouisse commented Jan 21, 2019

I also would like to use css variables that are reusable in many contexts, not being coupled to CSSResult per se.

For instance: I want to be able to define my (grid) breakpoints as a single source of truth throughout my app, and reuse them in both css and js:
https://stackblitz.com/edit/lit-element-css-usecase?file=css-test.js
(using these variables: https://stackblitz.com/edit/lit-element-css-usecase?file=variables.js)

Or, more generically speaking: I might want to be able to combine my globally defined variables in other contexts (for instance combine it with other technology stacks) that do not rely on CSSResult. It would be nice if I could do that without wrapping all my vars in css``.

Also, in some situations with a lot of small nested css strings (for instance when creating functions comparable to Sass Mixins) it feels a bit cumbersome to wrap every small string in css. It would also be a bit less performant.

Alternative to CSSResult
Instead of using CSSResult, one would also be able to use css as a function and pass in a string array as its first parameter:

const myBreakpoint = 400;
const myStyles = css`
  @media (max-width: ${css([myBreakpoint])}px) {
     ...
  }
`

This would also mean that usage of css by itself doesn't solve a security issue? Since you can do:

const myBreakpoint = somethingPotentiallyMaliciousProvidedByUserInput;

Taking this into account, it would be the responsibility of the developer to make sure there would be no security issues, not the css function.

Sanitization?
The security argument would mainly make sense to me if the css function would do sanitization.
I don't know much about adoptingStylesheets, but would it happen here?

     if (supportsAdoptingStyleSheets) {
        this._styleSheet = new CSSStyleSheet();
        this._styleSheet.replaceSync(this.cssText);
      }

https://github.com/Polymer/lit-element/blob/master/src/lib/css-tag.ts#L30

@mikesamuel
Copy link

Why is this an issue with css but we can interpolate strings in html?

In CSS, the risk is mostly privacy violations.

There's always injecting a background URL that's a transparent image that phones home to a server controlled by the attacker.
Depending on Referrer-Policy this can leak quite a lot.

The other risk is stealing data from the page including from form inputs.
https://www.mike-gualtieri.com/posts/stealing-data-with-css-attack-and-defense is a pretty good writeup on that.

Ideally, URLs in CSS would all be specified as SafeURLs and coerced to a known format like url("...").


This is a common pattern I have throughout my project to handle usages of import.meta so assets can have absolute paths.

Ok, so you already trust the origin import.meta to load script from, so why not trust it for images and fonts too?

Do JS bundlers rewrite import.meta.url to a string literal? For example if I do npm install https://github.com/...?
If so, clients of a library that does this might think they're loading assets from a CDN they control, but images and fonts are coming from the package repository.

@43081j
Copy link
Contributor Author

43081j commented Jan 21, 2019

Ok, so you already trust the origin import.meta to load script from, so why not trust it for images and fonts too?

I do. I don't remember saying I don't, so not sure I quite follow you here...

Do JS bundlers rewrite import.meta.url to a string literal? For example if I do npm install https://github.com/...?

It gets rewritten to a path relative to the output bundle afaik, with static assets output alongside it (as well as other chunks).

@mikesamuel
Copy link

@43081j

I do. I don't remember saying I don't, so not sure I quite follow you here...

Sorry for the confusion. I was asking to see if we're reasoning along the same lines.

It gets rewritten to a path relative to the output bundle afaik, with static assets output alongside it (as well as other chunks).

Thanks for explaining.

@justinfagnani
Copy link
Contributor

@mikesamuel @rictic Would it be feasible to allow strings if we fed the bindings through the sanitizer with a fake<style> as the parent element?

@rictic
Copy link
Contributor

rictic commented Jan 23, 2019

Seems reasonable to me. I'm not sure what values are allowed as style text. Is there a TrustedStyleContent type?

@sorvell
Copy link
Member

sorvell commented Jan 23, 2019

See #471 (comment).

@sorvell sorvell added this to the 2.0.0 milestone Jan 23, 2019
sorvell pushed a commit that referenced this issue Jan 23, 2019
Fixes #451 and #471. Non-literal values can now be included in styling with `css` by using the `unsafeCss` function. This is named "unsafe" so users know this they must use this carefully to avoid security issues.
dfreedm pushed a commit that referenced this issue Jan 24, 2019
* Adds `unsafeCss` for composing values into `css`

Fixes #451 and #471. Non-literal values can now be included in styling with `css` by using the `unsafeCss` function. This is named "unsafe" so users know this they must use this carefully to avoid security issues.

* Address review feedback

* add docs
* refine error message
@mikesamuel
Copy link

@justinfagnani Seems reasonable. Which sanitizer are we talking about though?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants