I'd like to propose a change to the ServiceWorker spec that allows for applications which cannot be forced by the server to install updates.
Native applications have a significant security advantage over web applications: they control their own update lifecycles. New code is not loaded by default, so if there is no auto-update functionality built in, new versions are only adopted at the user's will. By contrast, web applications load new, untrusted code from a server each time the user navigates to them. This increases the attack surface for malicious code to be deployed by whoever has control of the server (hackers, political actors, rogue service administrators, etc.).
The Application Cache API allowed this feature; servers could set the AppCache manifest max-age very far in the future which would prevent the browser from contacting the server. However, servers configured to cache aggressively would cache these manifests and brick apps accidentally, leaving developers unable to deploy fixes to their apps. Since this problem was common, the ServiceWorker spec is defined to limit max-age to 24 hours, after which an update is ensured to happen.
Server-forced updates need to be prevented in the following cases:
For an example of code that uses this ability of the Application Cache API, see hyperboot (written by @substack, the author of browserify).
In the ServiceWorker spec, preventing forced updates must only happen when explicitly requested, so as not to cause the accidental bricking tragedies seen with AppCache.
Possible methods for adopting this feature:
Service-Worker-Max-Age header (#721). Servers likely won't set this header by default.Service-Worker-No-Forced-Updates header, that should only be used to specifically opt in to this feature, which will remove the 24-hour cap.These changes still allow for applications to trigger their own updates by unregistering their ServiceWorker and registering the new version. It may also be beneficial to add a method which triggers a reload of the registered ServiceWorker and does the standard byte-for-byte comparison to see if there is an update.
Thanks for considering this proposal. I believe this small change would make ServiceWorkers much more powerful, and bring the web a huge step closer to parity with native applications.
Side-note: it was a great idea by you guys to develop this spec on Github. :+1: This allows for a very open discussion. (I have never made any proposals like this to any sort of standards body before this).
I can't quite get my dead around the use cases.
Handling private keys or other sensitive data
Apps that rely on anonymity or encryption, such as TOR or secure messaging
How does the current update model prevent this?
ServiceWorkers which verify integrity and authenticity of updates loaded from an untrusted CDN
SW scripts are same-origin for security reasons. When you're adding thigns to the cache you can already verify integrity, although CSP is a better mechanism for this.
I can't quite get my dead around the use cases.
Imagine we have created a PGP app to encrypt messages, and we serve it at https://mypgpapp.com. It generates private keys and stores them in IndexedDB, then lets the user encrypt messages with those keys so they can copy their encrypted messages to send via email.
So far, our security is pretty good. Since we used HTTPS, we can be reasonably sure the user won't load a backdoored script from some attacker's server. However, what happens if _our server_ gets compromised? If an attacker had access to it, they could deploy an update to the registered SW script which fetches code that will upload the private key somewhere. Now it's game over for any user who visits the app and has their SW script caching expire.
How does the current update model prevent this?
By this example I meant that when the app is being hosted on servers owned by some other party, the host who actually runs the machines could deploy backdoored code. But if a SW prevented forced updating, it could be built to check that the updated version is cryptographically signed by the author before accepting updates. Then, the author can sign the resources offline, and never has to give the private key to the host to have the users verify authenticity.
CSP doesn't solve this since the malicious scripts are coming from a whitelisted domain.
(I suppose I didn't need to mention integrity, authenticity was the important part).
Related if not dup of #761
Use case of giving the user the ability to optionally stop an update is enough for me. Consider my sw could give this chance if updating patch numbers but forcing the update if increasing minor or major numbers.
I resonate with this use-case (I worked on the Firefox OS mail app which similarly wanted what amounted to signed-offline app packages), but I think giving the service-worker the ability to defeat upgrades of itself is the wrong way to handle it. (And I think this issue is roughly equivalent to 761, although this issue more concretely describes motivating use-cases.)
The security model of https is trust in an origin, authenticated by the certificate authority (CA) infrastructure, with compromises being handled via OCSP (stapling) and/or short-lived certificates. Using a trust-on-first-use service worker that can deny upgrades is a clever way to attempt to embed an offline signature based security/trust model inside the existing online signature trust model. Some people have even come up with an exceedingly clever approach also combining HPKP and AppCache, see https://www.reddit.com/r/encryption/comments/4027ci/how_2_spacex_alums_are_using_encryption_for_good/cyywmc8
The major risk of a prevent-update feature, as covered by Jake, in #761 is allowing an evil/attacker-controlled service worker to extend the duration of its nefarious residency, potentially permanently. And also as Jake suggests, it seems better to make the validation part of a standard already concerned with validating contents, namely CSP which already has precedent with its SRI-like "valid hash" mechanism https://w3c.github.io/webappsec-csp/2/#source-list-valid-hashes. This also allows the browser to alert the user, helping avoid spoofable UI and related confusion/fatigue.
Additionally:
The big wrinkle is that what is really needed is a cross-browser effort with this specific use-case in mind because it really is its own big picture idea. I believe there are many people who care about the use-case, but I suspect most browser engines are focusing their efforts just on getting service workers and progressive web apps going. The best hope for this use-case right now are browser extensions/add-ons. All browsers seem to be converging on WebExtensions and at least some browsers (ex: Firefox) allow easy disabling of extension auto-updates. Which is important since the trust-model looks like it depends on the extensions marketplaces' authentication mechanisms and them not being compromised themselves, which is strictly weaker than an air-gapped private key. (NB: Pre-WebExtensions Firefox extensions can, however, be cryptographically signed.) This is clearly nowhere as good as a packaged apps model that does not require installation like Firefox OS was shooting for, but it seems to be the most realistic cross-browser solution.
[Sorry for the 1.5 year late reply :) coming here from #1208, which is similar to this issue but instead of preventing the update, the old Service Worker warns the user about the update.]
@asutherland and others have brought up valid concerns about this approach, e.g., what if the user force-refreshes the web app, what if they open it in an incognito window, what about when they open it for the first time, what about on a new device. I agree with all of those, in fact, I proposed a different solution built on Certificate Transparency a while back that would solve those things, and if browsers implemented that, I would be very happy too :)
However, a solution built on Service Workers has the big advantages that
And I think it would solve the biggest part of this problem, especially because any attempt to send the user unsigned code runs the risk of detection by the SW, unless the server can somehow detect that the SW is being bypassed. That might be possible in the case of force-refresh (Cache-Control headers), though. Maybe we can use the SW to always send force-refresh-like Cache-Control headers?
And while some have indeed used browser extension to solve this problem (Signal, blockchain.info and mega.nz come to mind, although the first two of those are Chrome apps, which are getting phased out. I myself made an old-style Firefox addon), it would be preferable if all users are protected, and they don't even protect against most of the loopholes ("what if they open it in an incognito window, what about when they open it for the first time, what about on a new device").
Hi,
I want to secure my app by storing each asset SHA256SUM on a blockchain, then allow update when on-line file match the shasum. This way release have to be signed by author/auditors before update. This is an innovative use case of webworker/blockchain that can bring a new level of security to web applications, because an attacker would have to gain access to both the service and the private keys to perform a mass-attack.
However, this will be impossible to do as long as the service worker update is forced. Does someone knows about an alternate way to achieve this or eventually a feature implementation that could open this possibility?
I understand the issue about attacker being able to leverage the service worker self-control to implement malicious code durably. I'm not sure how this could be mitigated.
I've been thinking a bit about it and I believe we can solve this one properly.
The core idea is having the installed service worker being able to check the new available version before installation; Eventually, it would have the power to prevent it if certain condition are not met (essentially if the new version is judged insecure). This should be an exceptional event, especially as securing service worker update would make the attack useless. I'm basing this affirmation on the proposed use case for this feature so far.
On the other hand, we don't want an attacker to install a corrupted service worker that wouldn't allow any update, implementing durably malicious code. This attack could happen massively on websites which didn't implement an integrity check on service worker update. It could also happen on supposedly secured website on a per-user basis. Meaning, by altering someone's browser files by physical access or exploiting a flaw in the operating system. This last attack doesn't scale, but has the viciousness of breaking a system in which the user would have the highest confidence, which could lead to the biggest damage (think banking/crypto currency apps).
A natural solution respecting thoses two requirements would be warning the user when an update have been blocked. This highlight that an exceptional situation is happening, and that something may need to be done about it. The active service worker would have to provide a message explaining the reason why it blocked an update, and the user would be able to inform herself on social medias / forums /... about what's happening. An option would be provided to update anyway.
The banner could use the same UI than the one proposing PWA installation. I would advice to use a sober banner, but to put the message telling the update have been blocked in red. I would rather avoid something like what we've seen in the past where everything becomes orange and red and convey more fear than knowledge to non-technical people. But, some bit of something is needed to call the attention of the user, as we want to be sure the exceptional situation is known by her.
I would rather make the banner non-intrusive and the navigation to continue. If closed, a new pop would happen on the next normal cache update if the situation is still abnormal (so every 24h or more often depending on cache headers). Another strategy could be blocking totally the navigation. This would prevent corrupted code to run at all in some case (locally corrupted service worker). On the other hand, it could give some incentive to allow the update no matter what so the website can be used. I think some further thinking on this very aspect is needed.
Technically, I would expect this feature to be available from the service worker environment. There could be an event so we can use self.addEventListener('updateAvailable', ...). waitUntil would be available to allow for further fetching/computation. The 'updateAvailable' event would be triggered again at normal cache interval. This would allow to perform an update that have first been blocked but that have been verified as valid at a later time.
The function blocking the update could be more widely available as 'navigator.serviceWorker.rejectUpdate(message)'. The hard-coded reaction of the browser, which's showing a banner,聽 would prevent any abuse/hackerish use of this fonctionality.
I have no technical knowledge about how service workers are currently implemented, so this may not really fit. I'm basing this mostly on how I would find it convenient and logical to use as a website developer. It would be nice if someone could confirm this actually make sense implementation-wise.
I'd like to ask anybody interested in this feature to participate and challenge the design I'm proposing. Better think good about it before going ahead. I'd also like to hear about implementators and weither they would consider this solution as acceptable and implementable. I think we have a nice opportunity to push further the usefulness of this new web technology, so I hope we can go ahead and add this one to the specs.
It's been 3 month since I did propose a fix to this, and got no answer. Is there anything I can do so we can move on with this? Should I go ahead and make contact somewhere else? Or is it already settled ?
This issue is on the upcoming ServiceWorkers F2F agenda for discussion in October, although I can't promise any resolution.
Note that my previous comments still stand about the security model of the web and in particular, the Clear-Site-Data header since implemented by Chrome and Firefox has a "kill switch" which will wipe out all storage for an origin including ServiceWorkers. That's an intentional feature that a SW would never be able to inhibit even if normal updates could be blocked or delayed.
There is a new experimental option that seems better suited to your use-case. There's a very experimental Mozilla project https://github.com/mozilla/libdweb that enables WebExtensions to implement custom protocols like IPFS (see the blog post).
In regards to your proposal, I understand the core of your suggestion to be that a banner would be presented if the potentially-malicious SW blocks an update and that the potentially-malicious SW is able to provide some of the text to be displayed in the banner. The user would then reach a decision informed by searching the web and asking other people on social media what they should do.
The issue with prompting the user in cases like this is that the user is frequently unable to make an informed decision about what is going on. Especially if the attacker can supply scary text like "There is a virus in the update, don't update or your computer will be permanently damaged!" or text that leverages update fatigue like "The update will take 10 minutes to install, are you sure you want to update?" or unique strings that the attacker can use to game any search the user would make so that the guidance they find on the internet is from the attacker. This isn't really solving the problem, it's making the problem the user's problem.
Thank you for the answer and feeding the search.
My proposal has indeed nothing to do with bypassing clear browser cache functionality.
One of my premise was that in legit case, the banner wouldn't show for long as the server take-over is likely to be fixed within hours or at worth within days.
On the other hand, a malicious SW would continue to pop a warning forever until updated, incentivising user to do something about it. Non technical users are likely to accept the update after a few days and wipe malicious SW just to get rid of the banner. Not saying it's so great, just saying it leverages blind behaviors.
Now there may be a other ways to leverage this difference in update denial timespan, like setting a reasonable time limit to rejection at 24 or 48 hours.
The problem I see in their cases is that domain owners may know users have a malicious SW but remains powerless about it.
Another option (if it's acceptable for browsers) I can see is having the SW rejection option being enabled for a website through a DNS TXT field.
In this scenario, the malicious SW squat attack against a website that hadn't the SW update rejection enabled could work only by taking over the DNS, and only as long as control over DNS remains as legit owner would be able to switch off the rejection option, triggering SW renewal.
The TXT field would be either inexistent (option not enabled) either a number that represent for how much minutes an update rejection remains valid, maybe with a reasonable maximum limit.
Just thought I should chime in as I was the one who opened this issue:
I now understand the rationale in preventing updates. I previously thought the 24-hour limit was to prevent the accidental bricking of apps by well-meaning server admins, but really it's about preventing an attacker from intentionally bricking the app forever (so my proposed solution of using response headers doesn't really help). I no longer think it makes sense to be able to fully prevent updates in the Service Worker API.
BTW, I have some ideas about how to accomplish what I want with a Subresource Integrity attribute for iframes, hopefully that will be implemented some day.
I can't quite get my dead around the use cases.
The exact use case @mappum was describing seems to be what is being discussed over here. Although I can imagine not only E2EE apps can benefit from this. Almost any downloaded application uses codesigning to verify updates nowadays.
Servide Workers require https by default which I guess is the reason why code signed updates aren't really a thing on the web, and perhaps they're not really necessary. But I believe some applications could still benefit from this.
Perhaps being able to prevent server-forced updates is too generic. Maybe what we need here is something more specific to code signing updates. So that only Service Workers that make use of code signing may prevent server forced updates.
I'm not really sure how this would work though, because that way a malicious script could still block a Service Worker from getting updated by introducing code signing for the first time.
Most helpful comment
Imagine we have created a PGP app to encrypt messages, and we serve it at https://mypgpapp.com. It generates private keys and stores them in IndexedDB, then lets the user encrypt messages with those keys so they can copy their encrypted messages to send via email.
So far, our security is pretty good. Since we used HTTPS, we can be reasonably sure the user won't load a backdoored script from some attacker's server. However, what happens if _our server_ gets compromised? If an attacker had access to it, they could deploy an update to the registered SW script which fetches code that will upload the private key somewhere. Now it's game over for any user who visits the app and has their SW script caching expire.
By this example I meant that when the app is being hosted on servers owned by some other party, the host who actually runs the machines could deploy backdoored code. But if a SW prevented forced updating, it could be built to check that the updated version is cryptographically signed by the author before accepting updates. Then, the author can sign the resources offline, and never has to give the private key to the host to have the users verify authenticity.
CSP doesn't solve this since the malicious scripts are coming from a whitelisted domain.
(I suppose I didn't need to mention integrity, authenticity was the important part).