Storing Sensitive Data Offline

At Offline Camp California 2016, we organized a discussion about whether it’s practical to store sensitive information in the browser. Specifically: in an Offline First web application, would it be safe to store secrets in local storage?

This is a contentious issue that’s summed up well in this Stack Overflow discussion. The commenters on Stack Overflow have their reservations, and our discussion group did as well. In short, we decided the answer can be “yes,” but you probably won’t sleep well after you do it.

“Is my op-sec good enough?” Image Credit

Here’s a quick summary of why.

Why would you want to store secrets in the browser?

As a best practice, developers try to send secrets to the host, and clear them from the browser quickly. The reason for this is that browsers are not designed for storing secrets — they’re designed to have secure connections to hosts. With the host available, why bother trying to keep secrets in the browser’s storage at all?

Here’s one very good reason: offline operation.

Consider a point-of-sale application. The operator is swiping customer’s credit cards to complete sales. The app sends the credit card numbers to a payment provider, and then the app promptly forgets the credit card numbers. This works well.

Pictured: significant loss in the day’s sales. Image Credit

However — what happens when the network goes down? Now the payment provider isn’t reachable. Do you stop processing sales? For a business, that could be painful. It may be better to queue transactions offline and risk some bounced payments than to stop accepting credit cards entirely. One bad payment means one lost sale and perhaps lost inventory, but no transactions means no sales at all.

With this offline-operation scenario, we have a good reason to consider storing secrets — the credit card numbers — in local storage. So, what are the risks?

What are the risks?

If you store secrets in local storage, then there are three threat models to consider.

  1. Physical access
  2. Application compromise
  3. Platform compromise

“Physical access” is where somebody picks up the laptop and runs. They’ve stolen the hardware itself, and can now access the user interface.

Son of a gun. Image Credit

“Application compromise” is an attack on the application itself. A common example of this is Cross-Site Scripting (XSS), where the attacker finds a way to inject a script into the page.

“Platform compromise” is an attack on the browser or on the operating system, perhaps by exploiting a buffer overflow, installing a key logger, or tricking the user into installing a malicious extension.

All three threats are difficult to secure against, though recently application compromise has made strides with Content Security Policies. For attacks to be mitigated, all three scenarios need to be protected against.

Physical Access

In order to secure against physical access, the application’s secrets need to be encrypted at rest and moved carefully through memory.

You can, for instance, use the Web Crypto API to password-encrypt secrets before they’re placed in local storage. You would have the user enter the password each time a secret needs to be decrypted, then clear the password and decrypted data.

Don’t forget to clear the password from your sticky note. Image Credit

This works, but not terribly well. If physical access is compromised while the secrets are decrypted in memory, you have no security. Even after the application is closed, the secrets may be recoverable, since the browser doesn’t guarantee that data is removed from memory. It might be as simple as restoring the closed tab.

However, especially if incognito mode is used, this scheme offers some protection from an attacker that takes the laptop and runs. Of course, this scheme won’t work if the application or platform are compromised.

Application Compromise

To protect against application compromise, it’s important to make sure the page doesn’t run unauthorized software.

Cross-Site Scripting is the most common kind of compromise. Browsers have implemented Content Security Policies to guard against this attack, and any application trying to handle secrets must use this.

Clickjacking is another common kind of attack. Again, Content Security Policies are a good defense against them. OWASP provides guidelines about how to use Content Security Policies.

JavaScript hosted by a third party is dangerous when handling secrets, and should be avoided unless you trust the host completely.

Application compromise is actually one of the better-developed areas of defense in the Web, and it continues to get better. Of course, none of it will matter if you have a platform compromise.

Platform Compromise

As the application developer, you can’t directly protect the platform, but you can help your users follow best practices.

An unfortunate platform compromise. Image Credit

One common kind of platform compromise is via browser extensions. Extensions are empowered to read data from, and modify the contents of, Web pages. Extensions that start off as trustworthy have been sold to malware vendors. If your application handles secrets, you should advise users to avoid extensions.

There’s also the risk that the browser or the operating system will be infected with malware from 0-days, and there is no simple way to protect against 0-day exploits except to keep the browser and operating system updated.

So, should you store secrets in the browser?

Tough to say! Most engineers aren’t comfortable saying “yes,” but it’s clear that the situation is improving, especially for Web application security (thanks to Content Security Policies).

Compared to only storing secrets in the host, using local storage increases the cost of an application breach, since the app will have more secrets stored. When weighed against the advantages, however, like the revenue gained during network downtime, the cost of a breach may be worth the risk.

Editor’s Note: The security challenges of Offline First have been a popular topic at each of our camps to date. Here’s the viewpoint of our campers in the Catskills in June 2016: