Towards Fine-grained, Automated Application Compartmentalization

N. Vasilakis, B. Karel, N. Dautenhahn, N. Roessler, A. DeHon, J. M. Smith

The University of Pennsylvania

The rise of language-specific, third-party packages simplifies application development. However, relying on untrusted code poses a threat to security and reliability.

In this work, we propose exploiting module boundaries – and the general trend towards many, small modules – to achieve fine-grained compartmentalization. Automated transformations can hide compartment boundaries and minimize developer effort. Optional policy expressions can decouple security assumptions at development time from requirements during composition and runtime. Using JavaScript’s flourishing ecosystem, we discuss a wide range of risks and sketch how the use of language-level solutions coupled with systemic mechanisms can protect against them.

Copyright Notice

© 2017 Copyright held by the owner/author(s). Publication rights licensed to Association for Computing Machinery. ACM ISBN 978-1-4503-5153-9/17/10. https://doi.org/10.1145/3144555.3144563 (Published at PLOS’17, October 28, 2017, Shanghai, China)

.

Introduction

Composing software has changed significantly in scale, process, and basis for trust. Software such as the Linux kernel had many people focused on the quality and security of a single, large codebase [33]; yet this cohesive effort failed to prevent a slate of vulnerabilities [6,8].

Today, however, programmers make increasing use of third-party modules from language-specific repositories. These repositories contain tens of thousands of packages (Table 1) from thousands of different authors. As a result, applications can have hundreds of third-party dependencies (Table 4) which execute without meaningful privilege separation or isolation beyond type safety guarantees. Although using code from modules minimizes developer effort (reduced development and maintenance costs), it exposes the system to security risks (Table 2).

Table 1: Five popular programming languages and information about the size and growth of their package repositories (Snapshot: Aug. 1st 2017). “+” symbols indicate more package repositories (not counted here).
GH Repos Package Repo Size Growth
JavaScript 931,493 npm, ++ 530,050 507/day
Java 778,001 Maven Central 194,954 140/day
Ruby 569,180 RubyGems, ++ 134,764 33/day
Python 454,042 PyPi, + 113,602 72/day
PHP 408,210 Packagist, ++ 149,699 137/day

Further problems worsen these risks. Understanding the internals of a complex package and verifying that it will not behave in unintended ways [12,26] are both difficult tasks. The popularity of certain packages allows vulnerabilities deep in the dependency graph to cause widespread difficulties [18,24,45]. Vulnerabilities are hard to eradicate, since (i) some updates are fetched automatically [36], and (ii) module unpublishing has become difficult in order to avoid breaking dependency chains [44]. In the face of such challenges, the latest practice is to avoid packages not endorsed by security advisories [23,41,7].

Instead of merely reacting to announced vulnerabilities or avoiding composition altogether due to security concerns, we propose leveraging the trend towards more and smaller modules to enhance – or retrofit – application security. The core idea is to exploit programming language properties (e.g., abstraction, encapsulation, trust boundaries) to automatically transform a program at the module boundaries while offloading enforcement to the operating system (e.g., address space isolation, LXC/namespaces, scheduling). We propose a drop-in replacement of a language runtime’s module system, BreakApp, that uses module boundaries as guides to draw the lines between compartments.

BreakApp is centered around a parametrizable transformation technique that spawns modules in their own dedicated compartments during runtime. Automated transformations hide compartment boundaries by converting function calls to remote procedure invocations. Optional runtime policy expressions fix aforementioned parameters, effectively decoupling assumptions made during module development from requirements present during module composition. Since a single module can be used by several different types of applications, it is important to let the application developer choose which module behaviors to disallow. Policies also improve BreakApp’s performance, since they allow programmers to customize the provided functionality on a per-import basis.

BreakApp does not require any annotations, does not require any trace (pre-)runs, and does not rewrite any source code. Policy expressions are backwards-compatible with existing codebases and forwards-compatible with vanilla module systems. As a result, the system lowers potential barriers to widespread adoption and makes incremental security retrofit in existing systems possible.

Problems

Consider a widely deployed, open source publishing platform written in JavaScript: Ghost [46]. At its core, Ghost is an HTTP server that serves HTML generated from Markdown files, with the ability to edit and search documents, attach hypermedia, and include comments. Ghost has 62 top-level dependencies; counting recursive imports, the total jumps to 981 packages.

Fig. 1 – (a, b) presents a highly simplified version of Ghost highlighting typical module usage in modern applications. Different boxes correspond to the context of different modules, with the outer box corresponding to the top-level context. All these logically unrelated packages execute within the same address space; a problem in any one of the packages exposes other packages too. But what can go wrong when we are talking about a high level, memory safe, managed programming language?

Figure 1: A simplified server application with multiple third-party modules of varying trust.
Figure 1: A simplified server application with multiple third-party modules of varying trust.

Example 1 As a simple example, suppose that Ghost uses ejs for template generation (Fig. 1 (a), line 2). A malicious version of this module could try to get access to the database credentials by any one of the following ways: (i) attempt to read the global, singleton config object, (ii) import itself the config file from the file sysetm, (iii) access the config module directly through the module cache. In a conventional setup, all three are possible, mainly because it is difficult to distinguish these illegitimate behaviors from legitimate ones: any function can reach into global scope, any module can import built-in modules to read the file system, and any part of the program has direct access to cached modules for performance reasons.

Example 2 As a more interesting case, suppose Ghost provides search functionality (on line 5) using the minimatch module, which will necessarily be supplied user-generated strings. Even if minimatch itself is benign, a malicious user can launch a RegEx DoS attack by sending pathological regular expressions [9]. Given that many implementations follow an event-driven, single-threaded concurrency model, a problematic search query can cause the application to stop accepting requests until the pathological request completes.

Table 2: Eight major vulnerability classes and specific instances of packages available on npm [38], one of the main JavaScript package repositories; “++” indicates that many more packages with similar problems exist.
Problem Example Package
Directory Traversal hostr, bitty, restafary, ++
Denial of Service ejs, node-uuid, minimatch, ++
Remote Code Execution ejs, pouchdb, reduce-calc, ++
Timing Attack fernet, cookie-signature, ++
Uninitialized Mem. Exposure mongoose, bl, request, ws, ++
Command Injection git-ls-remote, shell-quote, ++
Native Code Vulnerabilities libxmljs, libyaml
Sensitive Info. Exposure airbrake

These two examples scratch only the surface of what is possible due to problematic modules. Table 2 summarizes a few vulnerabilities discovered in widely-used JavaScript packages. Such flaws can be attributed to a number of factors: (i) common features of dynamic programming languages (e.g., call stack inspection, reflection capabilities, monkey patching), (ii) language deficiencies (e.g., in JavaScript: default-is-global, prototype poisoning, mutability attacks), (iii) implementation-specific choices (e.g., event-driven implementations, cooperative multitasking, module system cache), (iv) authority considerations where any part of the application can read or write like any other (e.g., read process.env or process.args, write to the filesystem or network), and finally (v) use of modules written in C or C++, which nullifies the safety guarantees1 provided by a high-level programming language.

System Overview

BreakApp is a backwards-compatible, drop-in replacement of the language’s module system. It identifies when to spawn a new compartment (e.g., for each module) and then lets the operating system handle issues such as isolation, communication, and scheduling. It primarily consists of three interrelated tasks (Fig. 1 – (c)): (i) upon import, setup a new compartment along with a communication channel between the two; (ii) transform subsequent function calls to remote procedure calls (RPCs); (iii) periodically monitor compartments for health and status updates. For now, we can think of compartments as processes and communication channels as FIFO pipes or Unix domain sockets, but we will later discuss several different types when talking about policies.

Compartment Setup

Whenever the program executes code such as require("ejs") to import a new module, control jumps to BreakApp. If a module with this name is not already loaded in its own compartment, BreakApp (i) creates a new child compartment that imports only this module, and (ii) sets up a new communication channel between the two. The module system on the child’s side inspects the direct acyclic graph (DAG) object returned by the import, and recursively replaces each node with a wrapper:

  • primitive values are copied unmodified and wrapped with an interposition mechanism that records changes.

  • function values become RPC stubs, which serialize arguments, send them through the channel, and wait for the results.

  • mutable values have their getter and setter functions replaced with RPC stubs.

  • exceptions are re-thrown in the parent context after inspection from BreakApp running on the parent module.

If the specified module is already loaded, BreakApp simply retrieves the channel pointer and returns the previously-wrapped DAG.

The module system mediates between the parent and child compartments. Synchronous calls yield to the module scheduler, which serializes arguments, sends them through the channel to the child, and waits for a response. The child-side wrapper deserializes arguments and calls the required method, sending results back through the channel. For asynchronous function calls, the parent module wrapper registers an event that invokes the provided continuation when results become available on the channel. In cases when something does not go as expected in the child’s execution, its code will throw an exception. BreakApp code running on the child compartment will catch, serialize, and return it to the caller compartment, where the parent-side BreakApp code will deserialize and re-throw it.

Parent compartments naturally monitor the health (i.e., crashed, not responding) of child compartments upon remote invocations, and take curative actions based on the exact status (e.g., restart, kill, or spawn more compartments). This is helpful both in cases where the module within the compartment is launching a DoS attack as well as in cases where asynchronous execution has lead to exceptions (e.g., access global variable etc.). Child compartments can use OS primitives (e.g., SIGHUP on Linux) to be notified upon parent exit.

Overview of policies

Policies allow users to parametrize several aspects of the system’s behavior (Table 3). The goal is to give them the flexibility to selectively disable capabilities the programming language gives modules. For example, if a module is not explicitly allowed to introspect or monkey-patch on core application structures or access global state, these capabilities can be disabled. Since policies express user insight, they can also be used to fine-tune performance characteristics (e.g., number of compartments and their types).

Table 3: Examples of interesting policies.
Policy Explanation
Type Compartment type (e.g., context, process)
IPC Communication type (e.g., FIFO, UDS, TCP)
Context Whitelist pointers to parent context
Instantiate Fresh compartment for each import
Replicate Multiple replicas; schedule round-robin
OnFail Action upon failure (function)
MinTime RPC results available only after min time
Group Group modules in a single compartment
Preload Create proactively instead of lazily
Trust Whitelist allowed modules
Doubt Blacklist disallowed modules
Composition How to combine policies in conflict

Policies can be set at the application-wide level or at the level of individual modules. Below is an example of how users express policies upon import:

  var regex = require("minimatch", 
        { type: require.types.PROCESS });

This specifies that the minimatch module should be loaded in its own, fresh process. OS-enforced isolation via processes provides better and potentially more costly isolation guarantees compared to a V8 runtime context.2 However, these guarantees are probably worse (and certainly less expensive) than the ones provided by a virtual machine running on a different physical host.

Other examples of policies include context, an object mapping bound variables to their values. Variables can point to values in different runtime contexts as a way to share state. Policies such as instantiate and replicate affect how many compartments to create per module. onFail is used to express possible actions when unexpected behavior is detected (e.g., kill and restart compartment). We will see examples of uses in the next section.

Policy expressions are dynamic objects that can be generated during runtime. They can potentially change for each import — even between imports of the same module. This is a powerful feature, as different branches of a control flow statement might load the same module with a different policy. Composition options allow policies to freeze at the top level, trumping any other policy expression found in third-party modules.

Per module policy expressions are fully compatible with existing codebases. They are backwards-compatible with systems that do not provide a BreakApp-enabled module system: due to variadic arguments, the policy argument is ignored by the built-in require function. Not specifying policies (i.e., all of the code out there today) is forwards-compatible with systems that do provide a BreakApp-enabled module system: as alluded to earlier, BreakApp will use the application-wide default configuration.

Discussion

We want to get a sense of the decomposition potential out in the wild, the space of possible security benefits, and the worst-case performance costs associated with applications.

Decomposition Potential

What are the modularity characteristics of JavaScript applications? In particular, is there a potential for compartmentalization? Table 4 describes some of the most popular JavaScript applications by four metrics: (i) only top-level modules, (ii) all modules within the dependency tree, (iii) application code not part of a third-party module, and (iv) total code in all of its third-party imports.

Table 4: Module usage in five categories of applications.
Application Direct Total App Module
Imports Imports Code Code
cash 15 84 15936 142098
command eslint 34 135 231907 171209
yo 30 301 2005 27081
popcorn 46 765 103602 192082
desktop twitter 10 120 2951 419151
atom 57 358 19879 223147
hackernews 5 871 2603 333975
mobile mattermost 17 521 11383 305664
stockmarket 14 44 4473 406650
express 26 42 16906 10369
server ghost 62 981 96979 342676
strider 64 659 32115 99051
chalk 3 4 297 172
utility natural 3 3 19741 5863
winston 6 6 6229 2989
averages 26.13 326.27 37800 178811

Third-party code is a non-trivial portion of today’s applications. In our sample set, imported code is on average 4 times larger than homegrown; the ratio is much worse for large applications (1:120 for hackernews vs. 2:1 chalk). Different applications spread third-party code differently. For example, in mobile applications, more than 99% of their third-party code comes from a single package – the mobile framework in use (e.g., Ionic, ReactNative).

Direct module counts – the boundaries of trust between the code that a developer writes and its third-party dependencies – are somewhere between 2 and 65. These numbers highlight the minimum number of compartments (average: 26.13). More fine-grained compartmentlization at the level of individual packages requires an order of mangitude more compartments (average: 326.27). Since there is an 1-1 correspondence between files and modules, file-level compartmentalization is possible but would require 1-2 orders of magnitude more compartments (e.g., popcorn has more than 10K JavaScript files). Interestingly, analyzing more than 1K imports (translating to more than 100K file-level modules) reveals a 43.09 average ratio of lines of code per file – much smaller than what we expected to see.3

All of the above show that there is a potential for compartmentalization, but also that the flexible granularity that policies offer is crucial.

Mitigation and Policies

Are there any third-party vulnerabilities out in the wild, and if yes, would BreakApp mitigate them? Table 2 (presented earlier) contains a small set of distinct vulnerability classes along with several known instances found in the npm registry [38], caused by only a subset of the possible factors outlined at the end of Section [problems]. Revisiting the two example cases from Section [problems] shows how a BreakApp-enhanced version of the blogging application would mitigate these attacks.

Example 1 Under BreakApp, the ejs module is placed into its own compartment: it does not have access to the application’s global scope, it does not have access to the module cache, and — given a policy of restricting access within a new directory — it does not have access to the rest of the filesystem. If ejs attempts to access something it should not (e.g., a global variable), an exception will be thrown and get caught by BreakApp.

Other configurations are also interesting: by spawning the database config module in its own compartment, we make sure no other module gets access to it beyond the parent module. The startup cost is negligible since there is only one extra compartment started. IPC costs are also amortized over long periods of time in which the database credentials are not needed (i.e., the database config module is in the application’s “control plane”, not its “data plane”).

Example 2 With BreakApp, there are at least two concurrent processes executing – one handling the matching of regular expressions (and communicating only with the main application) and the main application itself receiving requests. This is already a big win: search requests that do not contain regular expressions can still get served; it is only a subset of only the search functionality that remains paralyzed by the DoS attack.

But what happens when a second malicious regular expression arrives? Due to monitoring, BreakApp is at least aware that the previous request is taking more time than expected. By examining the input to the most recent RPC, it concludes that the new one is also problematic. Policies give users several options at this point: (i) shut the child compartment down and report; (ii) restart the compartment; (iii) spawn a new replica and use a scheduling policy (e.g., round robin) to schedule RPC calls to these replicas; (iv) discard or pushback based on history; or (v) by passing a function, implement their own action. From the examples above, as well as our own preliminary experiences, it seems that much will depend on identifying a set of “good-enough” defaults balancing performance and security. These default policies are a primary goal of our future evaluation.

Performance

What is the performance cost expected from using BreakApp? Table 5 highlights compartment startup costs (left) and the costs of boundary crossing (right) under various configurations. Large compartment counts were evaluated to anticipate the trend towards small modules (maximum crossings of 500 when we did not witness more than 50). Experiments were run on a Linux server with 512GB of memory and 160 cores running at 2.27 GHz.

Table 5: Compartmentalization costs. Left: module startup times. Right: throughput (and latency ranges) of boundary crossing.
Compartments InProc FileSys V8sbx Proc Function Pipe UDS TCP
5 0.4ms 4.3ms 12.9ms 342.5ms 192.3GB/s 18.3GB/s 149.5MB/s 158.1MB/s
2.5ms 1.3–1.4ms 17.8 – 73.8ms 17.7 – 36.6ms
50 0.4ms 30.2ms 76.6ms 3.2s 157.1GB/s 17.5GB/s 127.0MB/s 134MB/s
3.18ms 11.6–13.2ms 244.5 – 536.6ms 210.3 – 566.8ms
500 0.5ms 136.4ms 524.7ms 35.2s 46.5GB/s 3.6GB/s 16.4MB/s 20.9MB/s
10.74ms 154.3–160.3ms 3.71 – 11.95s 6.5 – 15.6s
5K 1.0ms 1.7s 7.8s 362.4s

For the first experiment, we minimize the effects of module sizes by making modules return a single integer. Modules are loaded sequentially, since modules later in the program might be parametrized by values in earlier modules. FileSys is how the vanilla module system works: it looks up a module on the filesystem using a resolution algorithm, wraps it so that its global variables do not leak to the outer context (and to provide some global-looking variables, e.g., filename), and evaluates the code in the current context. V8sbx creates a new V8 context for each module and selectively whitelists shared variables from the parent context. Proc uses OS processes to isolate compartments between each other, resulting in higher costs, illustrating one instance of the security-performance trade-off policies attempt to address.

InProc keeps modules in memory and avoids any source code lookup. It shows that the vanilla system already costs users significantly more startup time (0.3ms) than an optimized version (0.2μs), without adding any meaningful security benefits.

For the second experiment, we process an in-memory stream of 0.5GB (/dev/shmem/) using a linear pipeline of mostly-empty stages (they flush some timing metadata when they detect the end of a stream). Streaming starts only after all connections have been established. Loopback TCP streaming has a higher throughput than domain socket streaming, but latencies are mixed. Experiments with fast userpace packet I/O libraries such as netmap [34] (not included here) and shared-memory pipes (included here) show much better latencies. This illustrates another reason why policies are important. Policies related to (i) the choice of compartment type and IPC primitive, or (ii) identification and expression of the critical path are both vital for high-performance applications.

Can we get good defaults?

There is indication that BreakAppcan work in practice with better performance than our conservative worst-case estimates might suggest:

Shallow dependency trees: Although the number of modules (hence, default compartments) is large due to fanout, the maximum number of boundary crossings is only related to the average depth of a tree – which seems much smaller (Applications in Table 3 have an average depth of only 6 levels).

Package deduplication: Some of the logical dependencies are identical between different modules (e.g., lodash, underscore). Although they show as distinct packages in the logical dependency tree, package managers usually deduplicate dependencies effectively flattening parts of the tree. For instance, popcorn went from 765 to 656 modules, resulting in 15% smaller tree.

Small working sets: Not all package subtrees are in the critical path. In our example application, both the database configuration and the regex module have different usage patterns than the bulk of the application. They can still be protected by this technique, without being responsible for any hot-path overheads.

Parallel and lazy loading: Dependency subtrees can take advantage of different loading strategies. Parallel startup is an obvious candidate: spawning 5K process compartments in parallel took 24s. Applications are naturally lazy in the way they load modules: we experimented with saturating disk bandwidth to the point where the 500 modules of the vanilla configuration took 3 seconds to load (instead of 136.4ms shown in Table 5 FileSys). Under such conditions, Ghost with 981 modules still started instantly.

Other heuristics: BreakAppcan take advantage of interposition (by design) on boundary crossings and identify “hot boundaries” as candidates for merge – which can be done automatically after a prompt for security audit.

These insights, combined with performance-oriented policies (e.g., group subtrees together in order reduce the costs, pick isolation guarantees etc.), suggest that a concrete instantiation of BreakAppcould be made practical in existing systems. However, to take full advantage of the increasing trend towards small modules (by, say, isolating even the tiniest modules, re-instantiating on every load, and running multiple replicas), all while maintaining negligible throughput and latency degradation (Table 4) might require new insights in low-overhead OS compartmentalization mechanisms.

Conclusion

This paper identifies and calls the community to harness a surprising potential in the way applications are built today: the use of many third-party modules, although risky, offers clear boundaries of trust. These boundaries can be used to automate parametrizable, compartmentalization-oriented transformations. Without any developer effort, a system can spawn modules into their own compartments and hide their boundaries from developers and users. Optional, flexible runtime policies let composers fine-tune security and performance trade-offs, essentially decoupling assumptions made during module development from requirements during the application runtime. Our vision is that, with the synergy between linguistic and systemic levels, we will succeed as a community in having applications with many, possibly dangerous, third-party packages be safer than their monolithic counterparts.

Acknowledgements

We would like to thank Athur Azevedo de Amorim, Cătălin Hriţcu, Björn Knutsson, Yash Palkhiwala, Benjamin C. Pierce, and John Sonchack for their helpful feedback. This research was funded in part by National Science Foundation grant CNS-1513687. Any opinions, findings, conclusions, or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the National Science Foundation.

References

[1] M. Accetta, R. Baron, W. Bolosky, D. Golub, R. Rashid, A. Tevanian, and M. Young, Mach: A New Kernel Foundation for UNIX Development, in USENIX technical conference, 1986.

[2] P. Agten, S. Van Acker, Y. Brondsema, P. H. Phung, L. Desmet, and F. Piessens, JSand: Complete client-side sandboxing of third-party javascript without browser modifications, in Proceedings of the 28th annual computer security applications conference, 2012, pp. 1–10. (http://doi.acm.org/10.1145/2420950.2420952)

[3] J. Armstrong, Erlang, Communications of the ACM, vol. 53, no. 9, pp. 68–75, 2010.

[4] M. Bauer, Paranoid penguin: AppArmor in ubuntu 9, Linux Journal, vol. 2009, no. 185, p. 9, 2009.

[5] A. Bittau, P. Marchenko, M. Handley, and B. Karp, Wedge: Splitting applications into reduced-privilege compartments, in Proceedings of the 5th usenix symposium on networked systems design and implementation, 2008, pp. 309–322. (http://dl.acm.org/citation.cfm?id=1387589.1387611)

[6] C. Cadar, D. Dunbar, and D. Engler, KLEE: Unassisted and automatic generation of high-coverage tests for complex systems programs, in Proceedings of the 8th usenix conference on operating systems design and implementation, 2008, pp. 209–224. (http://dl.acm.org/citation.cfm?id=1855741.1855756)

[7] M. Cadariu, E. Bouwers, J. Visser, and A. van Deursen, Tracking known security vulnerabilities in proprietary software systems, in Software analysis, evolution and reengineering (saner), 2015 ieee 22nd international conference on, 2015, pp. 516–519.

[8] H. Chen, Y. Mao, X. Wang, D. Zhou, N. Zeldovich, and M. F. Kaashoek, Linux kernel vulnerabilities: State-of-the-art defenses and open problems, in Proceedings of the second asia-pacific workshop on systems, 2011, p. 5.

[9] S. A. Crosby and D. S. Wallach, Denial of service via algorithmic complexity attacks., in Usenix security, 2003, vol. 2.

[10] E. W. Dijkstra, On the role of scientific thought, in Selected writings on computing: A personal perspective, Springer, 1982, pp. 60–66.

[11] M. Eriksen, Your server as a function, in Proceedings of the seventh workshop on programming languages and operating systems, 2013, pp. 5:1–5:7. (http://doi.acm.org/10.1145/2525528.2525538)

[12] C. Fournet, N. Swamy, J. Chen, P.-E. Dagand, P.-Y. Strub, and B. Livshits, Fully abstract compilation to javascript, ACM SIGPLAN Notices, vol. 48, no. 1, pp. 371–384, 2013.

[13] M. Fowler and J. Lewis, Microservices, 2014.. (http://martinfowler.com/articles/microservices.html)[Accessed: 17-Feb-2015]

[14] K. Gudka, R. N. Watson, J. Anderson, D. Chisnall, B. Davis, B. Laurie, I. Marinos, P. G. Neumann, and A. Richardson, Clean application compartmentalization with soaap, in Proceedings of the 22nd acm sigsac conference on computer and communications security, 2015, pp. 1016–1031.

[15] P. Haller and M. Odersky, Scala actors: Unifying thread-based and event-based programming, Theoretical Computer Science, vol. 410, no. 2, pp. 202–220, 2009.

[16] S. Hendrickson, S. Sturdevant, T. Harter, V. Venkataramani, A. C. Arpaci-Dusseau, and R. H. Arpaci-Dusseau, Serverless computation with openlambda, in 8th USENIX workshop on hot topics in cloud computing (hotcloud 16), 2016. (https://www.usenix.org/conference/hotcloud16/workshop-program/presentation/hendrickson)

[17] J. N. Herder, H. Bos, B. Gras, P. Homburg, and A. S. Tanenbaum, MINIX 3: A highly reliable, self-repairing operating system, SIGOPS Oper. Syst. Rev., vol. 40, no. 3, pp. 80–89, Jul. 2006. (http://doi.acm.org/10.1145/1151374.1151391)

[18] T. K. Kuppusamy, S. Torres-Arias, V. Diaz, and J. Cappos, Diplomat: Using delegations to protect community repositories, in 13th usenix symposium on networked systems design and implementation (nsdi 16), 2016, pp. 567–581.

[19] T. Lauinger, A. Chaabane, S. Arshad, W. Robertson, C. Wilson, and E. Kirda, Thou shalt not depend on me: Analysing the use of outdated javascript libraries on the web, 2017.

[20] H. M. Levy, Capability based computer systems. Digital Press, 1984. (http://www.cs.washington.edu/homes/levy/capabook/)

[21] J. Liedtke, K. Elphinstone, S. Schonberg, H. Hartig, G. Heiser, N. Islam, and T. Jaeger, Achieved ipc performance (still the foundation for extensibility), in Operating systems, 1997., the sixth workshop on hot topics in, 1997, pp. 28–31.

[22] J. Litton, A. Vahldiek-Oberwagner, E. Elnikety, D. Garg, B. Bhattacharjee, and P. Druschel, Light-weight contexts: An os abstraction for safety and performance., in OSDI, 2016, pp. 49–64.

[23] S. J. Long, OWASP dependency check, 2015.

[24] M. Maass, A theory and tools for applying sandboxes effectively, PhD thesis, CMU, 2016.

[25] D. Merkel, Docker: Lightweight linux containers for consistent development and deployment, Linux Journal, vol. 2014, no. 239, p. 2, 2014.

[26] J. Mickens, Pivot: Fast, synchronous mashup isolation using generator chains, in 2014 ieee symposium on security and privacy, 2014, pp. 261–275.

[27] M. S. Miller, M. Samuel, B. Laurie, I. Awad, and M. Stay, Safe active content in sanitized javascript, Google, Inc., Tech. Rep, 2008.

[28] S. Newman, Building microservices. O’Reilly Media, Inc., 2015.

[29] “npm, Inc.”, Npm-shrinkwrap: Lock down dependency versions, 2012.. (https://docs.npmjs.com/cli/shrinkwrap)

[30] E. Oftedal and others, RetireJS, 2016.. (http://retirejs.github.io/retire.js/)

[31] N. Peter Loscocco, Integrating flexible support for security policies into the linux operating system, in Proceedings of the freenix track:... usenix annual technical conference, 2001, p. 29.

[32] N. Provos, M. Friedl, and P. Honeyman, Preventing privilege escalation, in Proceedings of the 12th conference on usenix security symposium - volume 12, 2003, pp. 16–16. (http://dl.acm.org/citation.cfm?id=1251353.1251369)

[33] E. Raymond, The cathedral and the bazaar, Knowledge, Technology & Policy, vol. 12, no. 3, pp. 23–49, 1999.

[34] L. Rizzo, Netmap: A novel framework for fast packet i/o, in 21st usenix security symposium (usenix security 12), 2012, pp. 101–112.

[35] J. M. Rushby, Design and verification of secure systems, in Proceedings of the eighth acm symposium on operating systems principles, 1981, pp. 12–21. (http://doi.acm.org/10.1145/800216.806586)

[36] S. Saccone, Npm fails to restrict the actions of malicious npm packages, 2016.. (https://www.kb.cert.org/vuls/id/319816)

[37] J. H. Saltzer and M. D. Schroeder, The protection of information in computer systems, Proceedings of the IEEE, vol. 63, no. 9, pp. 1278–1308, 1975.

[38] I. Z. Schlueter and others, Node package manager, 2010.. (https://npmjs.com)[Accessed: 17-Feb-2017]

[39] N. Security, Continuous security monitoring for your node apps, 2016.. (https://nodesecurity.io/)

[40] J. S. Shapiro, J. M. Smith, and D. J. Farber, EROS: A fast capability system, vol. 33. ACM, 1999.

[41] Snyk, Find, fix and monitor for known vulnerabilities in node.js and ruby packages, 2016.. (https://snyk.io/)

[42] D. Stefan, E. Z. Yang, P. Marchenko, A. Russo, D. Herman, B. Karp, and D. Mazières, Protecting users by confining javascript with cowl, in 11th usenix symposium on operating systems design and implementation (osdi 14), 2014, pp. 131–146. (https://www.usenix.org/conference/osdi14/technical-sessions/presentation/stefan)

[43] J. Terrace, S. R. Beard, and N. P. K. Katta, JavaScript in javascript (js. js): Sandboxing third-party scripts, in Presented as part of the 3rd usenix conference on web application development (webapps 12), 2012, pp. 95–100.

[44] A. G. Williams, Changes to npm’s unpublish policy, 2016.. (http://blog.npmjs.org/post/141905368000/changes-to-npms-unpublish-policy)

[45] S. Yegulalp, How one yanked javascript package wreaked havoc, 2016.. (http://www.infoworld.com/article/3047177/javascript/how-one-yanked-javascript-package-wreaked-havoc.html)

[46] Ghost publishing platform. http://ghost.org/, 2013.


  1. Other examples of unsafe modules in safe languages include unsafePerformIO in Haskell, unsafe blocks in Rust, C/C++ modules in Ruby and Python, and foreign function interfaces (FFI) in Lua and Racket.

  2. In V8 terms, a context is an execution environment that allows separate, unrelated executions in a single instance of V8. Similar mechanisms exist in other language runtimes [15,3] or have been proposed at the OS level [22].

  3. As a point of comparison, Minix 3, a modern microkernel that championed least-privilege separation, comes with userspace servers on the order of thousands of lines of code.