r.va.gg

The perils of private politics in open source

The Node community and its leadership is evolving

Node Summit this year was an interesting, and encouraging experience. The stage was full of fresh faces. Fresh faces who weren't there just because they were fresh faces—the project and its surrounding community has genuinely refreshed. A couple of moments in the "hallway track" were instructive as we saw some historical big-names of Node, like Mikeal Rogers, go unrecognised by the busy crowds of active Node users. Node has not only grown up, but it's moved forward as any successful project should, and it has a new faces with fresh passion.

Node.js is a complex open source project to manage. There's a huge amount of activity surrounding it and it's become a critical piece of internet infrastructure, so it needs to be stable and trustworthy. Unfortunately this means that it needs corporate structures around it as it interacts with the world outside. One of the roles of the Node.js Foundation is to serve this purpose for the project.

The inevitability of politics

Politics is an unfortunate fact for open source projects of notable size. Over time, the cruft of corporate and personal politics builds up and creates messy baggage. Like an iceberg, the visible portion of politics represents only a small amount of what's built up over time and what's currently brewing. I had the displeasure of seeing behind the curtain of Node politics as I became more heavily involved, 6 or so years ago. It's not exactly a pretty sight, but I'm certain it's not unique to Node. For the most part, we have a tacit agreement to keep a lot of the messy stuff private. I suppose the theory here is that there's no reason to taint the rosy view of users and contributors who will never be directly impacted by it, and will never even have reason to see it. Speaking about a lot of these things would simply fall into the gossip category, in fact. So we set it aside and move on. But it never really goes away, the pile of cruft just gets bigger.

On many occasions I have watched as idealistic new contributors, technical and non-technical, are raised up to positions where they become exposed to the private politics. It's disheartening to stand by (or worse: be involved in the process), as someone with a rosy view of the Node community is lead, nose first, into its smelly armpits. Of course the armpits are only a small part of the body, so having a rosy view isn't illegitimate if you don't intend to get all up into the smelly bits! But leadership requires a good amount of exposure to the smelly parts of Node.

Many people who step into the elite realms of the Technical Steering Committee (TSC) and Community Committee eventually discover that there's a lot behind the curtains. I use the term "elite" sarcastically here, because that's not what these bodies are intended to be. But the fact of having a curtain creates an unfortunate tendency toward a separateness.

I'm confident that most, if not all, of the people currently on the TSC and the Community Committee, are in open source because they're attracted to open community. That's what's so great about Node and other thriving open source ecosystems. Many of us are in awkward positions where we have a foot in the corporate world and a foot in open source, so we have to learn to operate in different modes. We end up making choices about how we conduct ourselves regarding the community vs corporate and it not easy to stay conscious of the different requirements of our various roles.

There's a danger in being drawn up into these kinds of prominent positions in a complex open source project. We get exposed to the armpit, whether we like it or not. Making matters more complex, we are backed by, and therefore have to interact with, a corporation: the Node.js Foundation. The Foundation is run by a board of highly experienced corporate-political operators (and I mean no disrespect by this—it takes a significant amount of skill to navigate to the kinds of positions in large companies that make you an obvious choice to sit on such boards). Furthermore, the Node.js Foundation is essentially a shell that lives inside the Linux Foundation, a very rich source of corporate politics of the open source variety.

So we are faced with competing pressures: our personal passions for open communities, and the complexities of private corporate-style politics that don't fit very well with "openness" and "transparency". I've felt this since the Node.js Foundation begun—I sat on the board for two terms at the beginning, participating in those politics. But I've also been one of the loudest voices for transparency and "open governance"—a model that I championed prior to it being adopted by io.js, followed by Node.js under the Foundation. But to be honest, my record is mixed, just like everyone else who has had to navigate similar positions. None of us leave unscathed.

A TSC representative to the board usually wants to be able to report important items to the TSC where they are allowed, and also use the TSC as a sounding-board as they participate in the corporate decision-making process. The dual pressures of "this is sensitive and private" vs "we should be open and transparent" are nasty. Corporate structures and the kinds of politics they engender are not inherently bad, they are arguably necessary in the world in which we exist. In professionalised open source, we are trying to squish together openness and the closed nature of the corporate world which creates a lot of tension and conflicting incentives. It's not hard to understand why many open source projects actively avoid this kind of "professionalisation".

Serving as counter balance to the corporate

Here's the critical part that's so easy to lose sight of: those on the open source side should be the advocates for openness. That's a large reason we get to be at the big table and it's on us to keep the pressure on, to ensure that a tension continues to exist. Those on the other side are advocates (in some cases legally so) of the corporate approach. The lure of private politics is so strong that it will always have momentum on its side, and it takes very conscious effort to push back against it. From discussions during the formation of the Foundation, I know that there are many on the corporate side that expect open source folks to provide this kind of pressure, that's part of the system's design. Perhaps we should even insert our responsibility as advocates for openness and transparency as an explicit feature of project governance.

The temptation to "keep it private", "take it offline", or "get it right before going public", is natural, because it honestly makes many things easier in the short-term, i.e., it's the path of expedience. Our heated sociopolitical environment today makes this worse; with the potential for drama and mob behaviour leading to an understandable risk aversion.

And so we have private mailing lists, private sections of "public" meetings, one-on-one strategy discussions, huddles in the corners of conferences where we occasionally meet in person. We have to keep the wheels turning and it's easier to just push things through privately than have to deal with the friction of public discussion, feedback, criticism, drama.

But that's neglecting what our communities charge us with! Particularly for Node.js, where we have a governance model that makes it clear that the TSC doesn't own or run the project. The TSC is intended to simply be a decision-making fallback.

The project is intended to be owned and run by its contributors. The TSC should be the facilitators of that, and reluctantly involve itself collectively where individual contributors can't find a way forward. The Community Committee is intended take an outward facing role, making for a different kind of challenging bargain when they get sucked in. Today we have various stake-holders asking for "the TSC's opinion", or "the Community Committee's opinion" on matters. I even tried to get that when I was a board member, attempting to "represent the TSC," which I believed was my role at the time. But there really should be no such thing as the TSC or Community Committee's "opinion", it's frankly absurd when you consider that these bodies are supposed to be made up of a broad diversity of viewpoints.

The more we accept private decision-making and politicking, the more we undermine community-focused governance.

A busy time for private politicking

It could be an artefact of my perspective, but it seems that we have a nexus of some very heavy private political discussions happening in Node.js-land at the moment. My fear is that it's the sign of a trend, and I hope this post can help serve as a corrective.

Some highlights that you probably won't see the context of on GitHub include:

  1. Discussions about the Node.js Foundation's annual conference, Node.js Interactive, which was renamed this year to JS Interactive, and then renamed again recently to Node+JS Interactive. The Foundation's executive decided to involve the TSC and Community Committee in that last decision and it was resolved entirely in private (as far as I'm aware) and then announced to the world. I personally thought the switch to "JS Interactive" was a mistake. I also thought that changing the name again was a mistake. To be honest (and in hindsight), however, I'd rather the executive didn't even draw the TSC and Community Committee in, as collectives, to these private discussions, because we've now become complicit in the private decision making process. It's really not a good look for either committee to be involved in surprise major announcements—that stands in stark contrast to our open decision making processes. Seek individual feedback, sure, but this also goes back to my point about the problems with seeking collective opinions.
  2. Discussions about the efficacy of the Foundation as an executive body, particularly as it focuses on filling an empty Executive Director (i.e. CEO) chair. I'll admit guilt to fuelling a lot of this discussion myself, I've been a strong critic of the Foundation in recent times. However, those of us with something to say either need to be bold enough to be public with critique, take it directly to decision-makers involved, or butt-out entirely. As an advocate of openness and transparency, I'd suggest that public discussion on these matters would be fruitful because so many people and organisations are impacted by them.
  3. Discussions about very major restructuring of the Node.js Foundation itself. Having implications that would call for large changes to the by-laws, including changing the very purpose of the Foundation. I don't want to be the one to speak publicly about this, but I would like to see those who are driving this discussion be able to make their case in public sooner rather than later. This will again lead to surprise major announcements that the TSC and Community Committee will again be complicit in. The board should either make such changes and own the responsibility for it, or should set up an open process for feedback and discussion. The TSC and Community Committee should be rejecting the requests made of them to be the source of feedback and discussion prior to major changes. These bodies can facilitate broader discussions, but they are not the source of definitive opinion or truth for the Node.js community.
  4. Discussions about major changes to Node.js project governance, instigated by parties external to the TSC and Community Committee, entirely in private and with significant political and ideological pressure. Large discussion threads and entire meetings have been devoted to these matters already, without one hint to the outside world that a flurry of pull requests may soon appear with little context. Given some of the ideological content there is potential for more drama so I have a lot of sympathy for people for wanting to take the easy path with this. I'm close enough to the centre of these matters that I will likely write publicly about them soon. I have very strong opinions about what's good for open governance, and maintaining a diversity of opinion and viewpoints on the critical bodies surrounding Node.js. My primary objection to the process conducted so far is that any discussions about change in open source governance by governing bodies must be conducted in the open. And that any private collective change-planning by these bodies undermines the community-focused open governance model that Node.js has adopted.

Finding a balancing point

I'd like to not give private politics any more legitimacy in open source. Leave it for the corporate realm. I'd like for the TSC and Community Committee to adopt an explicit position of being against closed-door discussions wherever possible and being advocates for openness and transparency. That will cause personal conflicts, it will mean difficulty for the Node.js Foundation board and the executive when they want to engage in "sensitive" topics. But the definition of "sensitive" on our side should be different. What's more, we are not there to solve their problems, it's precisely the opposite!

The ideal dynamic between open source and its corporate partners

Here's my initial suggestions for guidelines:

  1. If you want to call something "sensitive" then it better involve something personal about an individual who could be hurt by public discussion of the matter.
  2. If you have a proposal for changing anything and you're not prepared to receive public feedback and critique, then it's not worth us discussing as groups.
  3. The TSC and Community Committees should refuse, as much as possible, to be involved as collectives in decision-making processes that must be private for corporate governance or legal reasons. That's not their role and it's unfair to force them into an awkward corner.

The wriggle-room between treating the TSC and Community Committee as "groups" vs pulling individuals from those groups into private politics as a proxy is a tricky one that every individual is going to have to negotiate. I would hope that having these groups explicitly state their preference for openness, while highlighting the risks would go a long way to creating the healthy tension that we need with our corporate partners.

I hope it’s obvious, but this is all my personal opinion and does not necessarily represent that of my employer nor any bodies surrounding Node.js that I’m involved in.

Node.js and the "HashWick" vulnerability

The following post was originally published on the NodeSource Blog. This text is copyright NodeSource and is reproduced with permission.


Yesterday, veteran Node.js core contributor and former Node.js TSC member Fedor Indutny published an article on his personal blog detailing a newly-discovered vulnerability in V8. Named HashWick, this vulnerability will need to be addressed by Node.js, but as yet has not been patched.

This article will cover the details surrounding the disclosure yesterday, and explain some of the technical background. As a patch for Node.js is not yet available, I will also present some mitigation options for users and discuss how this vulnerability is likely to be addressed by Node.js.

Responsible disclosure

Fedor originally reported this vulnerability to V8 and the Node.js security team in May. Unfortunately, the underlying issues are complex, and Node's use of older V8 engines complicates the process of finding and applying a suitable fix. The Node.js TSC delegated responsibility to the V8 team to come up with a solution.

After reporting the vulnerability, Fedor followed a standard practice of holding off public disclosure for 90 days, and although a fix has yet to land in Node, he published high-level details of his findings.

It is worth pointing out that Fedor’s disclosure does not contain code or specific details on how to exploit this vulnerability; moreover, to exploit HashWick a malicious party would need to tackle some fairly difficult timing analysis. However, knowledge that such a vulnerability exists, and can potentially be executed on a standard PC, is likely to spur some to reverse engineer the details for themselves.

These circumstances leave us all in an awkward situation while we wait for a fix, but I expect this disclosure to result in security releases in Node.js in the coming weeks.

Vulnerability details

There are three important concepts involved in this vulnerability:

  1. Hash functions and hash tables
  2. Hash flooding attacks
  3. Timing analysis

Hash functions

Hash functions are a fundamental concept in computer science. They are typically associated with cryptography, but are widely used for non-cryptographic needs. A hash function is simply any function that takes input data of some type and is able to repeatedly return output of a predictable size and range of values. An ideal hash function is one that exhibits apparent randomness and whose results spread evenly across the output range, regardless of input values.

To understand the utility of such functions, consider a "sharded" database system, divided into multiple storage backends. To route data storage and retrieval, you need a routing mechanism that knows which backend that data belongs in. Given a key, how should the routing mechanism determine where to put new data, and then where to get stored data when requested? A random routing mechanism isn't helpful here, unless you also want to store metadata telling you which random backend a particular key's value was placed in.

This is where hash functions come in handy. A hash function would allow you to take any given key and return a “backend identifier” value, directing the routing mechanism to assign data to a particular backend. Despite apparent randomness, a good hash function can thus distribute keys across all of your backends fairly evenly.

This concept also operates at the most basic levels of our programming languages and their runtimes. Most languages have hash tables of some kind; data structures that can store values with arbitrary keys. In JavaScript, almost any object can become a hash table because you can add string properties, and store whatever values you like. This is because Object is a form of hash table, and almost everything is related to Object in some way. const foo = { hash: 'table' } stores the value 'table' at key 'hash'. Even an Array can take the form of a hash table. Arrays in JavaScript are not limited to integer keys, and they can be as sparse as you like: const a = [ 1, 2, 3 ]; a[1000] = 4; a['hash'] = 'table';. The underlying storage of these hash tables in JavaScript needs to be practical and efficient.

If a JavaScript object is backed by a memory location of a fixed size, the runtime needs to know where in that space a particular key's value should be located. This is where hash functions come in. An operation such as a['hash'] involves taking the string 'hash', running it through a hash function, and determining exactly where in the object's memory storage the value belongs. But here's the catch: since we are typically dealing with small memory spaces (a new Array in V8 starts off with space for only 4 values by default), a hash function is likely to produce "collisions", where the output for 'hash' may collide with the same location as 'foo'. So the runtime has to take this into account. V8 deals with collision problems by simply incrementing the storage location by one until an empty space can be found. So if the storage location for 'hash' is already occupied by the value of 'foo', V8 will move across one space, and store it there if that space is empty. If a new value has a collision with either of these spaces, then the incrementing continues until an empty space is found. This process of incrementing can become costly, adding time to data storage operations, which is why hash functions are so important: a good hash function will exhibit maximum randomness.

Hash flooding attacks

Hash flooding attacks take advantage of predictability, or poor randomness, in hash functions to overwhelm a target and force it to work hard to store or look up values. These attacks essentially bypass the utility of a hash function by forcing excessive work to find storage locations.

In our sharded data store example above, a hash flood attack may involve an attacker knowing exactly how keys are resolved to storage locations. By forcing the storage or look-up of values in a single backend, an attacker may be able to overwhelm the entire storage system by placing excessive load on that backend, thereby bypassing any load-sharing advantage that a bucketing system normally provides.

In Node.js, if an attacker knows exactly how keys are converted to storage locations, they may be able to send a server many object property keys that resolve to the same location, potentially causing an increasing amount of work as V8 performs its check-and-increment operations finding places to store the values. Feed enough of this colliding data to a server and it'll end up spending most of its time simply trying to figure out how to store and address it. This could be as simple as feeding a JSON string to a server that is known to parse input JSON. If that JSON contains an object with many keys that all collide, the object construction process will be very expensive. This is the essence of a denial-of-service (DoS) attack: force the server to do an excessive amount of work, preventing it from being able to perform its normal functions.

Hash flooding is a well known attack type, and standard mitigation involves very good hash functions, combined with additional randomness: keyed hash functions. A keyed hash function, is a hash function that is seeded with a random key. That same seed is provided with every hash operation, so that together, the seed and an input value yield the same output value. Change the seed, and the output value is entirely different. In this way, it is not good enough to simply know the particular hash function being used, you also need to know the random seed the system is using.

V8 uses a keyed hash function for its object property storage operations (and other operations that require hash functions). It generates a random key at start-up and keeps on using that key for the duration of the application's lifetime. To execute a hash flood type attack against V8, you need to know the random seed it's using internally. This is precisely what Fedor has figured out how to do—determine the hash seed used by an instance of V8 by inspecting it from the outside. Once you have the seed, you can perform a hash flood attack and render a Node.js server unresponsive, or even crash it entirely.

Timing attacks

We covered timing attacks in some detail in our deep dive of the August 2018 Node.js security releases. A timing attack is a method of determining sensitive data or program execution steps, by analyzing the time it takes for operations to be performed. This can be done at a very low level, such as most of the recent high-profile vulnerabilities reported against CPUs that rely on memory look-up timing and the timing of other CPU operations.

At the application level, a timing attack could simply analyze the amount of time it takes to compare strings and make strong guesses about what's being compared. In a sensitive operation such as if (inputValue == 'secretPassword') ..., an attacker may feed many string variations and analyze the timing. The time it takes to process a inputValues of 'a', 'b' ... 's' may give enough information to assume the first character of the secret. Since timing differences are so tiny, it may take many passes and an average of results to be able to make strong enough inference. Timing attacks often involve a lot of testing and a timing attack against a remote server will usually involve sending a lot of data.

Fedor's attack against V8 involves using timing differences to work out the hash seed in use. He claims that by sending approximately 2G of data to a Node.js server, he can collect enough information to reverse engineer the seed value. Thanks to quirks in JavaScript and in the way V8 handles object construction, an external attacker can force many increment-and-store operations. By collecting enough timing data on these operations, combined with knowledge of the hash algorithm being used (which is no secret), a sophisticated analysis can unearth the seed value. Once you have the seed, a hash flood attack is fairly straightforward.

Mitigation

There are a number of ways a Node.js developer can foil this type of attack without V8 being patched, or at least make it more difficult. These also represent good practice in application architecture so they are worth implementing regardless of the impact of this specific vulnerability.

The front-line for mitigating against timing attacks for publicly accessible network services is rate limiting. Note that Fedor needs to send 2G of data to determine the hash seed. A server that implements basic rate limiting for clients is likely to make it more difficult or impractical to execute such an attack. Unfortunately, such rate limiting needs to be applied before too much internal V8 processing is allowed to happen. A JSON.parse() on an input string before telling the client that they have exceeded the maximum requests for their IP address won't help mitigate. Additionally, rate limiting may not mitigate against distributed timing attacks, although these are much more difficult to execute due to the variability in network conditions across multiple clients, leading to very fuzzy timing data.

Other types of input limiting will also be useful. If your service blindly applies a JSON.parse(), or other operation, to any length of input, it will be much easier for an attacker to unearth important timing information. Ensure that you have basic input limit checks in place and your network services don't blindly process whatever they are provided.

Standard load balancing approaches make such attacks more difficult too. If a client cannot control which Node.js instance it is talking to for any given connection, it will be much more difficult to perform a useful timing analysis of the type Fedor has outlined. Likewise, if a client has no way to determine which unique instance it has been talking to (such as a cookie that identifies the server instance), such an attack may be impossible given a large enough cluster.

The future for V8

As Fedor outlined in his post, the best mitigation comes from V8 fixing its weak hash function. The two suggestions he has are:

  1. Increase the hash seed size from 32 bits to 64 bits
  2. Replace the hash function with something that exhibits better randomness

The key size suggestion simply increases the complexity and cost of an attack, but doesn't make it go away. Any sufficiently motivated attacker with enough resources may be able to perform the same attack, just on a different scale. Instead of 2G of data, a lot more may need to be sent and this may be impossible in many cases.

A change of hash function would follow a practice adopted by many runtimes and platforms that require hash functions but need to protect against hash flood attacks. SipHash was developed specifically for this use and has been slowly adopted as a standard since its introduction 6 years ago. Perl, Python, Rust and Haskell all use SipHash in some form for their hash table data structures.

SipHash has properties similar to constant-time operations used to mitigate against other forms of timing attacks. By analyzing the timing of the hash function, you cannot (as far as we know) make inference about the seed being used. SipHash is also fast in comparison to many other common and secure keyed hash functions, although it may not be faster than the more naive operation V8 is currently using. Ultimately, it’s up to the V8 authors to come up with an appropriate solution that takes into account the requirement for security and the importance of speed.

Background Briefing: August Node.js Security Releases

The following post was originally published on the NodeSource Blog as Node.js Security Release Summary - August 2018. This text is copyright NodeSource and is reproduced with permission. This is a deep-dive into the security vulnerabilities described in my brief summary on the Node.js blog as August 2018 Security Releases.


This month's Node.js security releases are primarily focused on upgrades to the OpenSSL library. There are also two minor Node.js security-related flaws in Node.js' Buffer object. All of the flaws addressed in the OpenSSL upgrade and the fixes to Buffer can be classified as either "low" or "very low" in severity. However, this assessment is generic and may not be appropriate to your own Node.js application. It is important to understand the basics of the flaws being addressed and make your own impact assessment. Most users will not be impacted at all by the vulnerabilities being patched but specific use-cases may cause a high severity impact. You may also be exposed via packages you are using via npm, so upgrading as soon as practical is always recommended.

Node.js switched to the new 1.1.0 release line of OpenSSL for version 10 earlier this year. Before Node.js 10 becomes LTS in October, we expect to further upgrade it to OpenSSL 1.1.1 which will add TLS 1.3 support. Node.js' current LTS lines, 8 ("Carbon") and 6 ("Boron") will continue to use OpenSSL 1.0.2.

In the meantime, OpenSSL continues to support their 1.1.0 and 1.0.2 release lines with a regular stream of security fixes and improvements and Node.js has adopted a practice of shipping new releases with these changes included shortly after their release upstream. Where there are non-trivial "security" fixes, Node.js will generally ship LTS releases with only those security fixes so users have the ability to drop in low-risk upgrades to their deployments. This is the case for this month's releases.

The August OpenSSL releases of versions 1.1.0i and 1.0.2p are technically labelled "bug-fix" releases by the OpenSSL team but they do include security fixes! The reason this isn't classified as a security release is that those security fixes have already been disclosed and the code is available on GitHub. They are low severity, and one of the three security items included doesn't even have a CVE number assigned to it. However, this doesn't mean they should be ignored. You should be aware of the risks and possible attack vectors before making decisions about rolling out upgrades.

OpenSSL: Client DoS due to large DH parameter (CVE-2018-0732)

All actively supported release lines of Node.js are impacted by this flaw. Patches are included in both OpenSSL 1.1.0i (Node.js 10) and 1.0.2p (Node.js 6 LTS "Boron" and Node.js 8 LTS "Carbon").

This fixes a potential denial of service (DoS) attack against client connections by a malicious server. During a TLS communication handshake, where both client and server agree to use a cipher-suite using DH or DHE (Diffie–Hellman, in both ephemeral and non-ephemeral modes), a malicious server can send a very large prime value to the client. Because this has been unbounded in OpenSSL, the client can be forced to spend an unreasonably long period of time to generate a key, potentially causing a denial of service.

We would expect to see a higher severity for this bug if it were reversed and a client could impose this tax on servers. But in practice, there are more limited scenarios where a denial of service is practical against client connections.

The fix for this bug in OpenSSL limits the number of bits in the prime modulus to 10,000 bits. Numbers in excess will simply fail the DH handshake and a standard SSL error will be emitted.

Scenarios where Node.js users may need to be concerned about this flaw include those where your application is making client TLS connections to untrusted servers, where significant CPU costs in attempting to establish that connection is likely to cause cascading impact in your application. A TLS connection could be for HTTPS, encrypted HTTP/2 or a plain TLS socket. An "untrusted server" is one outside of your control and not in the control of trustworthy third-parties. An application would likely need to be forced to make a large number of these high-cost connections for an impact to be felt, but you should assess your architecture to determine if such an impact is likely, or even possible.

OpenSSL: Cache timing vulnerability in RSA key generation (CVE-2018-0737)

Node.js is not impacted by this vulnerability as it doesn't expose or use RSA key generation functionality in OpenSSL. However, it is worth understanding some of the background of this vulnerability as we are seeing an increasing number of software and hardware flaws relating to potential timing attacks. Programming defensively so as to not expose the timing of critical operations in your application is just as important as sanitizing user input while constructing SQL queries. Unfortunately, timing attacks are not as easy to understand, or as obvious, so tend to be overlooked.

Side-channel attacks are far from new, but there is more interest in this area of security, and researchers have been focusing more attention on novel ways to extract hidden information. Spectre and Meltdown are the two recent high-profile examples that target CPU design flaws. CVE-2018-0737 is another example, and itself uses hardware-level design flaws. A paper by Alejandro Cabrera Aldaya, Cesar Pereida García, Luis Manuel Alvarez Tapia and Billy Bob Brumley from Universidad Tecnológica de la Habana (CUJAE), Cuba, and Tampere University of Technology, Finland outlines a cache-timing attack on RSA key generation, the basis of this OpenSSL flaw.

The CVE-2018-0737 flaw relies on a "Flush+Reload attack" which targets the last-level of cache on the system (L3, or level-3 cache on many modern processors). This type of attack exploits the way that Intel x86 architectures structure their cache and share it between processors and processes for efficiency. By setting up a local process that shares an area of cache memory with another process you wish to attack, you can make high-confidence inferences about the code being executed in that process. The attack is called "Flush+Reload" because the process executing the attack, called the "spy", causes a flush on the area of cache containing a piece of critical code, then waits a small amount of time and reloads that code in the cache. By measuring the amount of time the reload takes, the spy can infer whether the process under attack loaded, and therefore executed, the code in question or not. This attack looks at code being executed, not data, but in many cryptographic calculations, the sequence of operations can tell you all you need to know about what data is being generated or operated on. These attacks have been successfully demonstrated against different implementations of RSA, ECDSA and even AES. The attack has been shown to work across virtual machines in shared environments under certain circumstances. One researcher even demonstrated the ability to detect the sequence of operations executed by a user of vi on a shared machine.

An important take-away about cache-timing attacks is that they require local access to the system under attack. They are an attack type that probes the physical hardware in some way to gather information. Public clouds are usually not vulnerable because of the way cache is configured and partitioned, but we shouldn't assume we won't see new novel timing attacks that impact public clouds in the future. Of course browsers blur the definition of "local code execution", so we shouldn't be complacent! CVE-2018-0737 is marked as "Low" severity by the OpenSSL team because of the requirement for local access, the difficulty in mounting a successful attack and the rare circumstances in which an attack is feasible.

The best protection against Flush+Reload and many other classes of timing attacks is to use constant-time operations for cryptographic primitives and operations that expose potentially sensitive information. If an operation follows a stable code path and takes a constant amount of time regardless of input or output then it can be hard, or impossible to make external inference about what is going on. An operation as simple as if (userInput === 'supersecretkey') { ... } can be vulnerable to a timing attack if an attacker has the ability to execute this code path enough times. In 2014, as the PHP community debated switching certain operations to constant-time variants, Anthony Ferrara wrote a great piece about timing attacks and the types of mitigations available. Even though it addresses PHP specifically, the same concepts are universal.

The fix that OpenSSL applied for CVE-2018-0737 was a straight-forward switch to constant-time operations for the code in question. For RSA, this has the effect of masking the operations being performed from side-channel inspection, such as the use of cache.

Be aware that Node.js has a crypto.timingSafeEqual() operation that can be used whenever performing sensitive comparisons. Using this function, our vulnerable operation becomes if (crypto.timingSafeEqual(Buffer.fromString(userInput), Buffer.fromString('supersecretkey')) { ... } and we stop exposing timing information to potential attackers.

OpenSSL: ECDSA key extraction local side-channel

All actively supported release lines of Node.js are impacted by this flaw. Patches are included in both OpenSSL 1.1.0i (Node.js 10) and 1.0.2p (Node.js 6 LTS "Boron" and Node.js 8 LTS "Carbon").

This flaw does not have a CVE due to OpenSSL policy to not assign itself CVEs for local-only vulnerabilities that are more academic than practical. This vulnerability was discovered by Keegan Ryan at NCC Group and impacts many cryptographic libraries including LibreSSL, BoringSSL, NSS, WolfCrypt, Botan, libgcrypt, MatrixSSL, and of course OpenSSL. A CVE was assigned for this issue specifically for libgcrypt, CVE-2018-0495.

This flaw is very similar to the above RSA key generation cache-timing flaw in that it also uses cache-timing and an attacker must be able to execute code on the local machine being attacked. It also uses a Flush+Reload to infer the operations being performed but this time it examines Digital Signature Algorithm (DSA) the Elliptic Curve Digital Signature Algorithm (ECDSA), but a little more information is required to mount a successful attack. In an attack scenario, the victim uses a private key to create several signatures. The attacker observes the resulting signatures must know the messages being signed. Then, the cache-timing side-channel is used to infer order of operations and backfill to find the private key.

This attack could be used against TLS, or SSH, and there are mechanisms in both that would give an attacker enough information to perform a successful attack under certain circumstances. The key component again being local access to a server performing the DSA or ECDSA signing operation, or access to a virtual machine on the same host as long as cache isn't partitioned as it often is for public clouds.

Unlike the RSA flaw, a fix is not as simple as switching to constant-time operations. Instead, the fix involves adding a “blinding”) to the calculation. Blinding is a technique that can mask the underlying operation from side-channel inspection by inserting unpredictability which can be later reversed. This specific fix addresses the problematic addition (+) operation which exposes the side-channel leak. It does this by adding a random value as noise to both sides of the equation. Now, when observing the operation, it is theoretically impossible to remove the noise and discover the important information that would leak data.

Unintentional exposure of uninitialized memory in Buffer creation (CVE-2018-7166)

All versions of Node.js 10 are impacted by this flaw. Prior release lines are not impacted.

Node.js TSC member Сковорода Никита Андреевич (Nikita Skovoroda / @ChALkeR) discovered an argument processing flaw that causes causes Buffer.alloc() to return uninitialized memory. This method is intended to be safe and only return initialized, or cleared, memory.

Memory is not automatically cleared after use by most software and it is not generally cleared within Node.js during an application's lifetime when memory is freed from internal use. This means that a call to malloc() (system memory allocation) usually returns a block of memory that contains data stored by the previous user of that block who free()d it without clearing it. This can cause problems if an attacker can find a way to create these blocks and inspect their contents as secrets usually pass through memory—passwords, credit card numbers, etc. Allocate enough blocks of uncleared memory and you're bound to find something interesting.

In the browser, you have no way to allocate uninitialized memory, so a malicious site can't inspect your memory to find sensitive data arising from your interactions with another site. ArrayBuffer and the various TypedArray types will only ever give you initialized, or zeroed memory—memory that contains only 0s.

Historically, for the sake of performance, Node.js has acted more like a traditional un-sandboxed server-side runtime that doesn't need the same kinds of protections as browsers. Unfortunately, many JavaScript programmers are not as attuned to the risks of using uninitialized memory. Additionally, the Buffer constructor itself has some usability flaws that have lead to many expert programmers exposing uninitialized memory to potential attackers. ws, the very popular WebSocket library, authored by skilled programmers, famously exposed uninitialized memory to client connections over the network by means of a simple remote ping() call that passed an integer instead of a string.

The usability concerns around Buffer lead to the deprecation of the Buffer() constructor and introduction of new factory methods: Buffer.from(), Buffer.alloc(), Buffer.allocUnsafe(), and the --zero-fill-buffers command line argument. It's worth noting that from version 1.0, N|Solid, NodeSource's enterprise Node.js runtime, included a "zeroFillAllocations" option in its policies feature to address similar concerns.

Unfortunately, the root cause of Buffer constructor usability concerns—too much flexibility in argument types—is still with us, this time in Buffer#fill() who's signature is far too flexible: Buffer#fill(value[, offset[, end]][, encoding]). Internal re-use of this function, and its flexible argument parsing, by Buffer.alloc() exposes a bug that allows a supposedly safe allocation method to return unsafe (i.e. uninitialized) memory blocks.

Buffer.alloc() allows a third argument, encoding. When there is a second argument, fill, this and the encoding argument are passed blindly to the internal fill() implementation as second and third arguments. This is where it encounters the familiar Buffer() constructor problem:

function _fill(buf, val, start, end, encoding) {
 if (typeof val === 'string') {
   if (start === undefined || typeof start === 'string') {
     encoding = start;
     start = 0;
     end = buf.length;
   } else if (typeof end === 'string') {
     encoding = end;
     end = buf.length;
   }
   // ...

The intention here is that by only passing three arguments, with the third one being encoding, the flexible argument parsing rules would enter the top set of instructions and set encoding = start, start = 0, end = buf.length, precisely what we want for a Buffer fully initialized with the provided val. However, because Buffer.alloc() does minimal type checking of its own, the encoding argument could be a number and this whole block of argument rewriting would be skipped and start could be set to some arbitrary point in the Buffer, even the very end, leaving the whole memory block uninitialized:

> Buffer.alloc(20, 1)
<Buffer 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01>
> Buffer.alloc(20, 'x')
<Buffer 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78 78>
> Buffer.alloc(20, 1, 20)
<Buffer 80 be 6a 01 01 00 00 00 ff ff ff ff ff ff ff ff 00 00 00 00>
// whoops!

This is only a security concern if you are allowing unsanitized user input to control the third argument to Buffer.alloc(). Unless you are fully sanitizing and type-checking everything coming in from an external source and know precisely what types are required by your dependencies, you should not assume that you are not exposed.

The fix for CVE-2018-7166 simply involves being explicit with internal arguments passed from alloc() to fill() and bypassing the argument shifting code entirely. Avoiding argument cleverness is a good rule to adopt in any case for robustness and security.

Out of bounds (OOB) write in Buffer (CVE-2018-12115)

All actively supported release lines of Node.js are impacted by this flaw.

Node.js TSC member Сковорода Никита Андреевич (Nikita Skovoroda / @ChALkeR) discovered an OOB write in Buffer that can be used to write to memory outside of a Buffer's memory space. This can corrupt unrelated Buffer objects or cause the Node.js process to crash.

Buffer objects expose areas of raw memory in JavaScript. Under the hood, this is done in different ways depending on how the Buffer is created and how big it needs to be. For Buffers less than 8k bytes in length created via Buffer.allocUnsafe() and from most uses of Buffer.from(), this memory is allocated from a pool. This pool is made up of areas of block-allocated memory larger than an individual Buffer. So Buffers created sequentially will often occupy adjoining memory space. In other cases, memory space may sit adjacent with some other important area of memory used by the current application—likely an internal part of V8 which makes heaviest use of memory in a typical Node.js application.

CVE-2018-12115 centers on Buffer#write() when working with UCS-2 encoding, (recognized by Node.js under the names 'ucs2', 'ucs-2', 'utf16le' and 'utf-16le') and takes advantage of its two-bytes-per-character arrangement.

Exploiting this flaw involves confusing the UCS-2 string encoding utility in Node.js by telling it you wish to write new contents in the second-to-last position of the current Buffer. Since one byte is not enough for a single UCS-2 character, it should be rejected without changing the target Buffer, just like any write() with zero bytes is. The UCS-2 string encoding utility is written with the assumption that it has at least one whole character to write, but by breaking this assumption we end up setting the "maximum number of characters to write" to -1, which, when passed to V8 to perform the write, is interpreted as "all of the buffer you provided".

UCS-2 encoding can therefore be tricked to write as many bytes as you want from the second-to-last position of a Buffer on to the next area of memory. This memory space may be occupied by another Buffer in the application, or even to another semi-random memory space within our application, corrupting state and potentially causing an immediate segmentation fault crash. At best this can be used for a denial of service by forcing a crash. At worst, it could be used to overwrite sensitive data to trick an application into unintended behavior.

As with CVE-2018-7166, exploiting this flaw requires the passing of unsanitized data through to Buffer#write(), possibly in both the data to be written and the position for writing. Unfortunately, this is not an easy scenario to recognize and such code has been found to exist in npm packages available today.

The fix for CVE-2018-12115 involves checking for this underflow and bailing early when there really are no full UCS-2 characters to write.

The Truth About Rod Vagg

NOTE: This post is copied from https://github.com/nodejs/CTC/issues/165#issuecomment-324798494 and the primary intended audience was the Node.js CTC.


Dear reader from ${externalSource}: I neither like nor support personal abuse or attacks. If you are showing up here getting angry at any party involved, I would ask you to refrain from targeting them, privately or in public. Specifically to people who think they may be supporting me by engaging in abusive behaviour: I do not appreciate, want or need it, in any form and it is not helpful in any way.

Yep, this is a long post, but no apologies for the length this time. Buckle up.

I'm sad that we have reached this point, and that the CTC is being asked to make such a difficult decision. One of the reasons that we initially split the TSC into two groups was to insulate the technical doers on the CTC from the overhead of administrative and political tedium. I know many of you never imagined you'd have to deal with something like this when you agreed to join and that this is a very uncomfortable experience for you.

It's obvious that we never figured out a suitable structure that made the TSC a useful, functional, and healthy body that might be able to deal more effectively with these kinds of problems, more isolated from the CTC. I'm willing to accept a sizeable share of the blame for not improving our organisational structure during my tenure in leadership.

My response

Regarding the request for me to resign from the CTC: in lieu of clear justification that my removal is for the benefit of the Node.js project, or a case for my removal that is not built primarily on hearsay and innuendo, I respectfully decline.

There are two primary reasons for which I am standing my ground.

I cannot, in good conscience, give credence to the straw-man version of me being touted loudly on social media and on GitHub. This caricature of me and vague notions regarding my "toxicity", my propensity for "harassment", the "systematic" breaking of rules and other slanderous claims against my character has no basis in fact. I will not dignify these attacks by taking tacit responsibility through voluntary resignation.

Secondly, and arguably more importantly for the CTC: I absolutely will not take responsibility for the precedent that is currently being set. The dogged pursuit of a leader of this project, the strong-arm tactics being deployed with the goal of having me voluntarily resign, or my eventual removal from this organisation are not the behavior of a healthy, productive, or inclusive community.

My primary concern is that the consequences of these actions endanger the future health of the Node.js project. I do not believe that I am an irreplaceable snowflake (I’m entirely replaceable). There is reason to pause before making this an acceptable part of how we conduct our governance and our internal relationships.

However, while I am not happy to have the burden of this decision being foisted upon all of you, I am content with standing to be judged by this group. As the creative force behind Node.js and the legitimate owners of this project, my respect for you as individuals and as a group and your rightful position as final arbiters of the technical Node.js project makes entirely comfortable living with whatever decision you arrive at regarding my removal.

I will break the rest of this post into the following sections:

  • My critique of the process so far
  • My response to list of complaints made against me to the TSC
  • Addressing the claims often repeated across the internet regarding me as a hinderance to progress on inclusivity and diversity
  • The independence of the technical group, the new threats posed to that independence
  • The threats posed to future leadership of the project

The process so far

My personal experience so far has been approximately as follows:

  • Some time ago I received notification via email that there are complaints against me. No details were provided and I was informed that I would neither receive those details or be involved in the whatever process was to take place. Further, TSC members were not allowed to speak to me directly about these matters, including my work colleagues also on the TSC. I was never provided with an opportunity to understand the specific charges against me or be involved in any discussions on this topic from that point onward.
  • 3 days ago, I saw nodejs/TSC#310 at the same time as the public. This was the first time that I had seen the list of complaints. It was the first that I heard that there was a vote taking place regarding my position.
  • At no point have I been provided with an opportunity to answer to these complaints, correct the factual errors contained in them (see below), apologise and make amends where possible, or provide additional context that may further explain accusations against me.
  • At no point have I been approached by a member of the TSC or CTC regarding any of these items other than what the record that we have here on GitHub shows—primarily in the threads involved and in the moderation repository, the record is open for you to view regarding the due diligence undertaken either by my accusers or those executing the process. I have had interactions with only a single member of the TSC regarding one of these matters in private email and in person which has, on both occasions, involved me attempting to coax out the source of bad feelings that I had sensed and attempting to (relatively blindly) make amends.

I hope you can empathise that to me this process is rather unfair and regardless of whether this process is informed or dictated by our governance documents as has been claimed, it should be changed so that in the future accused parties have the chance to at least respond to accusations.

Response to the list of complaints

I am including the text that was redacted from nodejs/TSC#310 as it is already in the public domain, on social media, also on GitHub and now in the press. Please note that I did not ask for this text to be redacted.

1.

In [link to moderation repository discussion, not copied here out of respect for additional parties involved], Rod’s first action was to apologize to a contributor who had been repeatedly moderated. Rod did not discuss the issue with other members of the CTC/TSC first. The result undermined the moderation process as it was occurring. It also undercut the authority as moderators of other CTC/TSC members.

Rather than delving into the details of this complaint, I will simply say that I was unaware at the time that the actions I had taken were inappropriate and had caused hurt to some CTC/TSC members involved in this matter. Having had this belatedly explained to me (again, something I have had to coax out, not offered freely to me), I issued a private statement to the TSC and CTC via email at the beginning of this month offering my sincere apologies. (I did this without knowing whether it was part of the list of complaints against me.) The most relevant part of my private statement is this:

In relation to my behaviour in the specific: I should not have weighed in so heavily, or at all, in this instance as I lacked so much of the context of what was obviously a very sensitive matter that was being already dealt with by some of you (in a very taxing way, as I understand it). I missed those signals entirely and weighed in without tact, took sides against some of you—apologising to [unnecessary details withheld] on behalf of some of you was an absurd thing for me to do without having being properly involved prior to this. And for this I unreservedly apologise!

I don't know if this apology was acknowledged during the process of dealing with the complaints against me. This apology has neither been acknowledged in the publication of the complaints handling process, nor has it seemed to have any impact on the parties involved who continue to hold it against me. I can only assume that they either dismiss my sincerity or that apologies are not a sufficient means of rectifying these kinds of missteps.

In this matter I accept responsibility and have already attempted to make amends and prevent a similar issue from recurring. It disappoints me that it is still used as an active smear against me. Again, had I been given clear feedback regarding my misstep earlier, I would have attempted to resolve this situation sooner.

2.

In nodejs/board#58 and nodejs/moderation#82 Rod did not moderate himself when asked by another foundation director and told them he would take it to the board. He also ignored the explicit requests to not name member companies and later did not moderate the names out of his comments when requested. Another TSC member needed to follow up later to actually clean up the comments. Additionally he discussed private information from the moderation repo in the public thread, which is explicitly against the moderation policy.

My response to this complaint is as follows:

  1. This thread unfortunately involves a significant amount of background corporate politics, personal relationship difficulties and other matters which conspired to raise the temperature, for me at least. This is not an excuse, simply an explanation for what may have appeared to some to be a heated interjection on my part.
  2. I did edit my post very soon after—I was the first to edit my posts in there after the quick discussion that followed in the moderation repository and I realised I had made a poor judgement call with my choice of words. I both removed my reading of intent into the words of another poster and removed the disclosure of matters discussed in a private forum.
  3. I do not recall being asked to remove the names of the companies involved, I have only now seen that they have been edited out of my post. I cannot find any evidence that such a request was even made. This would have been a trivial matter on my part and I would have done it without argument if I had have seen such a request. To find this forming the basis of a complaint is rather troubling without additional evidence.
  4. A board member asking another board member (me) to edit their postings seemed to me to be a board matter, hence my suggestion to take it to the board. I was subsequently corrected on this—as it is a TSC-owned repository it was therefore referred to the TSC for adjudication.

I considered the remaining specifics of this issue to have been resolved and have not been informed otherwise since this event took place. Yet I now find that the matters are still active and I am the target of criticism rather than that criticism being aimed at the processes that apparently resolved the matter in the first place. Why was I never informed that my part in the resolution was unsatisfactory and why was I not provided a chance to rectify additional perceived misdeeds?

3.

Most recently Rod tweeted in support of an inflammatory anti-Code-of-Conduct article. As a perceived leader in the project, it can be difficult for outsiders to separate Rod’s opinions from that of the project. Knowing the space he is participating in and the values of our community, Rod should have predicted the kind of response this tweet received. https://twitter.com/rvagg/status/887652116524707841

His tweeting of screen captures of immature responses suggests pleasure at having upset members of the JavaScript community and others. As a perceived leader, such behavior reflects poorly on the project. https://twitter.com/rvagg/status/887790865766268928

Rod’s public comments on these sorts of issues is a reason for some to avoid project participation. https://twitter.com/captainsafia/status/887782785221615618

It is evidence to others that Node.js may not be serious about its commitment to community and inclusivity. https://twitter.com/nodebotanist/status/887724138516951049

  1. The post I linked to was absolutely not an anti-Code-of-Conduct article. It was an article written by an Associate Professor of Evolutionary Psychology at the University of New Mexico, discussing free speech in general and suggesting a case against speech codes in American university campuses. In sharing this, I hoped to encourage meaningful discussion regarding the possible shortcomings of some standard Code of Conduct language. My intent was not to suggest that the Node.js project should not have a Code of Conduct in place.
  2. "Rod should have predicted the kind of response this tweet received" is a deeply normative statement. I did not predict the storm generated, and assumed that open discussion on matters of speech policing was still possible, and that my personal views would not be misconstrued as the views of the broader Node.js leadership group or community. I obviously chose the wrong forum. If TSC/CTC members are going to be held responsible for attempting to share or discuss personal views on personal channels, then that level of accountability should be applied equally across technical and Foundation leadership.
  3. "His tweeting of screen captures of immature responses suggests pleasure" is an assumption of my feelings at the time. I find this ironic especially in the context of complaint number 2 (above); I was criticised for reading the intention of another individual into their words yet that’s precisely what is being done here. This claim is absolutely untrue, I do not take pleasure in upsetting people. I will refrain from justifying my actions further on this matter but this accusation is baseless and disingenuous.
  4. To re-state for further clarity, I have not made a case against Codes of Conduct in general, but rather, would like to see ongoing discussion about how such social guidelines could be improved upon, as they clearly have impact on open source project health.
  5. I have never made a case against the Node.js Code of Conduct.
  6. I have a clear voting record for adopting the Node.js project's Code of Conduct and for various changes made to it. Codes of Conduct have been adopted by a number of my own projects which have been moved from my own GitHub account to that of the Node.js Foundation.

I will refrain from further justifying a tweet. As with all of you, I bring my own set of opinions and values to our diverse mix and we work to find an acceptable common space for us all to operate within. I don’t ask that you agree with me, but within reason I hope that mutual respect is stronger than a single disagreement. I cannot accept that my opinions on these matters form a valid reason for my removal. I have submitted myself to our Code of Conduct as a participant in this project. I have been involved in the application of our Code of Conduct. But I do not accept it as a sacred text that is above critique or even discussion.

While not a matter for the TSC or CTC, a Board member on the Foundation who (by their own admission), has repeatedly discussed sensitive and private Board matters publicly on Twitter, causing ongoing consternation and legal concern for the Board. As far as I know, this individual has not been asked to resign. I consider this type of behaviour to be considerably more problematic for the Foundation than my tweeting of a link to an article completely unrelated to Node.js.

Taking action against me on the basis of this tweet, while ignoring the many tweets and other social media posts that stand in direct conflict to the goals of the Foundation by other members of our technical team, its leadership and other members of the Foundation and its various bodies, strikes me as a deeply unequal (and, it must be said, un-inclusive) application of the rules.

If it is the case that the TSC/CTC is setting limits on personal discussion held outside the context of the project repo, then these limits should be applied to all members of both groups without prejudice.

Board accusations

In addition to the above list, we now have new claims from the Node.js Foundation board. It appears to suggest that I have and/or do engage in “antagonistic, aggressive or derogatory behavior”, with no supporting evidence provided. Presumably the supporting evidence is the list in nodejs/TSC#310 to which I have responded with above.

I can’t respond to an unsupported claim such as this, it’s presented entirely without merit and I cannot consider it anything other than malicious, self-serving, and an obvious attempt to emotionally manipulate the TSC and CTC by charging the existing claims with a completely new level of seriousness by the sprinkling of an assortment of stigmatic evil person descriptors.

To say that I am disappointed that a majority of the Board would agree to conduct themselves in such an unprofessional and immature manner is an understatement. However this is neither the time nor place for me to attempt to address their attempts to smear, defame and unperson me. After requesting of me directly that I “fall on my sword” and not receiving the answer it wanted, the Board has chosen to make it clear to where it collectively thinks the high moral ground is in this matter. As I have already expressed to them, I believe they have made a poor assessment of the facts, and have not made the correct choice on their moral stance, and have now stood by and encouraged additional smears against me.

I will have more to say on the Board’s role and our relationship to it below, however.

That I am a barrier to inclusivity efforts

This is a refrain that is often repeated on social media about me and it's never been made clear, to me at least, how this is justified.

By most objective measures, the Node.js project has been healthier and more open to outsiders during my 2-year tenure in leadership than at any time in its history. One of the great pleasures I've had during this time has been in showing and celebrating this on the conference circuit. We have record numbers of contributors overall, per month overall and unique per month. Our issue tracker is so busy with activity that very few of us can stay subscribed to the firehose any more. We span the globe such that our core and working group meetings are very difficult to schedule and usually have to end up leaving people out. We regularly have to work to overcome language and cultural barriers as we continue to expand.

When I survey the contributor base, the collaborator list, the CTC membership, I see true diversity across many dimensions. Claims that I am a barrier to inclusivity and the building of a diverse contributor base are at odds with the prominent role I've had in the project during its explosive growth.

My assessment of the claim that I am a hindrance to inclusivity efforts is that it hinges on the singular matter of moderation and control of discourse that occurs amongst the technical team. From the beginning I have strongly maintained that the technical team should retain authority over its own space. That its independence also involves its ability to enforce the rules of social interaction and discussion as it sees fit. This has lead to disagreements with individuals that would rather insert external arbiters into the moderation process; arbiters who have not earned the right to stand in judgement of technical team members, and have not been held to the same standards by which technical team members are judged to earn their place in the project.

On this matter I remain staunchly opposed to the dilution of independence of the technical team and will continue to advocate for its ability to make such critical decisions for itself. This is not only a question of moral (earned) authority, but of the risk of subversion of our organisational structures by individuals who are attracted to the project by the possibility of pursuing a personal agenda, regardless of the impact this has on the project itself. I see current moves in this direction, as in this week’s moderation policy proposal at nodejs/TSC#276, as presenting such a risk. I don't expect everyone to agree with me on this, but I have just as much right as everyone else to make my case and not be vilified in my attempts to convince enough of the TSC to prevent such changes.

Further, regarding other smears against my character that now circulate regularly on social media and GitHub. I would ask that if you are using any of these as the basis of your judgement against me, please ask for supporting evidence of those making or repeating such smears. It's been an educational experience to watch a caricatured narrative about my character grow into the monster that it is today, and it saddens me when people I respect take this narrative at face value without bothering to scratch the surface to see if there is any basis in fact.

The use of language such as “systematic” and “pattern” to avoid having to outline specifics should be seen for what they are: baseless smears. I have a large body of text involving many hundreds of social interactions scattered through the Node.js project and its various repositories on GitHub. If any such “systematic” behavioural problems exist then it should not be difficult to provide clear documentation of them.

Threats to the independence of the technical group

We now face the unprecedented move by the Node.js Foundation Board to inject itself directly in our decision-making process. The message being: the TSC voted the wrong way, they should do it again until you get the “right” outcome.

This echoes the sentiment being expressed in the Community Committee and elsewhere, that since there were accusations, there must be guilt and the fault lies in the inability of the TSC to deal with that guilt. With no credence paid to the possibility that perhaps the TSC evaluated the facts and reached a consensus that no further action was necessary.

I have some sympathy for the position of the Node.js Foundation board. These are tough times in the Silicon Valley environment, particularly with the existing concerns surrounding diversity, inclusivity, and tolerance. I can understand how rumors of similarly unacceptable behavior can pose a threat, even absent any evidence of such behavior. That said, I do not believe that it is in the long-term interests of Node.js or its Foundation to pander to angry mobs, as they represent a small fraction of our stakeholders and their demands are rarely rational. In this case, I believe that a majority of outsiders will be viewing this situation with bemusement at best. It saddens me that there is no recognition of the fact that appeasing angry and unverified demands by activists only leads to greater demands and less logical discussion of these issues. If we accept this precedent then we place the future health of this project in jeopardy, as we will have demonstrated that we allow outsiders to adjust our course to suit personal or private agendas, as long as they can concoct a story to create outrage and dispense mob justice without reproach.

While difficult, I believe that it is important for the technical team to continue to assert its independence, to the board and to other outside influences. We are not children who need adult supervision; treating us as such undermines so much of what we have built over these last few years and erodes the feelings of ownership of the project that we have instilled in our team of collaborators.

The threat to future leadership of the project

Finally, I want to address a critical problem which has been overlooked, but now poses a big problem for our future: how to grow, enable and support leadership in such a difficult environment.

My tenure in leadership easily represents the most difficult years of my life. The challenges I have had to face have forced me to grow in ways I never expected. I'm thankful for the chance to meet these challenges, however, and even though it's taken a toll on my health, I'll be glad to have had the experience when I look back.

One of my tasks as a leader, particularly serving in the role of bridge between the Board and the technical team, has involved maintaining that separation and independence but also shielding the technical team from the intense corporate and personal politics that constantly exists and is being exercised within, and around the Foundation. This role forced me to take strong positions on many issues and to stand up to pressure applied from many different directions. In doing what I felt was best to support my technical team members I’m sure I’ve put people off-side—that's an unfortunate consequence of good intentions, but not an uncommon one. I wouldn't say I've made enemies so much as had to engage in very difficult conversations and involve myself in the surfacing of many disagreements that are difficult and sometimes impossible to resolve.

Having to involve yourself in a wide variety of decision-making processes inevitably requires that you make tough calls or connect yourself in some way to controversial discussions. I'm sure our current leadership can attest to the awkward positions they have found themselves in, and the difficult conversations they have had to navigate, including this one!

I'll never pretend I don't have limitations in the skills, both intellectually and emotionally, required to navigate through these tough waters. But when I consider the sheer number of dramas, controversies, and difficult conversations I've had to be involved in—and when I consider the thousands of pages of text I have left littered across GitHub and the other forums we use to get things done—I come to this conclusion: If the best reason you can find force my resignation is the above list of infractions, given the weight of content you could dredge through, then you're either not trying very hard or I should be pretty proud of myself for keeping a more level head than I had imagined.

That aside, my greatest concern for the role of leadership coming as a consequence of the actions currently being pursued, is that we've painted ourselves into a corner regarding the leaders we're going to have available. The message that the Board has chosen to send today can be rightly interpreted as this: if the mob comes calling, if the narrative of evil is strong enough, regardless of the objective facts, the Foundation does not have your back. As developers and leaders, the Foundation is signalling that they will not stand up for us when things get tough. Combine this with a difficult and thankless job, where the result of exercising your duties could be career-killing, the only path forward for leadership is that we will likely only have:

  • Individuals who are comfortable giving in to the whims of the outside activists, whatever the demands, slowly transforming this project into something entirely different and focused on matters not associated with making Node.js a success
  • Individuals who are capable but shrewd enough to avoid responsibility
  • Individuals who are capable and take on responsibility, exercise backbone when standing against pressure groups and mob tactics but get taken down because the support structures either abandon them or turn against them

This kind of pattern is being evidenced across the professionalised open source sphere, with Node.js about to set a new low bar. Do not be surprised as quality leaders become more difficult to find or become unconvinced that the exercise of leadership duties is at all in their personal interest.

This is a great challenge for modern open source and I'm so sad that I am being forced to be involved in the setting of our current trajectory. I hope we can find space in the future to have the necessary dialog to find a way out of the hole being dug.

In summary

Obviously I hope that you agree that (a) this action against me is unwarranted, is based on flawed and/or irrelevant claims of “misbehaviour” and is based in malicious intent, and that (b) allowing this course of action to be an acceptable part of our governance procedures will have detrimental consequences for the future health of the project.

I ask the CTC to reject this motion, for the TSC to reject the demand by the Board for my suspension, and that we as a technical team send a signal that our independence is critical to the success of the project, despite the accusations of an angry mob.

Thank you if you dignified my words by reading this far!

Why I don't use Node's core 'stream' module

This article was originally offered to nearForm for publishing and appeared for some time on their blog from early 2014 (at this URL: http://www.nearform.com/nodecrunch/dont-use-nodes-core-stream-module). It has since been deleted. I'd rather not speculate about the reasons for the deletion but I believe the article contains a very important core message so I'm now republishing it here.

TL;DR

The "readable-stream" package available in npm is a mirror of the Streams2 and Streams3 implementations in Node-core. You can guarantee a stable streams base, regardless of what version of Node you are using, if you only use "readable-stream".

The good 'ol days

Prior to Node 0.10, implementing a stream meant extending the core Stream object. This object was simply an EventEmitter that added a special pipe() method to do the streaming magic.

Implementing a stream usually started with something like this:

var Stream = require('stream').Stream
var util = require('util')

function MyStream () {
  Stream.call(this)
}

util.inherits(MyStream, Stream)

// stream logic, implemented however you want

If you ever had to write a non-trivial stream implementation for pre-Node 0.10 without using a helper library (such as through), you know what a nightmare the state-management it can be. The actual implementation of a custom stream is a lot more than just the above code.

Welcome to Node 0.10

Thankfully, Streams2 came along with a brand new set of base Stream implementations that do a whole lot more than pipe(). The biggest win for stream implementers comes from the fact that state-management is almost entirely taken care of for you. You simply need to provide concrete implementations of some abstract methods to make a fully functional stream, even for non-trivial workloads.

Implementing a stream now looks something like this:

var Readable = require('stream').Readable
// `Stream` is still provided for backward-compatibility
// Use `Writable`, `Duplex` and `Transform` where required
var util = require('util')

function MyStream () {
  Readable.call(this, { /* options, maybe `objectMode:true` */ })
}

util.inherits(MyStream, Readable)

// stream logic, implemented mainly by providing concrete method implementations:

MyStream.prototype._read = function (size) {
  // ... 
}

State-management is handled by the base-object and you interact with internal methods, such as this.push(chunk) in the case of a Readable stream.

While the internal streams implementations are an order-of-magnitude more complex than the previous core-streams implementation, most of it is there to make life an order-of-magnitude easier for those of us implementing custom streams. Yay!

Backward-compatibility

When every new major stable release of Node occurs, anyone releasing public packages in npm has to make a decision about which versions of Node they support. As a general rule, the authors of the most popular packages in npm will support the current stable version of Node and the previous stable release.

Streams2 was designed with backwards-compatibility in mind. Streams using require('stream').Stream as a base will still mostly work as you'd expect and they will also work when piped to streams that extend the other classes. Streams2 streams won't work like classic EventEmitter objects when you pipe them together, as old-style streams do. But when you pipe a Streams2 stream and an old-style EventEmitter-based stream together, Streams2 will fall-back to "compatibility-mode" and operate in a backward-compatible way.

So Streams2 are great and mostly backward-compatible (aside from some tricky edge cases). But what about when you want to implement Streams2 and run on Node 0.8? And what about open source packages in npm that want to still offer Node 0.8 compatibility while embracing the new Streams2-goodness?

"readable-stream" to the rescue

During the 0.9 development phase, prior to the 0.10 release, Isaac developed the new Streams2 implementation in a package that was released in npm and usable on older versions of Node. The readable-stream package is essentially a mirror of the streams implementation of Node-core but is available in npm. This is a pattern we will hopefully be seeing more of as we march towards Node 1.0. Already there is a core-util-is package that makes available the shiny new is type-checking functions in the 0.11 core 'util' package.

readable-stream gives us the ability to use Streams2 on versions of Node that don't even have Streams2 in core. So a common pattern for supporting older versions of Node while still being able to hop on the Streams2-bandwagon starts off something like this, assuming you have "readable-stream" as a dependency:

var Readable = require('stream').Readable || require('readable-stream').Readable

This works because there is no Readable object on the core 'stream' package in 0.8 and prior, so if you are running on an older version of Node it skips straight to the "readable-stream" package to get the required implementation.

Streams3: a new flavour

The readable-stream package is still being used to track the changes to streams coming in 0.12. The upcoming Streams3 implementation is more of a tweak than a major change. It contains an attempt to make "compatibility mode" more of a first-class citizen of the API and also some improvements to pause/resume behaviour.

Like Streams2, the aim with Streams3 is for backward (and forward) compatibility but there are limits to what can be achieved on this front.

While this new streams implementation will likely be an improvement over the current Streams2 implementation, it is part of the unstable development branch of Node and is so far not without its edge cases which can break code designed against the pure 0.10 versions of Streams2.

What is your base implementation?

Looking back at the code used to fetch the base Streams2 implementation for building custom streams, let's consider what we're actually getting with different versions of Node:

var Readable = require('stream').Readable || require('readable-stream').Readable
  • Node 0.8 and prior: we get whatever is provided by the readable-stream package in our dependencies.
  • Node 0.10: we get the particular version of Streams2 that comes with the version of Node we're using.
  • Node 0.11: we get the particular version of Streams3 that comes with the version of Node we're using.

This may not be interesting if you have full control over all deployments of your custom stream implementations and which version(s) of Node they will be used on. But it can cause some problems in the case of open source libraries distributed via npm with users still stuck on 0.8 (for some, the upgrade path is not an easy one for various reasons), 0.10 and even people trying out some of the new Node and V8 features available in 0.11.

What you end up with is a very unstable base upon which to build your streams implementation. This is particularly acute since the vast bulk of the code used to construct the stream logic is coming from either Node-core or the readable-stream package. Any bugs fixed in later Node 0.10 releases will obviously still be present for people still stuck on earlier 0.10 releases even if the readable-stream dependency has the fixed version.

Then, when your streams code is run on Node 0.11, suddenly it's a Streams3 stream which has slightly different behaviour to what most of your users are experiencing.

One of the ways these subtle differences are exposed is in bug reports. Users may report a bug that only occurs on their particular combination of core-streams and readable-stream and it may not be obvious that the problem is related to base-stream implementation edge-cases they are stumbling upon; wasting time for everyone.

And what about stability? The fragmentation introduced by all of the possible combinations means that your otherwise stable library is having instability foisted upon it from the outside. This is one of the costs of relying on a featureful standard-library (core) within a rapidly developing, pre-v1 platform. But we can do something about it by taking control of the exact version of the base streams objects we want to extend regardless of what is bundled in the version of Node being used. readable-stream to the rescue!

Taking control

To control exactly what code your streams implementation is building on, simply pin the version of readable-stream and use only it, avoiding require('stream') completely. Then you get to make the choice when to upgrade to Streams3, even if that's some time after Node 0.12.

readable-stream comes in two major versions, v1.0.x and v1.1.x. The former tracks the Streams2 implementation in Node 0.10, including bug-fixes and minor improvements as they are added. The latter tracks Streams3 as it develops in Node 0.11; we may see a v1.2.x branch for Node 0.12.

Any library worth using should be following the basics of semver minor and patch versions (the merits and finer points of major versioning are still something worth debating). readable-stream gives you proper patch-level versioning so if you pin to "~1.0.0" you'll get the latest Node 0.10 Streams2 implementation, including any fixes and minor non-breaking improvements. The patch-level version of 1.0.x and 1.1.x should mirror the patch-level versions of Node core releases as we proceed.

When you're ready to start using Streams3 you can pin to "~1.1.0", but you should hold off until much closer to Node 0.12, if not after its formal release.

Small core FTW!

Being able to control precisely the versions of dependencies your code uses reduces the scope for bugs introduced by version incompatibilities or new and unproven implementations.

When we rely on a bulky standard-library to build our libraries and applications, we're relying on a shifting sand that we have little control over. This is particularly a problem for open source libraries whose users have legitimate (and sometimes not-so-legitimate) reasons for using versions that you'd rather not have to support.

Streams2 is a powerful abstraction, but the implementation is far from simple. The Streams2 code is some of the most complex JavaScript you'll find in Node core. Unless you want to have a detailed understanding of how they work and be able to track the changes as they develop, you should pin your Streams2 dependency in the same way as you pin all your other dependencies. Opt for readable-stream over what Node-core offers:

{
  "name": "mystream",
  ...
  "dependencies": {
    "readable-stream": "~1.0.0"
  }
}
var Readable = require('readable-stream').Readable
var util = require('util')

function MyStream () {
  Readable.call(this)
}

util.inherits(MyStream, Readable)

MyStream.prototype._read = function (size) {
  // ... 
}

Addendum: "through2"

If the boilerplate of the Streams2 base objects ("classes") is too much for you or triggers some past-life Java PTSD, you can just opt for the "through2" package in npm to get the job done.

through2 is based on Dominic Tarr's through but is built for Streams2, whereas "through" is a pure Streams1 style. The API isn't quite the same but the flexibility and simplicity is.

through2 gives you a DuplexStream as a base to implement any kind of stream you like, be it as purely readable, purely writable or a fully duplex stream. In fact, you can even use through2 to implement a PassThrough stream by not providing an implementation!

From the examples:

var through2 = require('through2')

fs.createReadStream('ex.txt')
  .pipe(through2(function (chunk, enc, callback) {

    for (var i = 0; i < chunk.length; i++)
      if (chunk[i] == 97)
        chunk[i] = 122 // swap 'a' for 'z'

    this.push(chunk)

    callback()

   }))
  .pipe(fs.createWriteStream('out.txt'))

Or an object stream:

fs.createReadStream('data.csv')
  .pipe(csv2())
  .pipe(through2.obj(function (chunk, enc, callback) {

    var data = {
        name    : chunk[0]
      , address : chunk[3]
      , phone   : chunk[10]
    }

    this.push(data)

    callback()

  }))
  .on('data', function (data) {
    all.push(data)
  })
  .on('end', function () {
    doSomethingSpecial(all)
  })