Open tabatkins opened 6 years ago
What about visitedness in local files (having file:///url)? I've saw some concerns about someone can save malicious web-page/SVG-image and leak data with embedded scripts.
Browsers already block Referer when the starting page is a local file, so that blocks 2, and browsers already have some ~magic~ for assigning local files an origin (I think each file is just in a unique origin?) so that blocks 1. 3 is still available, but only if you open the local file and then click links in it, which yeah, is already observable by a hostile local file.
Just so I'm clear on the trade off here - I'm reading this as breaking backwards compatibility for some subset of :visited
matching in order to allow custom paint on links and more expressive styling for :visited
. Is that correct?
https://bugzilla.mozilla.org/show_bug.cgi?id=1398414, https://bugs.webkit.org/show_bug.cgi?id=37443 and https://bugs.chromium.org/p/chromium/issues/detail?id=713521 are already-existing issues filed on browsers with very similar ideas, fwiw.
@astearns Yes on the cost, but the benefit is more than just more expressive styling; the current :visited hacks aren't totally secure, and still offer some ways to exfiltrate visitedness. This would be finally closing those holes, limited though they may be. It also simplifies styling code, at the cost of more complex visitedness tracking.
To add to https://github.com/w3c/csswg-drafts/issues/3012#issuecomment-412374078 we found several new timing attacks, so the Paint API is not really at fault here. I think your suggestion @tabatkins is great! (With something like this, we'd even be able to expose a getHistory
JavaScript API without impacting privacy [not that we should].)
@astearns This isn't just about the Paint API.
The paper describes other attacks, which can be summarized as: create a CSS rendering context which is very slow to re-render (SVG with lots of overlapping polygons, extensive nested 3D transforms), then trigger a relatively neutral style property change if :visited
applies (either by changing the:visited
style, or by changing the link href
), while using a requestAnimationFrame()
loop to test whether a repaint was triggered.
And yes, there are browser mitigations that could address that, too. Maybe force a repaint every time a link's visited status needs to be re-evaluated, regardless of computed style changes. Or maybe never re-evaluate existing links visited status or :visited
styles except in response to navigation actions.
But that still doesn't address other problems with :visited
, like using color or blend modes (see https://github.com/w3c/fxtf-drafts/issues/18) to make :visited
links more or less visible to the user & therefore trick users into revealing which ones are visited through their interactions.
The Paint API vulnerability was unique because of its high-throughput. But the fundamental problem is that rendering of a web page relies on information that the web page authors should not be able to access. And adding mitigations for each rendering pathway seems like trying to patch leaks with duct tape when you could just turn off the water at the faucet.
And all that duct tape just makes a sticky tangle of so many language features. E.g., Other open bugs for :visited
include #2263 #2844 #2884 #2037
Going back to Tab's post:
I agree that those 3 cases cover the main situations where :visited
is both useful and of little privacy concern.
The first case should also be of minor implementation cost: you only need to filter out the visited list by the current page origin when matching the pseudoclass.
The other cases would require more implementation overhead, because the history list would need to track referrers for every page. So that might be best left up to browser discretion.
An alternative option would be for the browser to safelist certain origins (e.g., your favourite search engine) on which :visited
could apply to cross-origin links without privacy masking. But again, that would need to be at browser discretion, and hopefully with plenty of transparency to the user about the impact of marking an origin as "safe" in this context.
All that said: For authors, having a fully functional, no-silliness, :visited
pseudoclass for same-origin links would help do things like showing "unread" badges on article lists without hacking around with white text on white backgrounds.
Excited to see this issue finally being tackled! This has been a recurring hurdle when talking about exposing paint timing information, as well as other timing APIs.
Regarding 2, it seems like it should somehow be bound to cookie state and request credentials mode (reliable tracking using Referer also requires having some user ID, as their IP could be shared with other users). Like @tabatkins said, I'm not sure supporting it will add many use-cases, and it will certainly add complexity.
@astearns - some of the use-cases for :visited will indeed be lost. I think the main one is the "cool links of the week" case, where users will no longer be able to know which of those links they already visited when seeing them fly by on their favorite social network. While regrettable, I think it's a small price to pay to fix this issue once and for all.
- [...] as there are a multitude of ways to track outbound links (JS auditing, , link shorteners, etc).
While it is true that tracking users on the server side is technically possible as you suggest, please note that it is now illegal to do so on EU citizens without their explicit consent (thanks to GDPR). So I don't think your argument that "the server can track this anyway" holds because most sites don't have that consent.
The sensible thing to do is deprecated :visited for the general profile and hide it under a user flag that can be enabled for stand-alone web-based software (such as web tech based mobile apps), or manually by the user (globally or per domain).
-> By default no :visited -> If you enable it, full :visited support without any magic that attempts to hack-fix the issue
@MatsPalmgren knowing which links the user visited from a site's origin can also be tracked in JS on the client-side, without tracking the user specifically. The :visited
changes @tabatkins proposed seem similar to that. (but I'm not a lawyer, so not sure where would either fall as far as GDPR goes).
That's a no-op argument. What javascript brings in terms of problems to the user is a different domain. Javascript is meant to tinker with behavioral aspects and one can op to disable it and sensible web authors can opt to still gracefully degrade.
Arguing that "because js may do evil, then css should be able to be evil, too" means users got no choice. Separating css from js is not only a historical design accident (remember NN 4.0 which only ran css if you had js enabled) but is also a logical one with benefits for the user.
If css is kept declarative and non-behavioral then privacy concerns will be small/smaller. The domain of js and privacy is another and should be solved over at the js-and-related camp (I certainly hope for a separation of js-std-libs into different parts that can one by one be enabled/disabled by the user, allowing for some js and disallowing other kinds of js).
Again: Why not keep :visited like it used to be, even relax its restrictions but hide it from the general brower profile to be enabled by users/web-apps. Little tech-debt and no privacy issues (with :visited).
@yoavweiss I'm not a lawyer either, but I did read through a GDPR overview a while ago (I'm a EU citizen and interested to know my new rights) and my understanding is that the intent of the law is that collecting/storing any personal data is illegal without prior explicit consent. I haven't read the actual law text, but I'd assume that it's written in general terms to be future-proof against changes in technology, and against attempts to circumvent it. So the technical details of how or where the data is collected/stored doesn't matter.
I'm pretty sure UA vendors have had actual lawyers look at GDPR to make sure storing link history in the UA itself is legal. I don't know what the GDPR says about unintentionally leaking that data to unauthorised parties though, for example through :visited
as suggested above. A quick web search on the topic suggests that UA vendors are legally responsible for not doing that. This page claims that "companies have to report a data leak within 72 hours after the leak occured" and that "the fines are severe and the same as with an unauthorised usage of personal data". If I understand that correctly, UA vendors can be fined "€20 million or 4% of global annual turnover (whichever is greater)" if they leak :visited
history. Again, I am not a lawyer, so I don't know for sure.
@astearns @yoavweiss: to the point about the cost and breakage, when I designed this "directed visitedness" approach a few years back (a side-effect of Intersection Observers), the research plan called for quantifying the size of the potential breakage. Several factors we discussed could impact this:
:visited
links the same colour as :link
or :active
? Engines should be able to detect this and report back. The fraction of links coloured this way will be unaffected, subtracting from the impact of the change. Anecdotally, many high-traffic sites already colour their links this way (e.g., Facebook).:visited
links that are not in the same back-forward navigation chain? That is, what faction of the time do users rely on :visited
to re-visit sites at some time in the future from their original back/forward clicking?The last one is hard to reason about. A thorough analysis will require user studies to understand if :visited
is frequently used in the negative sense: users relying on it to avoid re-visiting sites they've already seen.
I suppose an apology is in order: I had intended to start this work several years ago but didn't, leaving us with relatively little information to attack the problem with today. Will see if I can make the original design doc public.
Browsers could have a database of visited links per origin. In other words, instead of looking up is_visited(url)
, browsers could look up is_visited(url + origin)
(this is equivalent to tab's 1 and 3. I guess 2 could be added as a special case).
@kornelski This is essentially what we suggest in the paper presenting the attacks mentioned in the first post (p. 10, "Defense"). Tag history entries with the origin(s) to which they should be visible. To make it robust against side channels, this should probably be implemented at the lowest level possible (i.e., do the filtering at the storage engine responsible for executing history data queries, rather than at the renderer).
@kornelski @spinda that doesn't really solve the underlying problem though, it only constrains the problem to the origin (granted, it certainly helps limiting the damage). But, as I understand the GDPR, it is illegal for the UA to leak personal data to the page script in the first place even to the origin. If the page extracts data from the :visited
link database without my consent (illegally) then the UA vendor is liable. I don't see why it matters from a legal POV that the origin could have recorded that data itself (illegally) through other means.
@MatsPalmgren: it's perhaps not worth speculating without more expertise. Someone like @lknik would be in a better position to represent the situation accurately.
More explicitly: as non-lawyers, we can do nothing more than vaguely speculate about the legal implications of anything in this domain, particularly for something as wide-ranging but new-and-untested as GDPR compliance in a corner domain like this.
I suggest we stop speculating and ask our actual lawyers about this, if we believe regulation-compliance is relevant for this.
Yup, I'm merely speculating, since I'm not a lawyer. Seems worth raising the point though since you're proposing that the UA can share personal data with the origin without explicit user consent, IIUC. Precisely what the GDPR is intended to prevent.
This is an interesting idea building on the 20 years of problems of history stealing. But so far what has taught us is that there are quite a lot of ways to exfiltrate this kind of information, some even quite unexpected.
GDPR-wise it's about intent to a large degree (to simplify). So try boiling down to simple rules: just because you technically can, does not mean you should. But on the other hand: deploy tech in a way there are no leaks (state of the art). Still that would be in hands of end users. I would suggest not getting regulations here in the first place, but to focus on a security/privacy model to solve the problem, then see the implications.
And the general starting point ought to be that sites should be unable to allow the detection of (other) sites the user has visited.
A few thoughts here:
While this discussion seems to have been triggered by the paper cited in w3c/css-houdini-drafts#791, it's not clear to me that that paper brings anything particularly new to the discussion about which approach is best for addressing visited link privacy issues introduced through CSS. I think there are two parts to this: (a) I think the high-bandwidth attack that it introduces is fixable as I described in https://github.com/w3c/css-houdini-drafts/issues/791#issuecomment-412604601, and based on discussions we'd had about how we would implement the CSS Paint API in Gecko, I think we would likely have had that fix by default for performance reasons and it wouldn't have needed to be something special for visitedness. (It was also my understanding about how invalidation was intended to interact with the Paint API.) (b) Second, I think the other CSS-related attacks are variants of attacks that focus on performance of repainting in ways that cause differences resulting from link state to delay requestAnimationFrame
cycles, allowing one bit of information per possibly-delayed cycle. I think these attacks are probably fixable using the combination of what roc suggested in 2013 in Mozilla bug 557579 and what @AmeliaBR suggested above in https://github.com/w3c/csswg-drafts/issues/3012#issuecomment-412376605, i.e., by taking an approach that does both repainting whenever it might have happened as a result of an href
change or equivalent. I think we haven't focused on fixing these because they didn't appear (I think) to be substantially higher bandwidth than things like cache timing attacks or other attacks at the network level of the platform, but if we had a coherent strategy for mitigating visited leaks throughout the platform (not just in CSS) then I think it would be worth spending more effort on.
Despite (1), I think it's worth considering the approach Tab suggests above in https://github.com/w3c/csswg-drafts/issues/3012#issue-349828569 (and which I've previously heard suggested by other Googlers) because of the complexity the existing approach to dealing with preventing link history leaks through CSS imposes on adding features to the Web. That complexity has multiple problems: both the time we spend on it, and what happens when we get things wrong. But if we go down this path, I think we should consider the tradeoff against the value that visited links provide to users (and the possibility we could improve that value, for example by counting links that differ only with certain query parameters as equivalent).
I'd note that there is still no proposed remedy within the existing model to the one-bit-per-user-interaction leaks involved in building a game or other user interface where elements are hidden or not depending on whether links were visited. (This was the final bullet point in Jesse Ruderman's original report of CSS visited history sniffing in 2000.) That is clearly an advantage of switching to the alternative model suggested here, though it's not clear to me how important it is given the low rate of information leakage. (For example, if we worry about that... should we also worry about blocking exfiltration of data about the user's mouse movements, finger touches, or scrolling rate while reading text, from which one could infer a good bit about the user's reaction to the text? In other words, where is the limit of what the browser is responsible for blocking?)
I think if we're going to spend efforts making the CSS side more secure here, we should also get commitments from maintainers of other parts of the platform to fix issues in other areas, i.e., commit to making this a solid part of the platform's security model.
I'm not going to speculate on legal issues.
An implementation-specific motivation for redefining :visited
is that a browser's sandboxed renderer process handling requests for an origin might be compromised. The process might currently be able to query the user's full set of visited links, regardless of inbound and outbound origins.
Ideally, :visited
would be defined such that if an origin isn't allowed to know something, a page renderer for that origin doesn't need to know it either.
though it's not clear to me how important it is given the low rate of information leakage. (For example, if we worry about that... should we also worry about blocking exfiltration of data about the user's mouse movements, finger touches, or scrolling rate while reading text, from which one could infer a good bit about the user's reaction to the text? In other words, where is the limit of what the browser is responsible for blocking?)
I think there is a distinction between the channels you mention. In particular, as the attacker I don't need to snoop on the event loop to learn history data from a different context. (Arguably this will remain a challenge even with Tab's proposal and not something I think we need to (yet) tackle.) Today, I can just perform the computation on sensitive data myself and sniff your history.
I was talking about those as other sorts of data that are intrinsically sensitive on their own, not as other channels for leaking history data.
Just to understand fully, if I already visited site A directly and then I search for it on google.com, the link to A in google results might become unvisited because of the proposed cross-origin outbound rule above?
Just to understand fully, if I already visited site A directly and then I search for it on google.com, the link to A in google results might become unvisited because of the proposed cross-origin outbound rule above?
Yes. That's one of the use-cases for which support will get dropped, in favor of better user-privacy and improved visited styles where it is available.
@therealglazou
Just to understand fully, if I already visited site A directly and then I search for it on google.com, the link to A in google results might become unvisited because of the proposed cross-origin outbound rule above?
Yes, based on Tab's proposal.
This is why I suggested an additional option of letting the user safe-list domains for which the full browsing history would be used.
exfiltration of data about the user's mouse movements, finger touches, or scrolling rate while reading text
Actually, I'm all for solving those too, @dbaron
@AmeliaBR:
This is why I suggested an additional option of letting the user safe-list domains for which the full browsing history would be used.
I like this in principle but am generally scared of asking users to intervene (though the current state is worse, so I'd be +1 on this). Are you thinking of it as a permission-like model or browser configuration?
@deian I was thinking browser configuration (like: I have 3rd-party cookies turned off, but I safelist certain domains that can use them). But that really only benefits superusers who poke around in their browser settings.
Another option would be an API method that could be called from the web page that pops up a permission request, if the website author thinks history access would be beneficial. This seems most practical in the context of a larger JS API.
The Working Group just discussed Solve :visited once and for all
.
Yeah, I could maybe see a JS requestPermission() option for asking you to give up all of your browsing history to a site; we give approximately the same permission to any extension that can modify all sites. It's a scary permission to give, but I'd turn it on for Google search, for instance.
Idea: In case :visited
gets removed or restricted, show the info in the browser status bar, next to where the URL currently is shown when hovering. Of course, this would require the user to hover the link, but it would keep at least some functionality.
I like that idea a lot - however mobile/touch devices will not work.
I like that idea a lot - however mobile/touch devices will not work.
Maybe it can be shown in the context menu (long-press menu) – like alt texts for images (Chrome for Android). Not very neat, I know, but better than nothing.
@AmeliaBR
I was thinking browser configuration (like: I have 3rd-party cookies turned off, but I safelist certain domains that can use them). But that really only benefits superusers who poke around in their browser settings.
Another option would be an API method that could be called from the web page that pops up a permission request, if the website author thinks history access would be beneficial. This seems most practical in the context of a larger JS API.
A risk of asking permission, is that the user gives it without understanding the extent of what they're being asked for. Making it a browser configuration protects against that.
We're starting to use permissions more and more to push responsibility for decisions onto users, who for the most part won't understand the ramifications of what they're being asked. To give permission for something like this requires some fairly arcane knowledge that the majority of users simply won't have at their disposal.
@LJWatson
A risk of asking permission, is that the user gives it without understanding the extent of what they're being asked for. Making it a browser configuration protects against that.
Agreed. That's part of why I didn't think a permission prompt for just :visited
CSS made sense. It would need to be worded like "Do you want this website to have access to the entire history of which websites you have visited since [insert date the history was last cleared]?"
And asking for that only makes sense in the context of an API that adds more functionality than just different link colors.
The CSS Working Group just discussed Solve visited once and for all
.
All: this is great! @tabatkins @slightlyoff if it's helpful to have us involved at any point let me know.
EyeDropper
can also be used to leak :visited
(though low-bandwidth). https://github.com/WICG/eyedropper-api/issues/34
What is the status here?
^^ @miketaylr who showed interest in this at some point.
@zcorpan @yoavweiss We are currently implementing partitioned :visited links in Chromium - where partitioning is defined as storing visited links history via partition key, rather than just by the link URL. In our implementation, the partition key is: <link url, top-level site, frame origin>. The best place for more info on the design is the explainer: https://github.com/kyraseevers/Partitioning-visited-links-history. Feel free to reach out if you have any questions!
In https://github.com/w3c/css-houdini-drafts/issues/791 @deian explains a new channel for high-bandwidth leaking of :visited state by observing repaints with the Houdini Paint API. There doesn't appear to be a reasonable way to shut this down under the current regime of partial-censoring of
:visited
- the current fix for Chrome is to just disallow thepaint()
function from being used at all on any<a>
element or its descendants.We really need to finally define a sensible model of
:visible
-state visibility, based on what information would have already leaked to the page via standard, unpluggable channels; then we can finally drop all the silliness around:visited
and just treat it as a plain, ordinary pseudo-class that allows all properties to be used in the standard fashion.At minimum, same-origin visitedness is always visible to the page, as the server can track its own cross-links, assuming standard tracking mechanisms exists (cookies, sufficiently high-entropy user identification, etc). So all same-origin links should report :visited.
Cross-origin inbound links are always visible to the page if the Referer header was sent in the request.
Cross-origin outbound links are always visible to the page if the user visited that link from this origin, as there are a multitude of ways to track outbound links (JS auditing,
<a ping>
, link shorteners, etc).Any others?
If a link matches one of the conditions above, and is visited, it's allowed to match
:visited
; otherwise it never matches:visited
. So, what's the cost/benefit of each of the conditions above?1 is easy to apply and non-controversial. It also probably represents at least half of the benefit of :visited styling for the user - they can tell when they've already visited a given page on a site.
But I don't think it's a whole lot more than half - there is a lot of benefit to knowing what links you've visited from a given page, regardless of origin. (Think of clicking thru each of a list of outbound links, such as in a forum post, or in Google search.) So I think 3 is most of the rest of the benefit, but probably the most controversial in terms of theoretical privacy (even if it's nil in practical privacy for the vast majority of users). I think it's reasonable for browsers to nix this condition if the user is blocking script, as that's the primary tracking mechanism.
I think 2 is of relatively minor benefit, but it lies inbetween 1 and 3 in terms of privacy leaking. Some UAs do offer the ability to block Referer, and of course they would then block this visitedness channel, but otherwise it's common and not a big deal. It's just that most cross-origin pages won't have a link back to the page you visited from; the exception is things like weird cross-site web puzzles, or old-school webrings.
Are there any other conditions that would allow us to safely expose :visited state unreservedly?