Enhancing AWS Lambda Security with Deno

AWS, AWS Lambda, Deno, Security

Deno is an alternative JavaScript runtime that was released back in 2020. I’ve been seeing more interest in it recently, and it has some compelling features:

  • Avoid the need to install dependencies
  • Broad Web-standard APIs support
  • TypeScript out of the box
  • All-in-one tooling
  • Fine-grained permission checks
  • Safer NPM packages
  • High-performance

The feature that drew my attention was the fine-grained permission checks and the idea of safer NPM packages. Software supply chain attacks have become more frequent (2021, 2022, 2023) and are gaining more attention. Within the JavaScript ecosystem, NPM is a significant target. I looked for recent counts of how many public NPM packages exist and came up short. NPM’s homepage claims 17 million developers use it. I’m not surprised by that number at all.

One thing many of the malicious packages and code snippets have in common is the need to download additional dependencies or executables. That’s somewhere Deno can help! Deno doesn’t allow access to environment variables, the operating system, filesystem, subprocesses, or the network unless you permit it. Even better, for some of those, you can allow specific access. For example, you can enable access to your application’s domain but nothing else. If you installed a malicious package, it would have difficulty downloading its extra dependencies or exfiltrating data. The added security alone can make Deno an attractive option and is where I’m focusing for now.

A crypto miner hidden in a malicious package will have a limited impact on a Lambda function; the Lambda is unlikely to execute long enough to provide significant benefits. Data exfiltration and script execution from a remote source are more likely to be an issue. Both require access to the internet and with Deno, you have the potential to block that.

Using Deno with AWS Lambda functions requires a custom runtime. You can build your own runtime or use one that already exists. If you’re concerned about security, I suggest maintaining a copy of an existing runtime and carefully inspecting updates or creating your own runtime. For this proof of concept, I deployed the Serverless Application Repository (SAR) app for Deno into my AWS account. I used the included Lambda layer and the provided.al2 Lambda runtime to create my Deno Lambda function. I created a file called index.ts with some basic JavaScript code that makes requests to two different websites and returns the HTTP status code of the response or a caught error. I then updated the function’s configuration to reference the exported handler function.

export async function handler() {
  const r1 = await makeRequest('https://deno.com');
  const r2 = await makeRequest('https://example.com');
  return { r1, r2 };

async function makeRequest(url: string) {
  try {
    const res = await fetch(url);
    return res.status;
  } catch (error) {
    return error;

Once deployed, I invoked my Lambda function from the console and received two HTTP 200 responses. By default, the Deno Lambda runtime allows all network and environment variable requests due to requirements from the custom runtime. Lambda custom runtimes work by communicating with localhost and receiving initialization information through environment variables. Despite this default, we can scope down Deno’s permissions and restrict access more tightly. The custom runtime I used allows you to specify the permissions passed to the Deno runtime by modifying the DENO_PERMISSIONS environment variable. The default value is --allow-env --allow-net. If the Lambda function did not need any network access for the application, you could use a value like --allow-env --allow-net= to only allow the required Lambda Runtime API communication. For my proof of concept, I set DENO_PERMISSIONS to --allow-env --allow-net=,deno.com, allowing access to deno.com but not example.com. Invoking my Lambda function, I got the results below:

  "r1": 200,
  "r2": {
    "name": "PermissionDenied"

When running Deno locally, you are prompted to allow access, but within the Lambda function, an error will be thrown when unpermitted access is attempted. If a malicious package were executed, you might notice unexpected failures or log messages, but the impact of the code would be limited.

Using this approach would require additional effort from developers to ensure they account for all the permissions needed. That may be a trade-off worth making if you have concerns about malicious packages and supply chain attacks. So far, I have only used this for a proof of concept, nothing in production. Using Deno like this doesn’t replace code and dependency scanning tools. Still, it might provide an early warning system for when something does go wrong.