OReilly Serverless Security
OReilly Serverless Security
m
pl
im
en
ts
of
Serverless
Security
Protect Functions Using the
CLAD Security Model
Guy Podjarny
& Liran Tal
REPORT
Securing Open
Source Libraries
with Snyk
Join more than 300,000 developers
using Snyk to automatically find and fix
vulnerabilities in open source code
packages.
Customers protected by
Serverless Security
Protect Functions Using the
CLAD Security Model
The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Serverless Secu‐
rity, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc.
The views expressed in this work are those of the authors, and do not represent the
publisher’s views. While the publisher and the authors have used good faith efforts
to ensure that the information and instructions contained in this work are accurate,
the publisher and the authors disclaim all responsibility for errors or omissions,
including without limitation responsibility for damages resulting from the use of or
reliance on this work. Use of the information and instructions contained in this
work is at your own risk. If any code samples or other technology this work contains
or describes is subject to open source licenses or the intellectual property rights of
others, it is your responsibility to ensure that your use thereof complies with such
licenses and/or rights.
This work is part of a collaboration between O’Reilly and Snyk. See our statement of
editorial independence.
978-1-492-08250-7
[LSI]
Table of Contents
5. Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
v
CHAPTER 1
Introduction to Serverless and
Cloud Native
1
world of modern infrastructure has changed the nature of this
underlying server, constantly striving for better resource utilization,
lower management effort and greater agility.
Serverless is a controversial term. There’s ample debate about what
serverless is, given that there are still, at the end of the day, servers
running your code. We won’t explore this existential question in this
book, but rather give one description of the journey to serverless,
which should help clarify the security perspective in it.
Containers
Enter containers. VMs are big and heavy, both in file size and
startup times. In addition, multiple VMs running on the same phys‐
ical machine share nothing, for instance loading the operating sys‐
tem (OS) into memory separately—even if it’s the exact same OS.
Containers (e.g., Docker) are a lighter-weight form of virtualization,
enabling resources already loaded into memory to be shared, which
results in far better resource utilization.
Container Orchestration
So now we have these super-agile containers that you can slice and
dice in many ways, efficiently move them around, and execute
quickly and efficiently: hurray! Unfortunately, this flexibility intro‐
duces many moving parts, which need to be orchestrated—resulting
in the next step of the evolution: container orchestration platforms
(e.g., Kubernetes). These platforms help manage which containers
go where, oversee security access permissions, launch systems, shut
down systems, and many more tasks.
The most dominant container orchestration platform today is
Kubernetes. Even though Kubernetes is meant to help manage many
moving parts, running and operating Kubernetes itself is quite com‐
plicated in its own right and requires substantial expertise and time.
This led to the creation of multiple “managed Kubernetes” plat‐
forms, which take this effort off your hands. The Big Three cloud
platforms—Amazon Web Services (AWS), Microsoft Azure, and
Serverless
And so, finally, we get to serverless!
At this point, we’ve traveled quite far from the physical machine that
we used to run our applications on, but we’re still very much reliant
on its CPU, memory, and other components. Even when orchestrat‐
ing containers, we need to point to the (cloud) VMs they are on to
run, and specify the hardware requirements for those. Amidst the
many Kubernetes settings, we need to specify when to launch a new
VM to grow our capacity. If we don’t manage it well, we either won’t
scale to meet demand properly, or we’ll find ourselves paying for
resources we’re not really using. Not great!
But it would be great if you didn’t need to manage that server
capacity, right? If the managed cloud platform were to simply spin
up a machine when required and then shut it down when it was no
longer needed? If the OS was managed and patched by the cloud
platform, therefore removing that burden from the Dev teams?
That, in a nutshell, is the serverless promise. While clearly there are
still servers running code, serverless really means server-
management-less, pushing the effort of managing capacity and
servers to the platform, as shown in Figure 1-1.
Serverless is primarily implemented through Function-as-a-Service
(FaaS) platforms. Developers write small chunks of code, define
when and how the function should be invoked (e.g., in response to
an event or an HTTP trigger), wrap them as a function and push it
onto the platform. When the triggering event occurs, the platform
spins up a machine, deploys the function on it and executes it.
Application owners pay purely for the resources used at a very gran‐
ular level (per 100 ms of execution, for example), and don’t need to
care whether the platform keeps servers up or takes them down (as
long as request latency requirements are met).
7
than not), and a new version of the component is published. The
consumer of this component then needs to find out the vulnerability
was disclosed and then download and install the updated versions to
keep their machine secure.
Updating dependencies on a machine is a relatively easy, albeit
annoying, task (how often have you deferred your automatic
updates to “install later”?). It might break something but usually
doesn’t, and it might require a restart but not much more. However,
updating dependencies at scale—patching thousands of machines in
a continuous manner—is extremely difficult.
Subsequently, most companies fail at patching their servers well, and
exploiting unpatched servers has become the top reason for success‐
ful breaches. In its 2019 predictions, security firm TrendMicro
remarks that 99.99% of exploit-based attacks will not be zero-day
vulnerabilities, stating, “The most accessible opportunity for cyber‐
criminals is the window of exposure that opens up between the
release of a new patch and when it is implemented on enterprise sys‐
tems. In 2019, successful exploit-based attacks will involve vulnera‐
bilities for which patches have been available for weeks or even
months but have not been applied yet.”
When using serverless, patching servers is no longer your responsibil‐
ity (as application owner). The Function-as-a-Service (FaaS) plat‐
form is in charge of provisioning and launching a server for you on
demand, and it’s responsible for keeping its OS dependencies
patched. It is still your responsibility to use a platform that does this
well, but most cloud platforms do.
We saw a good example of this when the Meltdown vulnerability was
disclosed, describing how data can be stolen by exploiting a weak‐
ness in how the OS interacts with the underlying processor. Amazon
Web Services (AWS) quickly put some mitigating controls in place
and published a new patched OS. Customers were subsequently
advised to patch their instance operating systems, causing lots of
shuffling amidst users, whereas serverless AWS Lambda (and Far‐
gate) users received a simple “no customer action is required”
message.
Note that although FaaS patches vulnerable OS dependencies, it
does not patch vulnerable application dependencies. We look at that
in more detail in “Library Vulnerabilities” on page 23.
13
Access and permissions
Define resource permissions that the function needs to execute
and access in order to work properly.
Data security
Your function might need to access data persistency resources,
or transactions, which means that you need to ensure data secu‐
rity, as well.
Let’s further review how each of the CLAD model categories
unfolds.
Code Vulnerabilities
Whereas the Function-as-a-Service (FaaS) platform assumes the
ownership of server security concerns, network security and an
overall secure infrastructure, the execution runtime is still the
responsibility of the app developers who wrote its code. In other
words, the serverless paradigm further strengthens the DevOps cul‐
ture and the transformation of cloud ownership; at the same time, it
empowers developers to focus solely on writing their application
code.
However, it is the direct consequence of this same paradigm that
“enables” app developers to write and deploy applications with a
much smaller knowledge and skill set than ever before. They no
longer need to know anything about the server infrastructure: its
operating system (OS), OS dependencies, its configuration, how
those configurations might affect or serve their applications. All they
need to do is write discrete functions and deploy them.
Therefore, as the entire paradigm shifts responsibilities, so do
attackers shift their focus—they know they don’t necessarily need to
seek gateways through the infrastructure anymore. Ultimately, this
means that they know to seek and attempt access through the weak‐
est link. For that reason, the function’s own code effectively becomes
the first line of defense. This is because developers often don’t
understand infrastructure and how their apps interact with that
infrastructure. Even when functions are designed to execute only
small units of code (reducing the attack surface), developers increase
the function’s attack surface by deploying insecure coding conven‐
tions when they don’t understand the infrastructure that supports
their application. This means that traditional application vulnerabil‐
• A1:2017: Injection
• A2: 2017: Broken Authentication
Code Vulnerabilities | 15
• A3:2017: Sensitive Data Exposure
• A4: 2017: XML External Entities (XXE)
• A5: 2017: Broken Access Control
• A6: 2017: Security Misconfiguration
• A7: 2017: Cross-Site Scripting (XSS)
• A8: 2017: Insecure Deserialization
• A9: 2017: Using Components with Known Vulnerabilities
• A10: 2017: Insufficient Logging and Monitoring
You can find more information at the official OWASP wiki and
project page. Another resource to consult with is OWASP’s Server‐
less Top 10 security reference.
Injection Flaws
Historically, web security risks resulting from injection security vul‐
nerabilities (or, flaws) have been categorized as one of the OWASP’s
Top 10, and it made it to the top of that short list for both the 2013
and 2017 editions.
Injection flaws are those whereby data isn’t filtered or encoded with
the correct context and will therefore be interpreted as part of a
trusted execution context, leading to unintended results.
User input can be insecurely handled, leading to injection flaws in
SQL injections, system command executions, CSS, and JavaScript
code execution. It used to be easy to identify the point at which users
could enter input, out of a limited number of possibilities; today,
however, it no longer always clear where users can enter input
because there are many possibilities for user input with functions,
and often times the source of that input is vague. Figure 3-1 presents
examples of event sources to a function.
Code Vulnerabilities | 17
Following are real-life examples of such mappings:
Now, consider the following code example taken from one of the
serverless functions found in OWASP’s DVSA project to help edu‐
cate about serverless vulnerabilities:
1 def lambda_handler(event, context):
2 # Helper class to convert a DynamoDB item to JSON.
3 class DecimalEncoder(json.JSONEncoder):
4 def default(self, o):
5 if isinstance(o, decimal.Decimal):
6 if o % 1 > 0:
7 return float(o)
8 else:
9 return int(o)
10 return super(DecimalEncoder, self).default(o)
11
12 bucket = event['Records'][0]['s3']['bucket']['name']
13 key = event['Records'][0]['s3']['object']['key']
14 key = urllib.unquote_plus(urllib.unquote(key))
15 order = key.split("/")[3]
16 orderId = order.split("_")[0]
17 userId = order.split("_")[1].replace(".raw", "")
18
19 s3 = boto3.client('s3')
20 # download file to /tmp
21 download_path = '/tmp/' + order.replace(".raw", ".txt")
22
23 # print download_path
24 s3.download_file(bucket, key, download_path)
25 date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
26
27 os.system('echo -e "\t\n\t\tDate: {}" >> {}'.format(date,
28 download_path))
Code Vulnerabilities | 19
As an example, when running database queries always use safe-
bind parameter APIs from the database driver so that when the
query is parsed, the engine understands correctly which data is
trusted and which must be escaped due to the dangerous poten‐
tial as interpreted by the query.
Similarly, if your function code needs to execute an OS process
to perform a task, it shouldn’t assume anything about dynamic
data, such as the filename, being used. You should ensure that
the data you provide to the spawned process is correctly escaped
in its shell context to avoid cases of command injection
vulnerabilities.
Code Vulnerabilities | 21
An ideal situation is where each function is its own perimeter and
performs all required security aspects for it to function properly
without relying on order of execution.
Library Vulnerabilities
As we previously discussed, serverless takes vulnerable server
dependency patching off your hands, and that’s a huge advantage.
Unfortunately, an extremely similar problem remains under your
responsibility: vulnerable application dependencies fetched from
npm, Maven, PyPI and similar sources. These are bundled into your
function, are not maintained by the platform, and can grow stale and
vulnerable just like their server dependency peers. In fact, a recent
State of Open Source Security report by Snyk for 2019 reveals that
known application library vulnerabilities have increased 88% across
two years—almost doubling.
Library Vulnerabilities | 23
MITRE and NVD are most commonly associated with publicly
known security vulnerabilities because they both run as nonprofit
organizations that catalog and provide information about CVEs
which describes a security vulnerability along with its associated
severity scores and other details.
However, not enough CVEs for known security vulnerabilities in
open source packages are tracked by NVD. One reason for this is
that security vulnerabilities are reported by developers, sometimes
even categorized as bugs instead of as security related in order to get
the attention they require. As these issues are reported to the
projects that manage them directly, they often miss going through
the formal and official process of requesting a CVE, leaving NVD
completely unaware of such security vulnerabilities.
As an example for NVD lacking substantial information, based on
Snyk’s database, only 67% of RubyGem vulnerabilities have been
assigned a CVE, and only 27% of npm vulnerabilities. Tools such as
the OWASP Dependency Check that rely exclusively on public data
sources such as NVD completely miss many security vulnerabilities.
This means that there is great need for, and value in, vulnerability
databases that often span and surpass more vulnerabilities than are
found in open and public CVE databases such as NVD.
As the OWASP Top 10 Serverless project points out, components
with known vulnerabilities is a primary concern in serverless plat‐
forms and is a security concern that software composition analysis
(SCA) solutions help mitigate, among many other values, such as
tracking the usage of third-party components, their licenses, and
more.
The value of good SCA solutions manifests in access to good data‐
bases that are able to uncover and report substantially more infor‐
mation about known vulnerabilities than would be accessible
through public databases, doing so earlier than said public
databases.
Library Vulnerabilities | 25
wrote, two third-party libraries are implemented: aws-sdk and
node-fetch, which also bring more than a dozen indirect depen‐
dencies, accounting for almost 200,000 lines of code for this func‐
tion alone when it is executed and deployed.
Library Vulnerabilities | 27
Tracking security vulnerabilities for third-party open source vulner‐
abilities is just one part of the puzzle.
With many functions deployed, SCA should also provide an easy
way to centralize information about all the dependencies being used,
including their versions and licenses.
Library Vulnerabilities | 29
Figure 3-4. Continuous monitoring for existing and newly discovered
security vulnerabilities in deployed functions
And, just like lego bricks, our functions need to be all connected to
form business flows.
Functions create many moving parts for us to manage, for instance
per-function permissions, and mapping fine-grained resource
Least-Privilege Principle
Functions are meant to be small, allowing us to reduce their permis‐
sions set to a minimal scope so that they access only what is individ‐
ually required by each individual function in order to execute their
requirements. This results in dramatically reducing the damage a
successful attack can cause and reducing the surface of exposure for
the overall integration of several functions.
Isolate Functions
Although multiple functions can be deployed to create a complete
use case together, you should treat each as its own function perime‐
ter to ensure that a compromise in one function doesn’t escalate to a
compromise in others, as well. This concept is a part of a broader
Defensive Programming methodology that’s important for serverless
applications because each function can be invoked directly by a user
or by an attacker and can each be provided a multitude of unexpec‐
ted inputs.
Functions should also operate in isolated contexts, meaning the
environments to which they are deployed. This ensures that a func‐
tion’s compromise in one environment is contained to that environ‐
ment, and its impact is isolated to that environment’s scope.
The effect of this is akin to having bulkheads on a ship, in which the
craft can suffer a hull breach and yet it can stay afloat. Its engines
and controls continue to run, thanks to its self-contained engine
room and watertight compartments, whereas even a small leak could
have sunk a vessel of less robust design.
Generally, the cloud vendor is responsible for customer isolation;
however, some vendors also enable you to create subaccounts for
further internal customer isolation. You should follow cloud provid‐
ers best practices in terms of account-level isolation to restrict access
between accounts and isolate the resource access and scope that a
deployed function has.
Data Security
Data security is even more important when coding functions than
was for traditional server-based architecture due to the stateless
constraints with which functions work. The serverless platform is
provided by the cloud vendor and so we shouldn’t assume any
details about how the functions execute, and their underlying
mechanics with regard to resource allocation. For this reason, we
should treat functions as ephemeral. Therefore, in most circumstan‐
ces, saving any sort of state in a function’s own runtime is an antipat‐
tern that you shouldn’t follow. Instead, you should always store data
in external resources.
Data Security | 37
original binary, an attacker sends back a modified version of it to
install a backdoor, or steal information from the host making the
request:
function buildDownloadUrl(version, minorVersion) {
return 'https://selenium-release.storage.googleapis.com/' +
minorVersion +
'/selenium-server-standalone-' + version + '.jar';
}
provider:
name: aws
goodbyeWorld:
handler: goodbye.get
First, let’s review the positive aspects of this structure. The hello
World function makes good use of the principle of isolating func‐
tions. The GITHUB_API_KEY environment variable is available only to
the function being invoked, and not to any other function in the
code, in this case—goodbyeWorld.
On the other hand, the secret token for GITHUB_API_KEY (ABCDEF in
the previous example) is hardcoded in the serverless.yml file, which
mixes issues surrounding configuration and secrets together, leading
to potential information exposure if this configuration file is pub‐
lished publicly or shared with other users.
The following example uses the AWS Systems Manager (SSM),
designed to fetch secrets that were securely encrypted by the AWS
Parameter Store. In our example, the SSM variable is used to access
a specific value that was created beforehand, making it available for
a function through an environment variable:
service:
name: hello-world
provider:
name: aws
functions:
helloWorld:
handler: helloworld.get
environment:
GITHUB_API_KEY: ${ssm:/github/api-key}
For additionally improved security and flexibility in managing
secrets for your applications, consider accessing the secrets and
other configuration information in runtime instead of using envi‐
ronment variables that require a process restart in order to reapply
the new configuration.
Data Security | 39
Rotate Keys and Credentials Regularly
Although using storage for your secrets reduces the chance of a key
leaking, it doesn’t completely eliminate it. Fortunately, if you’re using
secrets storage in your function, this means nobody cares what the
key actually is, and so you can rotate the key regularly! This way, if
the key leaks or is stolen, it can be used for only a short period of
time before it simply expires.
Rotating keys is an easy task when you use a key management ser‐
vice (KMS) and you can execute it routinely. However, it can some‐
times prove a bit trickier when integrating with other systems.
When the key is necessary for accessing a third-party system,
explore the API available from that third-party for a way to rotate
the key automatically. If one exists, create a routine job that creates a
new key, updates the token in the KMS, and expires or blacklists the
old key in the next iteration. In this way, at any given time, only two
keys are active. Because your functions should be short-lived, they
pick the new key quickly, allowing you to expire the old key and
start another key rotation often—daily or even hourly.
• Always use a secure transport layer such as HTTPS with TLS 1.2
or 1.3 encryption to keep in-transit data safe from Man-in-the-
Middle attacks.
• Use a secrets store for all secrets and plan a key rotation strategy
for both keys you create and those for third-party systems.
• Avoid logging data that could lead to leaked secrets, or sensitive
information exposure.
• Avoid storing secrets in insecure mediums such as plain-text
files committed to the source code repository or passed as envi‐
ronment variables.
• As a countermeasure to leaking secrets to source control, aim to
adopt a tool such as Yelp’s detect-secrets, or AWS’s git-
Data Security | 41
secrets to prevent a developer from committing secrets to the
repository.
Project Setup
This serverless application is meant to be deployed as an Azure
Functions project, and its source is available here.
We invite you to follow the sample project repository for updates
and support for other cloud providers as we add those over the
course of time, and we welcome any contributions from the com‐
munity to further build on the basis of the sample application as
presented here in this book.
If you want to try running the application locally or deploying to
Azure, you’ll need the following:
43
• Node.js and npm to run the application locally.
• An Azure account and Azure CLI tools.
For Git and the Node.js stack, refer to their respective websites for
the best installation method suited to your requirements.
Azure command line tools enable deployment and local testing.
Specifically, we use v2 of the Azure CLI and a globally installed npm
module to invoke the serverless functions toolkit.
{@if cond="'{device}'=='Desktop'"}
<h1 id="page-title">{title}</h1>
{#todos}
<div class="item">
<a class="update-link"
href="#{id}"
title="Get this todo item">
{text}
</a>
</div>
{/todos}
</body>
</html>
This template includes logic, according to which if we provided a
device query parameter with the value of Desktop it would use a
smaller font-size, and would default to a larger one when provided
any other value.
What if we had tried to tamper with the input and instead of provid‐
ing the string Desktop we added characters that are used in an
HTML context to escape elements? Consider the following URL
address, /api/rendertodos?device=Desktop', in which we added a
single quote suffix. The rationale of using a single quote is that sin‐
gle and double quotes are usually used in language constructs as
wrappers for strings. If we know the code isn’t properly escaping,
we’d be able to use the quotes to create a code injection in the con‐
texts where they are used.
This attempt, however, proved futile; we can take a look at the
dustjs library source code at dust.js to reveal the reason why:
dust.escapeHtml = function (s) {
if (typeof s === 'string') {
if (!HCHARS.test(s)) {
return s;
}
return s.replace(AMP, '&')
.replace(LT, '<')
.replace(GT, '>')
.replace(QUOT, '"')
.replace(SQUOT, ''');
}
The built-in dustjs helpers provide the @if directive to allow con‐
ditional logic that we use in our view. If we take a further look at the
dustjs source code at dust-helpers.js, we can see how it works:
cond = dust.helpers.tap(params.cond, chunk, context);
// eval expressions with given dust references
if(eval(cond)){
if(body) {
return chunk.render( bodies.block, context );
}
else {
_log("Missing body block in the if helper!");
return chunk;
}
}
As you can see, the conditional statement is dynamically evaluated
and opens the possibility for a severe code injection.
To exploit this vulnerability in our render function, we can send the
following request to the serverless application endpoint: /api/
rendertodos?device[]=Desktop’-console.log(1)-'. The payload
in the request makes more sense now that we’ve uncovered why the
vulnerability happens.
11 directories, 6 files
Why does our serverless application deploy user-facing functions as
well as administration capabilities in the same project (for example,
the one named ZZAdminAPI)?
When we deploy functions in bulk, such as in this example, in which
an administration interface is delivered along with our business
logic, we fail to follow best practices such as deploying functions in
granularity, as we covered in previous chapters. This means that we
often end up with functions that have a broader permissions scope
than they should have, due to global roles and resource access that
applies to the entire project with which they’re associated, and not
just specific functions.
Unfortunately, serverless projects often follow this antipattern of
deploying functions in bulk because it is more convenient and
makes it easier for developers to manage and control as they are
working on a project.
Let’s assume that we have taken extra measures to define per-
function permissions and that we have made sure that our
administration-related functions did not use any third-party libra‐
ries (if that is even possible) and that our code is written with the
best security practices in mind.
Now, let’s build on the anonymous-access remote code injection vul‐
nerability that we discussed in the previous section and see how it
can be exploited to affect our admin-related functions.
What if the request payload to exploit the dustjs library was modi‐
fied to list all files? Such a payload would look like this:
context.res = {
status: 401,
body: {
error: JSON.stringify(error.message),
trace: JSON.stringify(error.stack)
}
};
return context.done();
}
}
Even though this is a partial code snippet, it already points out bad
practice in our admin-related function code where a shared secret
has been committed to source control and is hardcoded in our
admin function code that is now available for anyone who is able to
exploit our to-do list application in the way that we demonstrated.
What if we stored the shared secret in a separate file? What if we had
stored secrets in a deployment configuration file such as a server‐
less.yml? The answer for that would lie in the way that our serverless
project is deployed. If the tooling also deploys the manifest files, our
shared secret will be available, as well. This is where the importance
of using key management services comes in and help to mitigate the
issue of floating secrets in a code base, or a deployment package.
Separation of concerns is a pattern that we should follow in code as
well as in the way that we deploy our serverless projects. Each
project should aim to be deployed in minimal granularity as possible
to isolate and reduce to a minimum any security impact as we’ve just
demonstrated.
// Invoke it!
context.log("invoking " + remoteFunctionURL);
res.on("end", () => {
context.log(`successfully invoked azure function:
${action}`);
context.res = {
status: 200,
body: "API call complete"
};
return context.done();
});
});
• Code vulnerabilities
• Libraries vulnerabilities
• Access and permissions
• Data security and privacy
55
the application owner, such as managing the actual underlying
servers, it still requires a fair bit of configuration—this can often
lead to misconfigured access and permissions that results in flawed
security controls and further risks serverless applications. Finally,
data security and privacy concerns are important for functions due
to their extremely stateless circumstances and require the applica‐
tion owner to ensure sensitive data is treated in a secure manner.
To conclude, each of these four security categories is highly relevant
for serverless applications because they easily become a target for
attackers to focus on, and as the application owner, you should
ensure you are building a plan to address security concerns in each
category.
56 | Chapter 5: Summary
About the Authors
Guy Podjarny (@guypod) is Snyk’s cofounder and CEO, focusing on
using open source and staying secure. Guy was previously CTO at
Akamai following their acquisition of his startup, Blaze.io, and
worked on the first web app firewall and security code analyzer. Guy
is a frequent conference speaker and the author of O’Reilly’s Secur‐
ing Open Source Libraries, Responsive & Fast, and High Performance
Images.
Liran Tal is a Developer Advocate at Snyk and a member of the
Node.js Security working group. He is a JSHeroes ambassador, pas‐
sionate about building communities and the open source move‐
ment, and greatly enjoys pizza, wine, web technologies, and CLIs.
Liran is also the author of Essential Node.js Security, a core contribu‐
tor to the OWASP NodeGoat project, and loves to dabble about
code, testing, and software philosophy.