<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  
  <title>Yoav Vainrich</title>
  <subtitle>Notes on Node.js, dependencies, and things that broke.</subtitle>
  <link href="https://yoavain.github.io/feed.xml" rel="self" />
  <link href="https://yoavain.github.io/" />
  <updated>2026-09-05T00:00:00Z</updated>
  <id>https://yoavain.github.io/</id>
  <author>
    <name>Yoav Vainrich</name>
  </author>
  <entry>
    <title>The Weakest Link</title>
    <link href="https://yoavain.github.io/blog/the-weakest-link/" />
    <updated>2026-09-05T00:00:00Z</updated>
    <id>https://yoavain.github.io/blog/the-weakest-link/</id>
    <content type="html">&lt;p&gt;They say people are the weakest link in cybersecurity. If that is true, I would
nominate time as the second.&lt;/p&gt;
&lt;p&gt;I can offer evidence. What follows happened in January 2022. I drafted it that
same week, and then did not publish it for nearly five years, because there was
always something with a deadline sitting in front of it. The post lost to the
exact shortage it is about.&lt;/p&gt;
&lt;p&gt;Some of the details have aged. Node.js has since made unhandled rejections fatal
by default, which is to say that the behaviour I spend this post objecting to
is now the behaviour you get for free. What has not aged is how it was forced on me.&lt;/p&gt;
&lt;p&gt;With unlimited time, I would write far more of my own code and lean on far fewer
third-party packages. For the packages I did adopt, I would read them end to end
before shipping them.&lt;/p&gt;
&lt;p&gt;Nobody has unlimited time. So we cut corners. We install a lot of third-party
code, directly and transitively. We test it, of course: we check that it does
the job, sometimes that it is fast enough, occasionally we compare two or three
candidates before picking one.&lt;/p&gt;
&lt;p&gt;And then we move on, and what we are left with is a black box.&lt;/p&gt;
&lt;p&gt;We expect two things from that box:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;That it does the job we installed it for.&lt;/li&gt;
&lt;li&gt;That it stays out of the way when it is not doing that job. In other words,
that it has no side effects.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We are reasonably good at testing the first expectation. We almost never test
the second. That asymmetry matters, because when a package misbehaves inside the
flow that uses it, the package is an obvious suspect. When it misbehaves
somewhere else entirely, you are in for a long day.&lt;/p&gt;
&lt;p&gt;This is the story of one of those days.&lt;/p&gt;
&lt;h2&gt;The setup&lt;/h2&gt;
&lt;p&gt;The system was a PERN stack: PostgreSQL, Express, React, Node.js. It was started
back when callbacks were still more popular than promises, before anyone on the
team had said the words &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; out loud, and before TypeScript was part
of our toolchain. It had been running in production for years. We considered it
boring, in the good way.&lt;/p&gt;
&lt;p&gt;Then one afternoon I made a typo while calling a REST API on my local machine,
and the server process died. The log blamed an unhandled promise rejection.&lt;/p&gt;
&lt;p&gt;That should not have happened. We were on Node.js 14, where an unhandled
rejection produces a warning, not a fatal error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(node:26404) UnhandledPromiseRejectionWarning: This is an unhandled exception
(node:26404) [DEP0018] DeprecationWarning: Unhandled promise rejections are
deprecated. In the future, promise rejections that are not handled will
terminate the Node.js process with a non-zero exit code.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I knew that warning well. It was a problem for future me. Apparently future me
had arrived early.&lt;/p&gt;
&lt;h2&gt;The dig&lt;/h2&gt;
&lt;p&gt;The stack trace pointed at a package that had nothing to do with the endpoint I
had just broken:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RuntimeError: abort(This is an unhandled exception).
    at process.abort (...&#92;node_modules&#92;wawoff2&#92;build&#92;compress_binding.js:1:10773)
    at process.emit (events.js:412:35)
    at processPromiseRejections (internal/process/promises.js:245:33)
    at processTicksAndRejections (internal/process/task_queues.js:96:32)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/wawoff2&quot;&gt;&lt;code&gt;wawoff2&lt;/code&gt;&lt;/a&gt; is a WOFF2 font compressor. It
was in the dependency tree, and it was required somewhere at startup, but it was
categorically not in use anywhere near my REST call. So what was in it that could
kill my server?&lt;/p&gt;
&lt;p&gt;The file was minified. I reformatted it, went looking, and found this:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;process&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;on&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;unhandledRejection&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;reason&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; reason&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Globals. You have to love them.&lt;/p&gt;
&lt;p&gt;Node emits an &lt;a href=&quot;https://nodejs.org/api/process.html#event-unhandledrejection&quot;&gt;&lt;code&gt;unhandledRejection&lt;/code&gt;&lt;/a&gt;
event whenever a promise is rejected with no handler attached within a turn of
the event loop. The event exists so that you can observe those rejections: log
them, count them, alert on them. The handler above does not observe anything. It
rethrows the rejection reason into the main flow as an uncaught exception, which
terminates the process.&lt;/p&gt;
&lt;p&gt;Nothing about the package&#39;s documented job, compressing fonts, requires that.
Merely importing the module was enough to change a process-wide default for
every other line of code in the application.&lt;/p&gt;
&lt;p&gt;Here is the whole thing in fifteen lines:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;ms&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ms&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;unhandledRejectionHere&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;This is an unhandled rejection&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unhandledRejectionHere&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Survived: before require&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;wawoff2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;unhandledRejectionHere&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Survived: after require&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// never runs&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first rejection warns. The second one kills the process. The only difference
between them is an import.&lt;/p&gt;
&lt;h2&gt;First stop: the co-worker&lt;/h2&gt;
&lt;p&gt;I went to the colleague who had added the package, and started bargaining:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Is this package already in active use in production?&lt;/li&gt;
&lt;li&gt;When you added it, did you evaluate any alternatives that do the same job?&lt;/li&gt;
&lt;li&gt;What would it take to run this code in a child process?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Question three is the honest fallback. If you cannot trust a dependency to leave
the process alone, put it in a process you are willing to lose. But it is a
workaround, and workarounds have a way of quietly becoming architecture.&lt;/p&gt;
&lt;h2&gt;Second stop: upstream&lt;/h2&gt;
&lt;p&gt;With the low expectations that the open-source ecosystem has trained into all of
us, I opened the package&#39;s npm page. Eighteen thousand weekly downloads. A real
user base. Last published ten months earlier. Ouch.&lt;/p&gt;
&lt;p&gt;(Those were the 2022 numbers. I checked again before finally publishing this:
it is now a little over 534,000 downloads a week.)&lt;/p&gt;
&lt;p&gt;I checked the documentation for any hint that this was intentional. Nothing.&lt;/p&gt;
&lt;p&gt;So on 19 January 2022, at 10:54 UTC, I opened
&lt;a href=&quot;https://github.com/fontello/wawoff2/issues/9&quot;&gt;an issue&lt;/a&gt; with a clear title, a
fifteen-line reproduction, the expected output, and the actual output.&lt;/p&gt;
&lt;p&gt;Sixteen minutes later, the maintainer, &lt;a href=&quot;https://github.com/puzrin&quot;&gt;Vitaly Puzrin&lt;/a&gt;,
replied. He explained that the offending code is generated by
&lt;a href=&quot;https://emscripten.org/&quot;&gt;Emscripten&lt;/a&gt;, not written by hand, and offered three
hypotheses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It was already fixed upstream in Emscripten and only needed a rebuild.&lt;/li&gt;
&lt;li&gt;The build options were wrong or missing.&lt;/li&gt;
&lt;li&gt;It was an Emscripten bug and belonged in their tracker.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;He was candid that he was busy and did not keep Emscripten&#39;s flags in his head,
and asked whether I had time to localise the problem, noting that the build was
containerised and, in his words, &amp;quot;as friction-less as possible.&amp;quot;&lt;/p&gt;
&lt;p&gt;Three minutes after that, a second reply, linking a related Emscripten issue
involving &lt;code&gt;uncaughtException&lt;/code&gt; rather than &lt;code&gt;unhandledRejection&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Two substantive replies in twenty minutes, on a package he had not touched in
ten months. That is commitment. Challenge accepted.&lt;/p&gt;
&lt;h2&gt;Third stop: the build&lt;/h2&gt;
&lt;p&gt;Some searching turned up
&lt;a href=&quot;https://github.com/emscripten-core/emscripten/pull/9061&quot;&gt;emscripten-core/emscripten#9061&lt;/a&gt;,
which had made the rejection handler opt-in behind a setting. My first read was
that a newer Emscripten would fix this for free. Vitaly had already pushed a
&lt;code&gt;dev&lt;/code&gt; branch built with a newer version; I tested the artifacts, and the handler
was still there. The setting had to be turned off explicitly, not merely left
alone.&lt;/p&gt;
&lt;p&gt;So I forked the repo, built it, adjusted the scripts from Linux to Windows, and
built again. Vitaly was right about the build being frictionless. He also passed
along a good debugging trick: drop &lt;code&gt;-s SINGLE_FILE=1&lt;/code&gt; and add &lt;code&gt;-g2&lt;/code&gt;, and the
generated wrappers come out unminified, so you can diff one build against the
next.&lt;/p&gt;
&lt;p&gt;Two flags turned out to matter:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Removes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-s NODEJS_CATCH_REJECTION=0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;unhandledRejection&lt;/code&gt; handler that rethrows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-s NODEJS_CATCH_EXIT=0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the matching &lt;code&gt;uncaughtException&lt;/code&gt; handler&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A third candidate, &lt;code&gt;-s ENVIRONMENT=node&lt;/code&gt;, produced tighter output by hardcoding
the environment checks:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// before&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ENVIRONMENT_IS_WEB&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ENVIRONMENT_IS_NODE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; process &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;object&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// after&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ENVIRONMENT_IS_WEB&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ENVIRONMENT_IS_NODE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That would have restricted the module to Node and broken it in browsers, so I
left it out. Fixing your own problem is not a licence to break someone else&#39;s.&lt;/p&gt;
&lt;p&gt;The change came down to one line in &lt;code&gt;src/Makefile&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-make&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-make&quot;&gt;CARGS=--bind -s NODEJS_CATCH_REJECTION=0 -s NODEJS_CATCH_EXIT=0 -s ALLOW_MEMORY_GROWTH=1 -s SINGLE_FILE=1 -O3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I opened &lt;a href=&quot;https://github.com/fontello/wawoff2/pull/10&quot;&gt;a pull request&lt;/a&gt; at 16:03.
It was merged at 16:16. I thanked Vitaly and asked whether he could publish a
release, without which none of it would have reached anyone. He had already done
it. Version 2.0.1 went out at 16:24.&lt;/p&gt;
&lt;p&gt;Issue opened to fixed version on npm: &lt;strong&gt;five hours and twenty-nine minutes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That is still the newest release. Nothing has been published since, which means
every one of those half a million weekly downloads carries the fix.&lt;/p&gt;
&lt;h2&gt;What I took away from it&lt;/h2&gt;
&lt;p&gt;The easy conclusion is &amp;quot;be careful with open source,&amp;quot; which is advice nobody can
act on. Here is what I actually changed my mind about.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A dependency&#39;s blast radius is not its API surface.&lt;/strong&gt; I had been reasoning
about packages in terms of the functions I call. But an import runs code, and
code at import time can reach anything process-global: signal handlers,
&lt;code&gt;process&lt;/code&gt; listeners, prototypes, &lt;code&gt;Error.prepareStackTrace&lt;/code&gt;, environment
variables. The API is the part you agreed to. The import is the part you got.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;No side effects&amp;quot; is a property you test for, not assume.&lt;/strong&gt; We verify that a
package does its job. Almost nobody asserts that importing it changes nothing
else. That test is cheap. Snapshot the listener counts on &lt;code&gt;process&lt;/code&gt; before and
after the import, and fail if they moved. It is the test that would have caught
this on day one instead of years in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generated code deserves more suspicion, not less.&lt;/strong&gt; Nobody at Fontello wrote
that handler. Emscripten did, and its defaults shifted underneath a build that
had not been re-run in ten months. Compiled and transpiled artifacts carry their
build environment&#39;s opinions along with them. That covers wasm glue, bundled
binaries, and anything with a toolchain between the source and the
&lt;code&gt;node_modules&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maintainer responsiveness is a selection criterion.&lt;/strong&gt; We compare packages on
downloads, bundle size, and benchmarks. The variable that decided this outcome
was that a busy maintainer answered in sixteen minutes and shipped the same
afternoon. You can estimate that before you adopt a package: open the issue
tracker, see how old the open issues are and how the closed ones ended.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Report it upstream.&lt;/strong&gt; Vendoring a patch or shelling out to a child process
would have unblocked me in an afternoon and left eighteen thousand other weekly
downloads exposed. Filing the issue cost me one day and fixed it for everyone,
permanently. That trade is usually better than it looks from inside a sprint.&lt;/p&gt;
&lt;p&gt;So: do not give up on open source. It took one afternoon, two flags, and a
maintainer who cared, and the fix is now in everyone&#39;s &lt;code&gt;node_modules&lt;/code&gt;. That is
the system working exactly as intended.&lt;/p&gt;
&lt;p&gt;Just remember that every install is a decision to run someone else&#39;s code in your
process, and that the chain is only as strong as its weakest link.&lt;/p&gt;
&lt;p&gt;Vitaly went from a stranger&#39;s bug report to a published fix in five and a half
hours. I went from a finished draft to a published post in nearly five years.
People may be the weakest link. Time is right behind them, and it is undefeated.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The full exchange is public: &lt;a href=&quot;https://github.com/fontello/wawoff2/issues/9&quot;&gt;fontello/wawoff2#9&lt;/a&gt;.
Thanks to &lt;a href=&quot;https://github.com/puzrin&quot;&gt;Vitaly Puzrin&lt;/a&gt; for the fastest turnaround I
have ever had on a bug report.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
</feed>