 Hi everyone, I'm Kumavis. I founded MetaMask with Dan Finley and now I run the security research team at MetaMask. Today's talk is the attacker is inside. JavaScript supply chain security and love mode. So yeah, love mode. This is what I've been spending the past couple of years working on. First and foremost, it is a set of security tools for any JavaScript application to mitigate software supply chain risks. So what is the software supply chain? It's everything that touches an application or plays a role in its development. Sonatype has a cool website. It's a timeline of supply chain attacks. You can scroll through it and see how many they are. And a lot of them are from Python and Rust, but a good 50% are from MPM. They're from the JavaScript ecosystem. So why is JavaScript supply chain targeted so frequently? Primarily because it's really popular. This is from the Stack Overflow user survey. And for self-described professional developers, JavaScript is way at the top. And there's TypeScript and Node.js not far behind. So when we're talking about JavaScript supply chain, we're primarily talking about MPM dependencies. So this is your apps dependencies, your dependencies dependencies, your build systems dependencies, any other tools you might be using. You might think that these are your dependencies, just the dependencies listed in package.js on your app. But that's just like an inner ring around your application in part of a constellation of your whole dependency graph. So that's why there's so many Node modules in the Node modules directory. Okay, so to understand what it looks like when it goes wrong, let's look at the copay wallet hack. Copay wallet was a Bitcoin wallet by BitPay. And this is my primary case study for designing lava mode. So at the time, at the end of 2018, the copay wallet hack, also known as the event stream incident, made a lot of news. An attacker got control of a dependency that was used in a Bitcoin wallet, deployed a targeted attack against that wallet, and stole private keys and digital assets from the users of that wallet. And it was, I think, three versions of the wallet that had the attack in it were published. So they didn't even know it was in there for a while. Okay, so let's look at the build pipeline to understand how it got in there. Now, so this is a non-custodial wallet. So the keys are in the user's application only. They're not on the server or something like this. So the attack happened here at runtime. That's where the secrets were. That's where the private keys were. But the evil dependency wasn't even part of the front-end application. It was part of their build system. So it actually entered in here, and then it went on the disk during build time and modified a dependency that ended up in the web app. It could have also happened here at the dependency install time. NPM has life cycle scripts, so after you install a package, it gets a chance to just run any command on your computer to finish up its install process or whatever. So Jackson Palmer of Dogecoin said, BitPay essentially trusted all the upstream developers to never inject malicious code into their wallet, upstream developers being the dependency developers in this case. And yeah, he's right. And it sounds pretty scary when you say it in these terms. And was BitPay being particularly unsafe? Well, this is how we make all JavaScript applications today and most wallets. Not MetaMask. So when this attack happened, there was all this discussion. How do we fix this? Should we never use dependencies? Are we giving up on open source and collaboration? What about don't roll your own crypto? How does that fit in? Audit all your dependencies always? Yeah, that's a great idea. But how many dependencies do you have? Hundreds, thousands, tens of thousands? And when you need to make a change, are you going to be able to wait and re-audit all those things? So is there nothing else? What else do we have here? So around this time, I met Agorik and learned of their EndoJS hard-end JavaScript toolset. So these are tools for working with untrusted JavaScript. And in the core of it, they have this package called SAS. And it gives you a couple of things for dealing with untrusted JavaScript. So there's a couple of things about JavaScript that make it easy to attack. One is everything is mutable by default. All the built-in functionality and everything. So you can just go in and modify array prototype and map and change the way JavaScript works. This can also give you access to objects that are being used like on the complete other side of the application of where you got your attack in. So you want to lock this down. And conveniently, SAS provides a function called lockdown that does exactly this. By calling lockdown, you're doing what's shown here at the bottom of the screen, basically calling object freeze on all the prototypes, all the built-in parts of JavaScript. Okay, that was simple enough. All right, there's this other problem called ambient authority. This means simply by being able to run some JavaScript, you get access to all the powers of that platform, network access or disk access. So say I have some little package. I'm an attacker. I've gotten a hold of some package that's deployed in my victim's code. Now, this package doesn't do very much. It's just formatting strings. But because of ambient authority, I have access to fetch, to network access. I have access to environment variables, and I can send them home to my evil heir. And maybe there's some API keys or something in there. Just because I get to provide a little bit of code, I get to have all powers of the platform. And that's quite dangerous. So SAS provides this notion of a compartment. It puts a little container around the JavaScript and limits access to what that code can modify. So for example, if I have some code that needs to make network requests and it needs to know the current location of the web app and the web browser or something, I can give it just those things and not give it disk access or other powerful features of the web browser. And then I can run my code in here, and it will only get access to those things, as well as the basic JavaScript functionality. You don't need to know the internals, but I think it's really fascinating. I've been working with JavaScript for a long time before I saw how this works, and I didn't think it was possible. So let's take a look. It relies on two weird parts of JavaScript that you may not be familiar with. One is the with statement. With the with statement, first you have an object, it has some properties on it, and you have the with keyword, and then you put the object as the target of the with keyword. Then you have this block. Inside the block, all the properties on the object can be accessed as if they were variables in scope. Well, there it is. It's not particularly interesting, but that's what the with statement does. The next piece is the proxy. If you're familiar with getters and setters properties, it's like that for the whole object and any operation on that object. You get various handlers to intercept what those things do. Now by combining these two things with the with statement and then a proxy in here, we control all the scope access inside this block. So you have your untrusted code here. It's trying to get network access with fetch. It'll look it up here. It'll go to the proxy. You'll say, do you have fetch and your proxy can throw a reference error, return undefined, whatever you want, but it can block that lookup. Another way to write this, which is easier to audit, it may be easier to think about, is with two statements. One here, the inner one, you put the untrusted code here. It's trying to look up fetch. It'll look up inside this object first, and that's the object we gave to the compartment when we said you should have access to these things and these things only. If it's in there, it'll just get it. It'll get whatever value you gave it. If it's not in there, it'll go up to the scope terminator, and that thing just always says no, you don't get it. There's a couple more things you need to make it safe, but you don't really need to think about the implementation. You can just use this API. Okay, so SES gives us lockdown and compartment. And I compose these in LavaMode. LavaMode wraps packages inside of compartments and only gives them what they need to run. So that little string formatting package we had before, it won't have access to network, and it won't have access to disk and other powerful features. How you use it, sorry, it's a little small. It's kind of like this. Previously, you might have run node index for your little server. You replace that with LavaMode index, but first you put this right auto policy API and that will generate a policy file of what the packages are allowed to use. And then you just replace node with LavaMode and run normally, and it will at runtime enforce those packages only get what they need. The policy looks something like this. This is a policy for one package. You'll have a bunch of them in your policy file. This is all automatically generated and it works 99% of the time without any additional changes. Here in the built-ins, these are built-in packages provided from node. We're giving them just exactly what they need. For example, we're giving it just read access from the file system and not write access or anything else. Also giving some global variable access and they're only allowed to import these packages. So that's just an example. But in order to help you review your policy, we have this visualization dashboard. So there's the list of your dependencies on the left and a graph of your dependencies on the right. The purple node is our application here, our example application. And then all of these, this graph coming out of it are all the dependencies and the transitive dependencies. And you can dig in and take a look at what these packages are and what their policy is. And the point of this is to help you prioritize the auditing of your dependencies and review your policy. Because remember, without LavaMode, this is what your app looks like. Every single package is maximally dangerous. Whereas with LavaMode, we know that some of these are safe. All these green ones, for example, are not importing any globals or any built-ins. They're only composing other packages. This was a static version in case the internet didn't work. Okay, so here's the build pipeline again. Install time, build time, and then run time on your user's machine. So we have in LavaMode tools for each of these steps. For depths install, we have LavaMode allow scripts. This will prevent all these dependencies from being able to run a random command on your computer when you install them. And then you can opt into just the ones you want. At build time, you want to run your build process in LavaMode node and you want to add to your build process a plugin for your bundler. So we have one for Browserify, we're working on one for Webpack, for React Native, and SWC. So this is in production in MetaMask. We're protecting tens of millions of users now. So we know this can scale and you can use it too. But we do need your help. It's open source, it's free, it's out there. Find it on GitHub, try it out, let us know if it works. If you're running to a bug, open an issue. If we don't have a plugin for your bundler, let us know. And maybe you can help us with that. Okay, thank you very much. This has been LavaMode. Thank you. Does anyone have any questions? So I actually worked at BitPay on a different team. Oh, cool. It's overlapping the time of the co-pay hack. And yeah, like you said, it's not necessarily that that company had bad processes. It's sort of that the whole ecosystem doesn't really have a good solution. Since that time, not only at that company did no one really talk about LavaMode. I mean, I brought it up because I was aware of it at the time. But externally, any side projects I've been on, I've really never had anyone propose using it. So do you think there's something not at the technical level, but at the social level that we should be doing to make this a more common part of stacks, either across the board or when we're working on applications that deal with private keys specifically? Yeah, great question. So what processes should we put in place? What tools are out there? There's a lot of projects that are working on preventative measures, like CI flows that warn you if there's known vulnerabilities or some code scanning to see if there's something funny looking like eval. And those are great and fantastic, and you can use them in conjunction here. Like one example is socket.dev. That's a friend's company and they're doing great scanning work, but it's all preventative at the beginning. That's not the way the security of your operating system works, right? It's making sure that all your applications are safe. I mean, I was very surprised that no one else was working on runtime protections besides the Algorik folks, which we partner on building LavaMoot. Yeah, I think more people need to just know that there's a solution that exists and you can actually do something about this. Hi. So I had a question about the auto policy generation. Can you explain the logic behind that a little bit? Yeah. We are using static analysis here for convenience and not for the security layer. Static analysis can be faulty. It'll miss things and it's generally imperfect. But here we use it to generate the policy so you don't have to write it all by hand. And then we enforce it with the LavaMoot kernel, which is not relying on static analysis. But yeah, basically what it's doing is you give it your entry point. It's walking through all the requires and imports and walking through your graph. It converts the code to an AST. It looks and analyzes it for references, sees when there's global references, sees when there's imports. And it'll actually follow the imports to where they're used so it can give you just the minimal things that you need, like only using the read power of the file system instead of the write power, for example. I was wondering if you can talk a little bit about the limitations of LavaMoot. Specifically, even if you grant access to a specific function or feature to a package, they can still, I guess, use that feature in a malicious way. Yeah, I was wondering what else we should watch out for even if we use LavaMoot. Yeah, absolutely. So it will look at your modules and generate a policy. And if it sees that network access is used there, it'll put that in the policy. But if it's something that's not supposed to have network access, then we rely on the user, the security engineer, to catch that. Now, it's most likely you don't have a supply chain attack in your app right now, but you might have one in the future. So every time you change your dependencies, you can just run that thing, generate a new policy, and you'll see the diff of your policy, and that's a lot less to review. Some other things, there is a slight performance impact for Compute, but if you're relying on network or disk, which is usually what you're bound when you're computing, then it doesn't make a difference for you. It is very cool how you solve the problem with running insecure code with the width and the proxy. And I was wondering if there is any difference with doing it with a web worker. A web worker? Yeah, you can use iframes and web workers, and you can use on Node.js, you can use the VM package, but what those create are what we call realms. See, I actually have a slide for it. It does work, but it's not very good for backwards compatibility. Yes, when you use... Oh, I'm not up on the screen. We have a lot of jargon when we're talking about this JavaScript language security stuff, but we use this term realm to refer to like a window frame, and so an iframe is another realm. And they're different in a couple of subtle ways. One is inside of the VM or the iframe or the web worker, capital A array is not the same as the capital A array on the outside. We call this problem the identity discontinuity problem, and it breaks things like instance of. So an array from the inside is not an instance of array on the outside. And so this is fine, you can deal with this problem, but it is really bad for backwards compatibility, and that was a primary requirement for LavaMote, because this was not built into men of us from the beginning. I built it afterwards and rewriting the app from scratch was not an option. Currently, there is a proposal in ECMAT EC32, 39, sorry, to introduce compartments natively in JavaScript. I would like to know if you guys are behind that proposal and what do you think about the current state? Yeah, great. So, yeah, we're trying to take some of the things that we built here, like the compartment API, and actually get it standardized into the JavaScript language at the TC39 JavaScript standards body. I'm not up to date on what the status is, but it seems to be finding its way in via the loader API so that when you load modules, you load them in a special compartment. Thanks for your talk. I would like to ask if you can share some supply chain attacks that might have actually been detected at MetaMask since you started using it? Great question. We have not detected any supply chain attacks. The day-to-day battles at MetaMask are primarily phishing, but the problem with the supply chain attack is even if it's more rare, less common, it could take down the whole wallet, and that's why this protective system is so important. I think we have time for another question. Yeah. My name is Ruheli Morel from Terion Swarm. My question for you is why not use QuickJS with Wasam engines instead of the CEST, Prunagoric? Were you asking about Wasam modules? Yeah, why not use QuickJS with any Wasam engine compatibility? I didn't catch the whole question, but Wasam modules have been designed to be sort of self-contained and not have ambient authority, not have access by default to network and disk. So that is fantastic. I'm so glad they designed those things that way. Blockchain companies use some Wasam engines that have some special add-ons that you can enable networking and file system access. Sorry, I couldn't understand the question. Sorry, so there were some engines in the market that support add-ons that give you a connection to TensorFlow file system and networking? I heard some cool things like TensorFlow in there, but I still can't make out the whole question. Maybe you could ask someone next to you to repeat it for whatever reason I'm not able to hear it. I don't know if I've got a butcher question, but I'm a bit closer. He was asking about inside Wasam or the Wasam connections. Are there any connections to TensorFlow? Or what's the last one? File system or networking? All three. Oh, yes. Okay, so how do Wasam modules connect to the powers they need like disk access and network access? Great question. So much like with the compartments, you have to pass them in and make them accessible on the like foreign function list that you exposed to the Wasam. I'm not a Wasam expert, so I can't answer perfectly, but you do have to, you know, go in and put them there. They don't get it by default. I have a follow-up question. Well, not directly related, but we were hacking on snaps over the weekend and I was just wondering like the interface in which lava mode interacts with SES and that JavaScript engine. Because, no, I mean, just on the high level, I was having a hard time differentiating where lava mode ended and SES started. Yeah, great question. So we have these compartments from SES. SES is giving us primarily lockdown and compartments. There's a few other features I didn't cover today. And then I in lava mode composed those by putting the compartments around the packages. You also mentioned metamask snaps. This is a plug-in system we're building for metamask to extend the functionality and create new user experiences for metamask. But because we're putting outside code inside the wallet, we have to make sure it's safe. And so we're also using SES compartments to limit their powers like limiting network access, et cetera. Thank you so much. Thank you.