Threat environment
The threat environment for Node.js is similar to that for other runtimes that are primarily used for microservices and web frontends, but there are some Node.js specific concerns.
We define both kinds of threats in this section. A reader familiar with web-application security can skip all but this page and the discussion of unintended require without missing much, but may find it helpful to refer back to the table below when reading later chapters.
Server vs Client-side JavaScript
Before we discuss the threat environment, it's worth noting that the threat environment for server-side JavaScript is quite different from that for client-side JavaScript. For example,
- Client-side JavaScript runs in the context of the
same-origin policy possibly with a
Content-Security-Policy which governs which code can load.
Server-side JavaScript code loading is typically only
constrained by the files on the server, and the values that can
reach
require(...)
,eval(...)
and similar operators. - Client-side JavaScript typically only has access to data that the human using the browser should have access to. On the server, applications are responsible for data compartmentalization, and server-side JavaScript often has privileged access to storage systems and other backends.
- File-system access by the client typically either requires human
interaction
(
<input type=file>
,Content-disposition:attachment
), or can only access a directory dedicated to third-party content (browser cache, local storage) and which is not usually on a list like$PATH
. On the server, the Node runtime process's privileges determine file-system access. - Client-side JavaScript has no concept of a shell that converts strings into commands that runs outside the JavaScript engine. Server-side JavaScript can spawn child processes that operate on data received over the network, and on data that is accessible to the Node runtime process.
- Network messages sent by server-side JavaScript originate inside the server's LAN, but those sent by client-side JavaScript typically do not.
- Shared memory concurrency in client-side JavaScript happens via
well-understood APIs like
SharedArrayBuffer
. Experimental modules (code) and a workers proposal allow server-side JavaScript to fork threads; it is unclear how widespread these are in production or how susceptible these are to memory corruption or exploitable race conditions. - Client-side, the browser halts all scripts in a document when a single event loop cycle runs too long. Node.js has few ways to manage runaway computations on the server.
The threat environment for server-side JavaScript is much closer to that for any other server-side framework than JavaScript in the browser.
Classes of Threats
The table below lists broad classes of vulnerabilities, and for each, a short identifier by which we refer to the class later in this document. This list is not meant to be comprehensive, but we expect that a thorough security assessment would touch on most of these and would have low confidence in an assessment that skips many.
The frequency and severity of vulnerabilities are guesstimates since we have little hard data on the frequency of these in Node.js applications, so have extrapolated from similar systems. For example, see discussion about frequency in buffer overflow.
For each, relevant mitigation strategies appear in the mitigations columns, and link to the discussion.
Shorthand | Description | Frequency | Severity | Mitigations |
---|---|---|---|---|
0DY | Zero-day. Attackers exploit a vulnerability before a fix is available. | Low-Med | Med-High | cdeps fail |
BOF | Buffer overflow. | Low | High | ovrsi |
CRY | Misuse of crypto leads to poor access-control decisions or data leaks. | Medium | Medium | ovrsi |
DEX | Poor developer experience slows or prevents release of features. | ? | ? | dynam ovrsi |
DOS | Denial of service | Medium | Low-Med | TBD |
EXF | Exfiltration of data, e.g. by exploiting reflection to serialize more than intended. | Med-High | Low-Med | ovrsi |
LQC | Using low quality dependencies leads to exploit | Medium | Low-Med | kdeps ovrsi |
MTP | Theft of commit rights or MITM causes npm install to fetch malicious code. |
Low | Med-High | kdeps cdeps |
QUI | Query injection on a production machine. | Medium | Med-High | ovrsi qlang |
RCE | Remote code execution, e.g. via eval |
Med-High | High | dynam ovrsi |
SHP | Shell injection on a production machine. | Low | High | ovrsi cproc |
UIR | require(untrustworthyInput) loads code not intended for production. |
Low | Low-High | dynam |
Meltdown and Spectre
As of this writing, the security community is trying to digest the implications of Meltdown and Spectre. The Node.js blog addresses them from a Node.js perspective, so we do not comment in depth.
It is worth noting though that those vulnerabilities lead to breaches of confidentiality. While confidentiality violations are serious, the suggestions that follow use design principles that prevent a violation of confidentiality from causing a violation of integrity. Specifically:
- Knowing a whitelist of production source hashes does not allow an attacker to cause a non-production source to load.
- Our runtime
eval
mitigation relies on JavaScript reference equality, not knowledge of a secret.