HACKER Q&A
📣 nibdo

Can you prevent code tampering on client web with temp service worker?


I am trying to find way, how to make web apps more secure with code signing and verifying source code without browser extensions just by using service workers.

Concept is, that service worker will verify any fetched file with signing key and prevent malicious updates (from hacked server, etc.). This is working great till it comes to actual service worker file.

Bad actor can just change SW file, which is executed at browser's level and you cannot intercept it in any way (or I could not find it). Although not everything is accessible from SW context (like window), this is pretty big hole in my attempt.

Only close option I found is using server to serve service worker files on temporary dynamic paths, which are initiated from client and are only for single use.

Path would be forgotten after initial download, so anytime client app tries to fetch new service worker, it would result in error. This is obviously problematic, because it might not work for every browser and you can't control how such errors would be handled in the future and SW might be just invalidated.

Some secure cached wrapper (disconnected via domain config after it is loaded) around iframe could work better, but SW does not intercept fetch requests, so nothing here.

Is it worth investing time into to provide at least some level of awareness (like alerts for users)? I have feeling that investing time in such hacky solutions is not time well spent.


  👤 mgsouth Accepted Answer ✓
I think your threat model is that your application wants to execute or process (signed) resources that are stored on an untrusted third-party server (or one of your servers which is compromised). You're attempting to mitigate issues with a core "service worker" app which loads and verifies the resource before executing it. IANASEAIMMHH*, but

- You're hosed if the core (service worker) app is compromised.

- Adding more layers (a "core core" "secure cached wrapper") doesn't really help. If your core can be compromised, then so can your "core core".

- You can't protect against a malicious user modifying your app; see the long, long history of copy-protection battles. This particularly applies to javascript; obfuscation doesn't work very well. Even if you could, the bad actor could run it in a modified JS engine.

- In general, don't trust the client. The most robust app model is to do all the sensitive stuff on the server, with the client code establishing encrypted sessions and submitting transaction requests. If you do sensitive stuff on the client then you are creating a distributed application which partially runs on an untrustworthy platform you do not control and cannot even examine.

- "Don't put all your eggs in one basket" does not apply to application code security. There is one egg (the "core", "core core", etc.) you must trust. Pulling some of the code out of that egg and putting it in another then means there are two eggs you must trust, and two different trust mechanisms. If you have a web app page that loads a "secure cached wrapper" which loads a "service worker" which loads a resource then you've got four things you must trust. You've got four times the surface area which can be compromised. From a security standpoint the best solution would be for the web app page to include everthing, or at least all the code. Constrain your must-trust eggs to as few baskets as possible and guard those baskets.

- Every time you load code you've introduced a security weak point. From a security footprint standpoint, a one-time-loaded Single Page Application with no external resources would be best. The more typical code-load-per-page app, loading select trusted libraries and resources--not so much. AJAX apps using Really Cool NPM Libraries are a horror show.

* I Am Not A Security Expert And It Makes My Head Hurt


👤 Operyl
I’m confused here, why not just serve your stuff over HTTPS with SRI? Who is the bad actor, the local user?

👤 solardev
Never trust the client.

Offload it to an edge function instead.