Video Summary and Transcription
npm packages are unsanitized inputs from the internet that we run without much scrutiny, so we need to address the issue of malicious packages. Lavamote offers proactive runtime protections to automatically detect and mitigate threats. Lava Mode uses Hardened JavaScript to provide isolation and enforce a policy for your application's build process. The talk introduces a webpack plugin for those who don't want to use the browserify ecosystem. Lavamote's behavior is explored, showcasing how it restricts package access to certain properties. Beta testing is open to gather feedback and improve Lava Mode.
1. Introduction to npm packages
Imagine I gave you a bit of JavaScript code and asked you to run it in your application. Would you do it without checking? Probably not. But if I offered to put it in a tar.gz file, some people would be more willing. That's because npm packages are just tar.gz files that you pull from the internet and install in your application. However, we have to acknowledge that these are unsanitized inputs from the internet that we run without much scrutiny. This is the topic we'll discuss today.
πππππππππππππππππππππππππππππππππποΏ½λ°πππππππππππππππππππππππππππππππππππππππππππππ
Okay, imagine I gave you a bit of text and told you it's JavaScript, it does something, just put it in your application and run it. Would you put it in your application and run it in production for your users? Well, I've done this before, I've asked this question and no one wants to run my code without checking what it is, but if I offered to put it in the tar.gz file, would that help? And now, some people are suddenly more willing to run my code. Why is that? Well, that's because npm packages are just tar.gz files that you pull from the internet, don't read their contents and put them in your application. And don't get me wrong, this is great, I use npm packages all the time, but we have to admit that these are unsanitized inputs from the internet that you put in your application and run without much scrutiny, right? So this is what we're going to talk about today.
2. Dealing with Malicious npm Packages
We need to address the issue of malicious npm packages. Reactive tools like npm audit and SocketDev can help identify potentially malicious packages, but they require manual checking. Alternatively, Lavamote offers proactive runtime protections for your application. Imagine a scenario where a developer installs a build tool, and a malicious hacker inserts code into a dependency. This code can compromise sensitive information. To mitigate this risk, we need an app that can automatically detect such threats.
You know, we're installing npm packages, we're installing a lot of them. But what if some of them are not great? And by not great, I don't mean lousy packages, I published a bunch of lousy packages in my time. Nothing bad happened, but I mean actually malicious packages. This talk is going to be strictly about malicious packages.
How do we handle that situation? Well, there's tools that I call reactive tools. You can use npm audit or anything that goes through CVE reports, etc., and tells you that, hey, this package in your dependencies that has been in production for two months, someone reviewed it and found a problem with it. That package is actually malicious. Is that good enough? Probably not. Then there's SocketDev. SocketDev is this new thing where they use various ways of analyzing the packages automatically and come up with suggestions that, hey, this package, it looks like something's wrong with it. You should look it up, you should check what this package is doing. It looks risky. So SocketDev can tell you that even hours after the package has been published, because it's already analyzed. That's a big improvement. Although you have to do the checking yourself. Do you have time for that? Well, now you have to. What are the other options? Well, you can be proactive instead of reactive. And this is where Lavamote comes in. I'm going to tell you about Lavamote. Lavamote is a bunch of tools that together provide for protections for your application. But those protection happen at runtime instead of when you're looking up packages that you wish to install. Okay?
So how does it work? Well, let's go through the basics here. So imagine this developer guy. He wants to install a build tool for the application. What happens is one of the dev dependencies of his application, somewhere in the tree of dependencies of this build tool is controlled by a malicious hacker represented by a black hat and a hoodie, obviously. And what the hacker does, they put a bit of code in an existing package that was already relied upon. That code takes your GitHub token and sends it somewhere. Is that nice? That's not nice. So would you spot it? Well, it's unlikely that you read everything that you put in your dependencies. So instead, let's use an app for that.
3. Lava Mode and Policy Enforcement
Lava Mode generates a policy for your application's build process and enforces it. The policy is enforced per package, allowing you to control which built-ins, imports, and globals are available. If a package violates the policy, Lavamode prevents it from importing or using restricted items at runtime. Lavamode uses the language itself to defend against obfuscated or dynamically imported code, intercepting access to global variables. This is based on Hardened JavaScript, enabling isolation.
And this is where Lava Mode comes in. Lava Mode does two things at this stage. It first generates a policy. So you run Lava Mode on your application. In this case, your application is the build process. So you run your build process through Lava Mode with a flag that says, oh, generate the policy automatically. And do you get a policy file that you can edit? Let's get to it in a moment. And then your actual script is not running purely in Node, but it's running in Node instrumented by Lava Mode. And Lava Mode is going to make sure that the policy that is set is going to be enforced.
What's that policy all about? This is the policy. This is a short bit of the content of one policy. So you have that eslint plugin, and it has a dependency that the hacker modified, and the use of HTTPS and process env will likely be detected. So it shows up in the policy. You can switch that to false. Or you can remove those items altogether from the policy, which means these are not going to be available. And what's great is the policy is enforced per package. So inside of the same process, inside of the same contexts, we have sandboxed each package separately in a way that you can decide which built-ins or which imports in general are going to be available to it and which globals are going to be available to it.
So now, with the policy change, you will get an error saying that this package, EvilDependency, requested HDDPS, and it was not supposed to do so. So if a package that was only supposed to process strings had a policy generated, this thing is going to prevent it from importing or using some globals at runtime. And I need to double check that you understand only the initial step of generating the policy is processing the code at all. If it finds something that is being used in a regular fashion, it's going to put it in the policy and then you can modify it. But if the package, even if the package is obfuscated or it uses some... It imports things dynamically or it uses some globals dynamically, if the policy didn't detect it, these things didn't end up in the policy, which means at runtime, Lavamode is no longer reading the code. Lavamode is using the language itself to defend. So if any program is capable of reaching the global variable named process, Lavamode is going to intercept that. And we have the power to decide if process is going to be the process that the program is expecting or if this particular package is going to get undefined instead. This is where the power comes from. You can decide, the hacker cannot. Okay? So this is all based on a thing called Hardened JavaScript. Hardened JavaScript will let us do the isolation.
4. Hardened JavaScript and Lava Mode
Hardened JavaScript provides isolation through compartments, lockdown, and harden. Compartments allow for the isolation of processes or tabs, while lockdown repairs and freezes language globals to eliminate prototype pollution. Harden function protects specific objects from untrusted code. The compartment implementation is an early stage proposal pushed by those responsible for object freeze, use strict, and promise. Lava Mode was created to protect Metamask, a browser extension, and uses a browserify based bundler.
And on top of that, we can build the part of Lavamode that decides what can be accessed by whom.
Okay, so Hardened JavaScript is three things. One is compartment. It's a thing that provides this isolation. So you can have compartments in one process in your application or in one tab in your browser. You can have multiple compartments that are isolated from each other. And for that to be safe and to avoid leaving anything in the language that can be messed with, we have lockdown. Lockdown is a thing that you run at the start of your application. And it's going to use the flexibility of JavaScript to remove the flexibility of JavaScript. So lockdown will repair and then freeze all of the globals that are part of the language itself. So object prototype is no longer going to be something that malicious code can fiddle with to do prototype pollution. Lockdown pretty much eliminates the global prototype pollution type of attack. And then last but not least, harden. Harden is a function that you can use to protect a specific object that you want to pass to some code that needs to use it, but you don't want to fully trust that thing. So you can use harden in your application when you're passing some objects to a dependency function and getting them back and then processing them still. So that, you know, they can replace an array with something that just happens to have the reduce method and is going to implement something completely different as the reduce method.
Okay, and compartments, or to be more precise, everything behind the compartment implementation is going to become part of the language. Well, eventually, it's an early stage proposal. There's been a bunch of progress on virtualizing the module loading. This is happening in 2C39, and eventually we will get all of the bits that are necessary to build a compartment in the language itself. And these are being pushed by people who are responsible for bringing us the things like object freeze, use strict or promise. So I'm hopeful.
And we're at a conference that's not exactly about Node. So my previous example was probably less interesting, but we have a bundler. So Lava Mode was created to protect Metamask, the crypto wallet, and Metamask happens to be a browser extension. So it needs to be built, and also it needs to run under content security policy that prevents the use of eval. And the current implementation of hardened JavaScript has to use eval to build an evaluator. In the future, evaluator is also going to be part of the language, but for now, we need an evaluator, but you don't have to use an evaluator that's built on top of eval. If you happen to be a bundler. So we have a browserify based bundler because browserify was the most flexible one and it was the low hanging fruit.
5. Introduction of Webpack Plugin and Beta Testing
We're introducing a webpack plugin for those who don't want to use the browserify ecosystem. Webpack is the most flexible built system, and after four tries, we made the plugin possible. We're opening our beta for testing, so please let us know if you encounter any incompatibilities. This is an open source project, and we want to provide support and fix any issues that arise.
So it got developed a few years back and is now been powering metamask integration with laugh mode for a while. So we can take that and use it right away, but we're introducing a webpack plugin for those of you who don't wish to stay in the browserify ecosystem or even get back to it because yes, yes I know browserify is kind of dated. It was great at the time, but now it's kind of dated. Webpack was the most flexible of the built systems that are currently used. So we went with that. And while it initially seemed impassable, but after four tries we got to a point where the plugin is actually possible. And we're going to open our beta, which is a trial run. So no guarantees for security and production yet, but we want to test it out on your project. And please let us know if you find any incompatibilities where your Webpack plugins get interfered with by our plugin. So please get in touch in the discussion thread. This is the only chance to get life support for the plugin without paying for it in any way because this is an open source project. And while we will probably not support everyone all the time, this is the time where we really want to be in touch with you and look at what you're doing and what trouble you get in so that we can fix it for everyone in the future, okay?
6. Exploring Lavamote's Behavior
Let's take a look at what Lavamote does live. It's a simple application that imports a package called Cookie Monster. The application sets a cookie, prints out the cookie and location host, and calls for a random quote from CookieMonster. The TypeScript file is also included, and the whole thing is built with webpack. The CookieMonster package is allowed to use fetch, math, and encode URI. However, it also attempts to steal cookies by sending them to a server.
So let's take a look at what Lavamote does live. So this is a very simple application. For the sake of brevity, what it's doing is it's importing a package I made called Cookie Monster. By the way, don't look for this package on NPM. It's a malicious package and publishing malicious stuff to NPM is not a good idea. I mean, I've been banned from NPM before. So let me tell you, you shouldn't do it.
And then there's another package just to prove that you can import some CJS old package and it's still gonna work. There's a bit of TypeScript that's being imported. This whole thing is getting bundled and sent into the browser for running. What does it do? It sets a cookie. And then it prints out the document cookie and the location host for context. And then it calls for a random quote from CookieMonster. And the CookieMonster gives us a quote, a famous quote from CookieMonster, it's always fun. We pass it through leftPad because why not? And then we get a reaction from the TypeScript file so that we know that the TypeScript is working. And this whole thing is built with webpack. So we have app.js as entry point. It gets bundled, and it has some plugins that webpack normally uses. But most of all, it has the ScoreTrop plugin. And we pass the policy in here. This could obviously be loaded from a file because why not, but we're passing the policy here. leftPad is not allowed to reach for any globals, that's normal because leftPad is not relying on any globals. And now, CookieMonster... We allow CookieMonster to use fetch, math, and encode URI. So these are the globals that CookieMonster is going to successfully access.
Okay, now let's look at the CookieMonster. CookieMonster is giving us a random quote from a bunch of quotes that are available, but then also attempting to steal our cookies. Well, is a CookieMonster, what did you expect? So yes, it's going to run a fetch and send your host and your cookies to a server to use them in the future. And this is not the best thing to happen to your cookies, mind you. Let's take a look at the behavior of this application.
7. Analyzing Application Behavior and Beta Testing
Let's take a look at the behavior of this application. When running without the Scoretrot plugin, the request sent by the Cookie Monster package steals our cookie. However, when running with Lava Mode, the package is restricted from accessing certain properties, resulting in undefined values. If you want to learn more, I recommend watching earlier talks about hardened JavaScript and taking part in our beta. We provide support to help you set up Lava Mode in your project and appreciate your feedback to improve it.
Let's take a look at the behavior of this application. So I've built this application with and without the Scoretrot plugin, and now I can show you what it's like. So here we are on a page with dev tools open where Scoretrot plugin was not involved. Okay, let me reload the page and show you what happens. So we're on localhost name, chocolate chip is what we have as cookies. We get the quotes and we also get an error because cookie monster was not too discreet with sending this request, but you can see that the request contains our cookie that has been stolen. Meanwhile, if we run with lav modes, you can see that we get undefined. Why do we get undefined? Because, location and document were not listed in the policy. So they do not exist for cookie monster. Meanwhile, our application was successfully accessing them for printing out in the console. That's pretty much what happens here. We're controlling that, not the cookie monster.
Okay, want to know more? There's some earlier talks that I gave about hardened JavaScript, also known as Secure ECMAScript, short for SAS. And Mark Miller, the inventor of hardened JavaScript, had a nice talk with more details that I also recommend you watch. I do recommend you go and take part in our beta, and remember, you can get help to set up your Lava Mode in your project if you just get in touch, either through the beta channel or if you want to run something else than just the Webpack plugin, feel free to get in touch, and I'm offering limited support for limited time to get you up and running. And all that I get back is your issues that you stumbled upon, so we can make Lava Mode even better. Thank you, and stay safe.
Comments