Lightning App Security & Architecture

by Tankred Hase

In a recent post we covered the new Lightning App from a UX perspective. In this post we’ll dig deeper into its software architecture with a focus on security and threat modeling.

Electron Architecture


App Architecture

As depicted above, the desktop application is based on Electron. Electron allows cross-platform desktop applications to be developed from a single code base for Windows, Mac and Linux. It’s based on the Chromium project from Google and is being developed by GitHub.

The app’s user interface runs in Electron’s rendering process (Window). The UI is written with the JavaScript libraries React Native (via react-native-web) and MobX (for state management). This stack allows us to reuse a large part of the code for our upcoming iOS and Android apps, while preventing code duplication per platform.

The app also packages our very own lnd which it communicates with over GRPC. Since Electron has some well known attack surfaces, we take several steps to mitigate these risks. In the following we’ll scope our app’s threat model and discuss several mitigation strategies.

Threat models and attack surfaces

Device is stolen or lost

The most obvious threat to any bitcoin wallet is device theft or loss. Several steps can be taken both on the user’s end as well as on the app architecture end to mitigate these threats.

For one, the user should always have their seed written down and backed up to a safe place. We encourage this in our application during the onboarding process and also make the user confirm that they have written down the seed.

Another step to mitigate device theft is to encrypt the wallet on disk with a password that is chosen by the user during the onboarding process.

Phishing and Transaction Spoofing

One of the most common attack vectors against users on the web and email is phishing. An attacker could send the user an email with a malicious lightning: uri. The application also takes several steps to mitigate this attack.

For one, incoming invoices are checked to make sure they don’t try to inject malicious code. Also, when opening the app from a link, a user must first input their wallet password to unlock the wallet. Last, but not least, the user has to confirm the lightning transaction and amount. Another step we might take in the future is to warn the user if the amount is above a certain threshold if we see this attack vector being used in the wild.

Compromised user space (malware)

Since the app runs on desktop operating systems such as Windows or macOS we need to be aware of malware that might have compromised the user’s machine. This is a common problem with most client side cryptography applications as endpoint security can easily be compromised this way.

An obvious precautionary measure users should apply first and foremost is to limit the funds per device to about the same amount of cash they might carry in their physical wallets. This allows the majority of the user’s savings to remain in cold storage.

Other steps might be to use a device with a TPM like a Chromebook or an OS with strong app sandboxing like iOS. These mitigations obviously need to happen on the user’s end as there is little the application can do to enforce these best practices. But we hope this list might give security conscious users a few simple guidelines. And we’ll work with UX researchers and users to figure out how to best educate users during the installation process.

Root level or physical access (key logger)

One threat model that is nearly impossible to mitigate in software is root level or physical access to a device a.k.a the evil roommate attack. In order to secure the user against these attacks, the application would need hardware wallet support. We do intend to add support for hardware wallets in future releases, but the current version does not support them, which is why this threat model is currently out of scope.

Vulnerable or malicious npm dependency

Like many JavaScript applications we use npm as our package manager. This might open the app up to certain attacks e.g. if an adversary published a malicious npm package that our code depends on (as in the recent eslint attack and yesterday’s event-stream attack on Copay wallets).

One step that our application takes to mitigate this attack vector is locking dependencies in the package-lock.json which stores the SHA-512 hashes of npm tarballs and verifies the integrity of dependencies when executing npm install. We also use GitHub security notifications in case one of our packages is out of date. With this feature, GitHub warns repository owners of potential vulnerabilities and recommends an update to mitigate the threat.

Finally, in case Electron or another app dependency does have a vulnerability, we can keep the vulnerability window small since our app uses auto-update. This is done using electron-updater and signed application builds published on GitHub. Upon download, electron-updater verifies the app’s signature and prompts the user to install the update in case they are running an out-of-date version.

Users can also choose to build their own application binaries directly from the GitHub repo in case they want to control the exact version they’re running. In this case, auto update will be disabled. Since most users will probably not be storing large quantities of bitcoin in their wallets anyway, we recommend using auto-update for most users, as this will ensure that you’re running the most up-to-date version with all security patches. This also reduces fragmentation and compatibility issues caused by unpatched desktop users, as our iOS and Android apps will use the native install/update mechanism in the respective app stores.

XSS in the rendering process

Since many desktop applications now use Electron, it has become the target of several XSS (cross-site scripting) and RCE (remote code execution) attacks. This is because by default, the platform isn’t configured using the recommended security best practices. Developers need to opt-in to most of these security features, which we’ll discuss in the following.

In general, most of the application’s attack surface is contained within the JavaScript code and Electron’s rendering process. A very simple mitigation strategy that we take is to run lnd in a separate child process. This child process holds the user’s private key material and this key material stays within lnd and is never exposed to the rendering process.

We also make sure to escape all strings that are rendering to and read from the DOM to mitigate XSS. When used correctly, React already does this out of the box.

Another step we take is to set a strict CSP (Content Security Policy) which prevents inline script from being executed, should an attacker successfully execute an injection attack.

Furthermore, we disable all node apis in the rendering process. Not only does this prevent file system access in the case of an XSS attack, it also enforces good architecture design by proxying GRPC calls via the Electron main process rather than in the rendering process directly. Since this does require access to Chromium’s IPC (inter process communication) apis, we make sure to not give the rendering process direct access to these apis, but rather whitelist only the required calls to and from lnd.

Last but not least, we enable the Chromium sandbox for the rendering process. This further reduces attack surface in the case of successful compromise of the rendering process.

Remote access to lnd’s GRPC api

In order to mitigate remote access to lnd via its grpc api, we employ several layers of security. First the wallet apis are only accessible while lnd is running and after the user has unlocked the wallet using their password. Second we employ macaroons in case an adversary has successfully compromised another host in the user’s private network e.g. for a DNS rebinding attack. Credit goes to @bitconner for a PoC attack in case macaroons are disabled.

Try the alpha release on testnet!

This concludes the architecture and security overview of our new desktop application. The application code is open source and licensed under GPLv3. To try the alpha release on testnet, just do the following:

  1. Download the latest release for your OS:
  2. Head on over to a faucet and fund your wallet with testnet BTC:
  3. Wait a few minutes for the wallet to sync. Once completed, the app will open payment channels automatically. The funding transactions need to confirm just like regular on-chain transactions.
  4. Go to a site that supports Lightning like Yalls and buy something:

That’s it!

We’re excited to hear your feedback as the alpha just represents the first step in an ongoing conversation with users. You can reach us by opening an issue on GitHub or our other developer resources. Thanks in advance for your input!