-
Notifications
You must be signed in to change notification settings - Fork 35
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
Create explainer.md #16
Conversation
Long overdue, this is an attempt to describe the API and its reasons for existing
What do you think, @esprehn @domenic @mkruisselbrink @dmurph @annevk |
This looks pretty good to me. My notes while reading:
|
Thanks for taking a look at this, @domenic! Responses inline below:
Yes, an array of cookie objects like {name: '...', value: '...'}, reflecting only those cookie fields visible in Cookie: headers or String(document.cookie).
The idea here is mostly to avoid firing up a service worker or JS handler when a cookie it doesn't care about changes.
I'm not completely decided on this myself. On the one hand, the HTTP header is Set-Cookie; on the other hand, the effective keys for this operation are odd, including domain, name, and path, of which only name will be supplied in many cases, and the "get" operation only reveals the name and value, not the rest of the key, and I'm concerned that calling it "set" will mislead developers into thinking that a cookie name/value they "get" can then be updated via "set" with the same name and a different value, whereas in fact they also need to correctly determine the hidden parts of the key too.
I don't see this as a powerful feature outside of ServiceWorker. I believe there are separate efforts underway to protect secure origins from cookie attacks carried out on unsecured origins in the same domain, including @mikewest's https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00 and https://tools.ietf.org/html/draft-ietf-httpbis-cookie-alone-00 I think you still need to be able to read and write non-"Secure"-flagged cookies (e.g. domain-wide UI preference cookies) from a ServiceWorker to interoperate with the existing web, and I also think this API is mainly improving convenience without introducing any security-relevant new powers (other than JS cookie access from SW, which is already secure-origin-only), and so the benefit from allowing all web content content (regardless of origin) to migrate to the new API likely outweighs any downside.
Not entirely sure about this one either, but I think "expires" (despite being the parameter name used in Set-Cookie) wrongly suggests that its value is boolean, whereas in fact it is a nullable timestamp with behavior side-effects (Session vs. non-Session lifetime). I was even thinking of allowing the special value 'session' here to indicate the non-timestamp behavior. What do you think?
Agreed. I'll add clarifying text and/or examples to make this more obvious.
Agreed. I think it's useful primarily when the value encodes a capability which is not usable directly from script, e.g. an OAuth 2 authorization code - in that case because the script lacks the client certificate/client secret needed to redeem the code for a refresh token, access token and/or other bearer tokens due to the script running outside the server-side application's trust perimeter.
I was somewhat reluctant to do this due to the same multipart-key concerns that led to my slightly preferring "put" to "set". However, I'm not deeply attached to this and can add sugar for this if you think it will make a noticeable usability improvement.
Agreed, this is broken. After some offline discussion with @dmurph I'm leaning toward ripping out and redoing the observe API (and adding unobserve) based on newer versions of the IndexedDB observer API.
Good point. I think I meant both:
and
This is intended to allow e.g. background tab power-saving modes where cookie change notifications occur only periodically (e.g. every ten minutes) or are temporarily suspended (e.g. due to power-saving modes) and only later resumed. This is also intended to offer no better guarantees about change notification than you would get today using String(document.cookie) inside setInterval.
The "document." is a typo, I'll delete that. Thanks for spotting it! I'll fix that and also add an explanation. Summary: registering interest is needed so that .observe(...) will be side-effect-free and so that events that will cause a stopped ServiceWorker to be started are all very explicitly under the control of the ServiceWorker APIs without strange side effect-wakeups due to use of unrelated APIs. Edit: clicked Comment too soon! Finished the dangling partial sentences |
This probably ties in to my other question about the service worker API. I would restrict this kind of matching to the service worker specific "interest registration" API, and not have coookieStore.match/matchAll.
My guess is that you'd find plenty of people on various browser security teams who disagree with you; in many senses, cookies are the original powerful feature. It seems very clear that we would be restricting cookies to secure origins if they were created today. Perpetuating that mistake in new APIs seems bad. I think it would be good to consult with Chrome Security (e.g. @mikewest) on this.
I don't feel strongly; I just thought it was weird to depart from well-known cookie terminology.
Sure, but setInterval of how many milliseconds? :) It sounds like the intent here is to not offer any guarantees. I'm a bit worried that will lead people to polling in order to get better guarantees. But maybe it's fine. In any case, it'd be good to be more explicit in the explainer, like you were in your second bullet.
The weird part about this API is that it causes an one API (cookieStore.observe, or whatever it ends up being) to be totally broken until some "unrelated" API (event.registerCookieInterest) is called. I'm not sure there is a good fix; you either have to have this action at a distance, or you have to give the ability to wake up a service worker to an API that is not service worker specific. I tried to look into how the push API does this, and came away a bit confused; it doesn't seem to use InstallEvent, but it is of course very tied to service workers. In that case I guess assigning |
A few notes:
|
Thanks for taking a look at this, @inexorabletash - responses inline below:
Agreed. Probably this should either be modified to take a RegExp pattern as a string, or to get rid of regexp matching entirely.
Yes, I don't actually like the regular expressions here at all.
Not that I'm aware of. Fetch interception is prefix-based, postMessage is '*' (by itself) or exact match, most others are exact-match.
Agreed. Indeed, cookie names with well-known prefixes is really the only special case I care about here. Maybe we can start with (String or Array). At that point I'm inclined to remove match/matchAll entirely and switch to using "get"/"getAll" with a new optional "matchPrefix" boolean in the optional options bag. What do you think about that?
I like it :)
I agree with this. Should we also disallow HTTP-compatible date strings, though?
Agreed, I will add sugar for this.
I'm hoping that (at least in document contexts) this API can be polyfilled. My hope is to not force rejection on eTLD+1 because it's not something that is easily implemented in a polyfill.
Would moving this to a CookieObserver instance make it any more palatable? |
cookieStore.get('COOKIENAME').observe(cookie => console.log(
cookie ?
('New value: ' + cookie.value) :
'No longer set or no longer visible to script')) This is pretty strange, it's using a subclass of Promise here so you have then() and observe(). I think we should follow the observer pattern that the rest of the platform is using (ResizeObserver, IntersectionObserver, MutationObserver, IDBObserver) so you'd do: var observer = new CookieObserver(function(changeRecords) {
// changeRecords has information about what cookies changed.
});
observer.observe(...patterns here...); |
I think I lean towards allowing them, but we need to specify where the parsing occurs, and what about error cases. Part of that may involve comparing HTTP date parsing vs. ECMAScript date parsing (which may be impl dependent???) and see how well they align/diverge.
I don't think we should constrain ourselves to what's polyfillable here, if it improves the behavior of the API. Would developers be likely to rely on that behavior, i.e. is it useful/very common to probe that failure case? Or is it more commonly a bug?
Ah, yes it would. The explainer has |
Follow-up to @domenic's follow-up inlined:
I'm also hoping that a developer can avoid needing to handle changes to cookies they do not care about. Would it feel any better if match/matchAll were gone and there were a prefix-matching option and an array-of-names option for get/getAll instead?
Is the idea that by making a cleaner cookie API available in unsecured contexts we would be inadvertently encouraging broader use of cookies in those contexts? In any case, I agree that this is worth discussing further.
Ok, and this is certainly API feedback I'd like to have more of :)
I think the requested interval does not matter when the timers are sometimes suspended indefinitely (so long as they don't get fresh focus) in background tabs in existing browsers. My intent is to not require the browser to spend more CPU time on those tabs than it already does, and ideally much less (by eliminating the attempted polling which is harmful in foreground tabs but may already be ineffective already in tabs without focus or perhaps in out-of-view IFRAMEs.)
I agree, and I'll try to be more explicit about it. I think the guarantee should be something like "no worse than a 5-second polling check using setInterval in a top-level tab with UI focus or its IFRAMEs that are currently visible or absolutely positioned offscreen (i.e., hidden service IFRAMEs which may still be ) or a ServiceWorker processing requests on behalf of such a context, ditto but 10 minutes for other contexts". What do you think? Is that too explicit? (I suspect so...) In any case I think those are the sorts of polling loops I'm aiming to replace, except that often they are not sophisticated enough to throttle themselves.
It's not entirely broken: it works fine for the remaining duration of the script execution context (and stops working after that, just like every other side-effect-free event handler registration.)
Right, that was precisely the sort of weirdness I was hoping to avoid a repetition of. If you have any other suggestions on how to accomplish this, I'm definitely interested! |
Follow-up to @inexorabletash's follow-up inlined:
On further thought I think we could implement this sanely. So rejecting in at least most such cases should not be a problem.
I suspect successful code rarely or never hits this case. I'm not sure there's anything particularly useful a developer can do in this case. However, I think we already indirectly expose the eTLD+1 restrictions through the unrelated
Agreed, that is a typo. |
Thanks for taking a look at this, @esprehn! I'm inlining my responses below:
Agreed, I'll be changing to that (and likely also eliminating match/matchAll entirely.)
Yes, exactly like that :) |
Not really. I prefer a compositional API that encourages you to use JavaScript's existing ability to use boolean logic to avoid taking actions (e.g.,
It's hard for me to say. I guess this might be a place for implementers to weigh in more... Getting the spec-ese right here is going to be tricky, but first we'd want to see what people are OK with implementing. |
- cookieList is indeed an array. Examples have been expanded to show this - regexp is gone in favor of strict name matching, name prefix matching, and "give me everything" where the developer-supplied script sorts it out; tried to strike a balance between not waking up handlers when the change is to an unrelated cookie that happens to be in-scope and the matching is definitely practical to implement in the browser (exact match or exact prefix match - both are used in ServiceWorker already) on the one hand and allowing JS to decide whether a change is interesting on the other hand (omit interest list and you get called back every time) - switched to `get` - added `delete` - proposed restricting write operations to secure contexts - `expires` rather than `expiration`, added explicit examples of numeric expiration timestamps - added some initial discussion of security implications - overhauled change monitoring APIs - attempted to explain change coalescing - attempted to explain ServiceWorker API and rationale - `match`/`matchAll` are gone - `await` - silent failures are gone - changed event name
- minor reformatting - note limitation of ServiceWorker cookie monitoring
I've attempted to incorporate your feedback, though I may have overlooked or misunderstood some of it. Please take a look and let me know what you think, @domenic @inexorabletash @esprehn |
clarify that the only ephemeral script contexts supported is ServiceWorker
Clarifications
BTW, typical cookie scanners described on the web seem to use setInterval and do not even bother throttling when the window/tab is blurred and poll at rates in the 1-10 Hz range: I have also seen implementations that automatically adapt the polling interval based on focus/blur and requested-vs.-actual time of invocation, which combines to give crude load-reduction and likely improves power consumption somewhat, but still is far more expensive than not polling. The first linked page above also mentions the cookie API for Chrome extensions (https://developer.chrome.com/extensions/cookies#event-onChanged) which has efficient change detection and may be an improvement over the change monitoring API currently outlined in this explainer. It does, however, assume a different security model than the one applicable to |
typofix
This is starting to look pretty solid. It might be time to merge and move the discussion to separate issues, but for now I'll just continue leaving comments...
Why restrict to in-scope? As noted, the security boundary here should be the origin... And why restrict this to service workers? I might name the parameter
I still think the correct way to do this is to have people do filtering in JavaScript with
Why a separate
The semantics of this are not very clear to me, but I imagine a more formal spec will help.
Does this match the other observers on the platform? I am pretty sure it does not match MutationObserver... |
Thanks for the additional feedback, @domenic ! Feedback inlined below:
Thanks! I'll go ahead and do that.
For URL scope my intent was to allow access to all URLs for which the ServiceWorker could control a page and inject script to achieve the same effect using existing APIs - in other words not an expansion of ServiceWorker power, just an improvement in efficiency and ease of use. I realize that by successfully guessing/constructing a 404 page URL which allows IFRAME-ing and then running script inside it the same technique could expand to the whole origin, but a carefully constructed site (one where no pages are IFRAME-able) can actually deny this capability to a path-scoped ServiceWorker today and I was reluctant to remove that restriction without further discussion of the implications. I'd especially like to understand why ServiceWorker fetch interception and foreign fetch interception should be path-scoped when cookie access is not. Would it be OK to move that discussion to a separate issue?
I chose
I have seen quite a few cookie change monitors that are prefix-based (even though it's frequently implemented using a regular expression match, the intent is clearly prefix matching in most cases), and I would like to give them a working implementation without each one having to reinvent the filtering (perhaps incorrectly - some of the regular expression matching accidentally does substring matching when prefix is the clear intent from context.) This also allows an implementation to avoid even running JavaScript when the change is to an irrelevant cookie. However, I'd still like to understand the rationale here more completely. Can we move this discussion to a separate issue, perhaps?
Good point. I'll change to that, and I'll also update it to make it clear that the observer instance is passed in as the second parameter to the callback, and add a
The intent of cookieChange.urls is to enumerate all the observed URLs (asURL-s?) to which the update element is applicable.
I intended to follow MutationObserver's behavior for multiple overlapping observations, but perhaps I got it wrong. I realize it's not the spec, but I read this explanation at https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver :
The living standard https://dom.spec.whatwg.org/#dom-mutationobserver-observe also suggests that additional calls to
|
- Mention cookie-aversion and other contexts with disallowed cookie access, and our chosen approach: reject operations, allow monitoring (but don't call back unless/until reads are possible) - Explain why we path-scope a ServiceWorker/disallow reading for out-of-scope URLs - Describe how fragile path-scoping is for this API - Mention possibly-surprising port number handling - Mention possibly-surprising different-path cookie-writing - Removed unobserve, replaced it with less-flexible disconnect - Changed description of observe to clarify additive behavior and lack of duplicated change reports - Changed (optional) matchType to accept one of two values: 'equals' and 'startsWith'; clarified that 'equals' is the default - CookieStore and URL are now one-per-item in CookieChange entries - The first parameter for CookieObserver callbacks and event.data for the CookieChangeEvent are now simply arrays of cookie changes - The second paramter for CookieObserver callbacks is now the CookieObserver - The url is now per-CookieInterest item in calls to observe and InstallEvent's registerCookieChangeInterest
@domenic : I have attempted to clean up observe and InstallEvent's registerCookieChangeInterest and also the shape of the parameters passed in event.data for CookieChangeEvent and as the first parameter in the CookieObserver callback. I think I will go ahead and merge, and further issues can be filed and tracked independently. |
I neglected to address this part of @domenic 's feedback earlier pertaining to reading cookies and monitoring for cookie changes on non-default URLs:
This is for the same reason that I propose to restrict the read operations and monitoring to in-scope URLs for ServiceWorkers: I believe a carefully-designed site can probably deny this capability to pages it serves today (though few do), at least in the absence of user interaction (due to popup-blocking), and I don't intend to give websites any major new cookie-related powers they don't already have through Should we later decide that it is useful and reasonable to extend this capability to document contexts and/or out-of-scope URLs, it should be very easy to do so without breaking by-then-extant uses of the API. On the other hand, once granted such additional capabilities would be very difficult to retroactively take away without breakage. |
Changes I inadvertently neglected in #16 : - Fix bit-rot in countMatchingSimpleOriginCookies example - Add missing url+getAll examples (countMatchingCookiesForRequestUrl and countAllCookiesForRequestUrl)
Missed a couple minor changes here which I'm making now in #17 - please take a look, and also let me know there if I missed anything else |
Long overdue, this is an attempt to describe the API and its reasons for existing