
Our Software Dependency Problem - dmit
https://research.swtch.com/deps
======
tc
The security of package managers is something we're going to have to fix.

Some years ago, in offices, computers were routinely infected or made unusable
because the staff were downloading and installing random screen savers from
the internet. The IT staff would have to go around and scold people not to do
this.

If you've looked at the transitive dependency graphs of modern packages, it's
hard to not feel we're doing the same thing.

In the linked piece, Russ Cox notes that the cost of adding a bad dependency
is the sum of the cost of each possible bad outcome times its probability. But
then he speculates that for personal projects that cost may be near zero.
That's unlikely. Unless developers entirely sandbox projects with untrusted
dependencies from their personal data, company data, email, credentials,
SSH/PGP keys, cryptocurrency wallets, etc., the cost of a bad outcome is still
enormous. Even multiplied by a small probability, it has to be considered.

As dependency graphs get deeper, this probability, however small, only
increases.

One effect of lower-cost dependencies that Russ Cox did not mention is the
increasing tendency for a project's transitive dependencies to contain two or
more libraries that do the same thing. When dependencies were more expensive
and consequently larger, there was more pressure for an ecosystem to settle on
one package for a task. Now there might be a dozen popular packages for fancy
error handling and your direct and transitive dependencies might have picked
any set of them. This further multiplies the task of reviewing all of the code
important to your program.

Linux distributions had to deal with this problem of trust long ago. It's
instructive to see how much more careful they were about it. Becoming a Debian
Developer involves a lengthy process of showing commitment to their values and
requires meeting another member in person to show identification to be added
to their cryptographic web of trust. Of course, the distributions are at the
end of the day distributing software written by others, and this explosion of
dependencies makes it increasingly difficult for package maintainers to
provide effective review. And of course, the hassles of getting a library
accepted into distributions is one reason for the popularity of tools such as
Cargo, NPM, CPAN, etc.

It seems that package managers, like web browsers before them, are going to
have to provide some form of sandboxing. The problem is the same. We're
downloading heaps of untrusted code from the internet.

~~~
faissaloo
This right here is why Go's 'statically link everything' is going to become a
big problem in the long run when old servers are running that software and no
one has the source code anymore.

~~~
stcredzero
Given the ease with which the parser and AST are made available to developers,
we should be able to implement tools which can detect naughty packages. Also,
given the speed at which projects can be compiled, the impetus to keep the
source code should remain strong.

~~~
viraptor
[https://en.m.wikipedia.org/wiki/List_of_tools_for_static_cod...](https://en.m.wikipedia.org/wiki/List_of_tools_for_static_code_analysis)

They're either going to miss things or have false positives. They sure improve
the situation, but you can't find all of the issues automatically.

~~~
stcredzero
Granted. But it will at least raise the bar for building an exploit package
from "knows how to code" to "knows how to code, knows something about
exploits, and knows how to avoid detection by an automated scanner."

------
smacktoward
It's an interesting line of inquiry to think about how many of these
evaluation heuristics, which are all described as things a person can do
manually, could instead be built into the package manager itself to do for you
automatically.

The package manager could run the package's test suite, for instance, and warn
you if the tests don't all pass, or make you jump through extra hoops to
install a package that doesn't have any test coverage at all. The package
manager could read the source code and tell you how idiomatically it was
written. The package manager could try compiling from source with warnings on
and let you know if any are thrown, and compare the compiled artifacts with
the ones that ship with the package to ensure that they're identical. The
package manager could check the project's commit history and warn you if
you're installing a package that's no longer actively maintained. The package
manager could check whether the package has a history of entries in the
National Vulnerability Database. The package manager could learn what licenses
you will and won't accept, and automatically filter out packages that don't
fit your policies. And so on.

In other words, the problem right now is that package managers are
undiscriminating. To them a package is a package is a package; the universe of
packages is a flat plane where all packages are treated equally. But in
reality all packages _aren 't_ equal. Some packages are good and others are
bad, and it would be a great help to the user if the package manager could
encourage discovery and reuse of the former while discouraging discovery and
reuse of the latter. By taking away a little friction in some places and
adding some in others, the package manager could make it easy to install good
packages and hard to install bad ones.

~~~
DoctorOetker
those are really good ideas!

a vague additional idea:

can we improve rough assessment of code quality?

1) suppose we have pseudonym reputation ("error notice probability"): anyone
can create a pseudonym, and start auditing code, and you mark the parts of
code that you have inspected. those marks are publicly associated with your
pseudonym (after enough operation and eventual finding of bugs by others, the
"noticing probability" can be computed+).

2) consider the birthday paradox, i.e. drawing samples from the uniform
distribution will result in uncoordinated attention, while with coordinated
attention we can spread attention more uniformly...

\+ of course theres different kinds of issues, i.e. new features, arguments
about wheiter something is an improvement or if it was an oversighted issue
etc... but the future patch types don't necessarily correlate to the
individuals who inspected it...

ALSO: I still believe formal verification is actually counterintuitively
cheaper (money _and time_ ) and less effort per achieved certainty. But as
long as most people refuse to believe this, I encourage strategies like
these...

~~~
EvilTerran
There's some relevant work going on in the "crev" project, discussed here a
couple of weeks ago:

[https://news.ycombinator.com/item?id=18824923](https://news.ycombinator.com/item?id=18824923)

The big idea is for people to publish cryptographically signed "proofs" that
they've reviewed a particular version of a given module, allowing a web-of-
trust structure for decentralised code review. I particularly like how, thanks
to the signatures, a module's author can distribute reviews alongside the
module without compromising their trustworthiness - so there's an incentive
for authors to actively seek out reviewers to scrutinise their code.

------
peteforde
This paper lends significant legitimacy to a casual observation that I've been
concerned about for a long time: as the standard for what deserves to be a
module gets ever-lowered, the law of diminishing returns kicks in really hard.

The package managers for Ruby, C#, Perl, Python etc offer ~100k modules. This
offers strong evidence that most developer ecosystems produce (and
occasionally maintain) a predictable number of useful Things. If npm has 750k+
modules available, that suggests that the standard for what constitutes a
valuable quantity of functionality is 7.5X lower in the JS community. Given
that every dependency increases your potential for multi-dimensional technical
risk, this seems like it should be cause for reflection. It's not an abstract
risk, either... as anyone who used left-pad circa 2016 can attest.

When I create a new Rails 5.2 app, the dependency tree is 70 gems and most of
them are stable to mature. When I create-react-app and see that there's 1014
items in node_modules, I have no idea what most of them actually do. And let's
not forget: that's just the View layer of your fancy JS app.

~~~
jrochkind1
When I create a new rails 5.2.2 app, I see 79 dependencies in entire tree.
Which is about what you said, and a lot less than 1014, sure.

There are various reasons other than "low standards" that the JS ecosystem has
developed to encourage even more massive dependency trees.

One is that JS projects like this are delivered to the browser. If I want one
function from, say, underscore (remember that?), but depend on all of it... do
i end up shipping all of it to the browser to use one function? That would be
unfortunate. Newer tools mean not necessarily, but it can be tricky, and some
of this culture developed before those tools.

But from this can develop a community culture of why _shouldn't_ I minimize
the weight of dependencies? If some people only want one function and others
only another, shouldn't they be separate dependencies so they can do that? And
not expose themselves to possible bugs or security problems in all that other
code they don't want? If dependencies can be dangeorus... isn't it better to
have _surgical_ dependencies including only exactly what you need so you can
take less of them? (Makes sense at first, but of course when you have 1000 of
those "surgical" dependencies, it kind of breaks down).

Another, like someone else said, is that JS in the browser has very little
stdlib/built in functions.

Another is tooling. The dependency trees were getting unmanageable in ruby
before bundler was created (which inspired advanced dependency management
features in most of the rest subsequent). We probably couldn't have as many
dependencies as even Rails has without bundler. Your dependency complexity is
limited by tooling support; but then when tooling support comes, it gives you
a whole new level of dependency management problems that come with the crazy
things the tooling let you do.

These things all feed back on each other back and forth.

I'm not saying it isn't giving rise to very real problems. But it's not just
an issue of people having low standards or something.

~~~
peteforde
You are correct, sir: 79 dependencies. Need more coffee!

The influence of Bundler is just another example of Yehuda Katz doing
something initially perceived as unpopular having a massive long-term impact
on developer ecosystems. He is the Erdős of the web dev world. I wish he would
sell options on his future endeavours like David Bowie did.

Anyhow, in trying to keep my initial comment relatively brief, I held back on
several points; what I really and truly don't get about the 750k thing is how
anyone can track the libraries available. You know... with their brains. In
Rails, gems are big ideas: authentication, an ORM, the ability to address all
of AWS, send tweets. The idea that someone would even think to publish a
"left-pad" (which I understand is just missing from the also-missing stdlib)
as something people should import is what seems crazy. What makes you stop and
think... wait, I'm typing too many characters, I need to see if there's
anything on Github that can insert spaces at the beginning of a string. How
would you even know what it's called? Is there a module for concatenating two
strings? When does it become silly?

How is it possible that finding a library to add padding which may or may not
exist, doing even cursory code review and integrating it not take longer than
just writing a few lines of code?

Using the example of the escape-string-regexp module mentioned in the
whitepaper... this would be a deeply flawed thing to add to your project. It
has a hard-coded error message that has zero affordances for localization
strategies. It is, at best, a few random lines from someone's hobby app.

If every one of those 1014 modules in an empty project have weird, unknown-
unknown failure modes, that sounds like a recipe for trouble.

Finally, 1014 packages getting frequently version bumped is way less reliable
in terms of unfortunate conflicts. Your surface area of all things that can go
wrong shoots up and to the right... all to save a few lines of code?

~~~
IggleSniggle
I don’t really disagree with anything you’ve said, just want to add.

It’s not really 750k packages you’re keeping in your head. Instead, there are
swirling communities of standard convention. This happens because the js
community is so large and the code syntax so “forgiving” that many different
“dialects” of js exist. There’s a reason “Babel” is called “Babel.” Within
your dialect, one package may be better suited than another package, which
does the same thing. A great example of this is where ‘_ => result’ may look
perfectly coherent in one sub-ecosystem, another sub-ecosystem would more
readily understand ‘function AsyncCalculationProvider(callback:
CompletionFunction) { return callback }’, both for perfectly legitimate
reasons. That people wish to ‘standardize’ on one common way of doing a thing
within their sub-ecosystem is the reason people accept dependencies for dumb
little things. That people disagree on how those things should manifest in the
context of their subculture is the reason there’s 10 different popular ways to
do the same exact little thing.

This isn’t even just ECMAScripts fault. The Web APIs provide a lot of great
fill-in as a standard library (for example, for Date localization), but even
here we do not see consistency. For example, the “fetch” api for browsers is
nice enough, but is not implemented in Nodejs. If you want to share a
signature for HTML requests between nodejs and browsers, you’re going to use
“axios” or maybe “node-fetch” (which replicates the Web api but is more
verbose), or you’re going to end up rewriting a wrapper for that
functionality, and why would you do that when there’s a community that will
immediately see and understand what you mean when you import the “axios”
module?

Big optional standard library needed, but minimized deliverable also needed.
Recent tooling (eg tree-shaking and Babel), make this more reasonable today
than it was in the past.

~~~
peteforde
If historical lessons continue to apply, what will likely happen is a few more
significant iterations of shakeout as the community coalesces around something
closer to consensus. It's important to remember that the vast majority of
developers in any ecosystem are simply trying to use a tool to do a job; they
are far less likely to commit a significant amount to the ecosystem and that's
okay. Wikipedia is very similar in this regards.

Perhaps the best comparison is the evolution of the Linux distribution
ecosystem. Maybe React is Ubuntu? The story is still being written.

------
Sir_Cmpwn
>Is the code well-written? Read some of it. Does it look like the authors have
been careful, conscientious, and consistent? Does it look like code you’d want
to debug? You may need to.

This, 10,000x. I've repeated a similar mantra many, many times, and it's one
of the most important reasons I refuse to use proprietary software. You should
consider no software a black box, and consider the software you chose to use
carefully, because it's your responsibility to keep it in good working order.

~~~
bunderbunder
Making it someone else's responsibility to keep it in good working order is
the value proposition behind (good) proprietary software: You give them money,
they give you a support contract.

For a company with more money than development resources, or even just a
company whose development resources can be more profitably focused elsewhere,
this can be a quite reasonable trade to make.

~~~
vharuck
If a company behind proprietary software goes belly up, there's no support.
But there are always companies or even freelance devs who can be paid to
support open source code.

~~~
IggleSniggle
Proprietary _and closed source_. We make use of open source (but not open
license) proprietary software on my team.

------
raphlinus
My personal sense, from watching developments in this space, is that we are
going to have to find some way for taking on an open source dependency to be
an economic transaction, with money actually changing hands. With open source,
the code itself is free (in both the libre and gratis sense), but there are
other places to identify value. One of them is chain of custody - is there an
actual, somewhat responsible human being behind that package? Many of the most
dramatic recent failures are of this nature.

Other value is in the form of security analysis / fuzzing, etc. This is real
work and there should be ways to fund it.

I think the nature of business today is at a fork. Much of it seems to be
scams, organized around creating the illusion of value and capturing as much
of it as possible. The spirit of open source is the opposite, creating huge
value and being quite inefficient at capturing it. I can see both strands
prevailing. If the former, it could choke off open source innovation, and line
the pockets of self-appointed gatekeepers. If the latter, we could end up with
a sustainable model. I truly don't know where we'll end up.

~~~
skybrian
On the other hand, it seems like making automatic payments to dependencies
would be easy to screw up. Adding money to a system in the wrong way tends to
attract scammers and thieves, requiring more security vigilance, while also
giving people incentives to take shortcuts to make money. (Consider Internet
ads, SEO, and cryptocurrency.)

Monetary incentives can be powerful and dangerous. They raise the stakes. You
need to be careful when designing a system that you don't screw them up, and
this can be difficult. Sometimes it can be easier to insulate people from bad
incentives than to design unambiguously good incentives.

~~~
x0x0
A counterpoint: the system has already attracted scammers. see eg the bitcoin
injection in npm. And now that someone smart has blazed the way and
demonstrated the opportunity, others are sure to follow.

------
jasode
Fyi, the article's title and sibling top-level comment by austincheney may
give the wrong impression of what Russ Cox is talking about.

His essay is _not_ saying software dependencies itself is a problem. Instead,
he's saying software dependencies __evaluation__ methodology is the problem.
He could have titled it more explicitly as _" Our Software Dependency
Evaluation Problem"_.

So, the premise of the essay is already past the point of the reader
determining that he will use someone else's software to achieve a goal. At
that point, don't pick software packages at random or just include the first
thing you see. Instead, the article lists various strategies to _carefully
evaluate_ the soundness, longevity, bugginess, etc of the software dependency.

I think it would be more productive to discuss _those evaluation strategies_.

For example, I'm considering a software dependency on a eventually consistent
db such as FoundationDB. I have no interest nor time nor competency to "roll
my own" distributed db. Even if I read the academic whitepapers on concurrent
dbs to write my own db engine, I'd still miss several edge cases and other
tricky aspects that others have solved. The question that remains is if
FoundationDB is a "good" or "bad" software dependency.

My evaluation strategies:

1) I've been keeping any eye on the project's "issues" page on Github[0]. I'm
trying to get a sense of the bugs and resolutions. Is it a quality and
rigorous codebase like SQLite? Or is it a buggy codebase like MongoDB 1.0 back
in 2010 that had nightmare stories of data corruption?

2) I keep an eye out for another high-profile company that successfully used
FoundationDB besides Apple.

3) and so on....

There was recent blog post where somebody regretted their dependency on
RethinkDB[1]. I don't want to repeat a similar mistake with FoundationDB.

What are your software dependency evaluation strategies? Share them.

[0]
[https://github.com/apple/foundationdb/issues](https://github.com/apple/foundationdb/issues)

[1] [https://mxstbr.com/thoughts/tech-choice-regrets-at-
spectrum/](https://mxstbr.com/thoughts/tech-choice-regrets-at-spectrum/)

~~~
SmirkingRevenge
A couple questions that I like to ask myself...

\- How easily and quickly can I tell if I made the wrong choice?

\- How easily and quickly can I switch to an alternative solution, if I made
the wrong choice?

To contextualize those a bit, its often when trying to pick between some fully
managed or even severless cloud services vs something self-managed that ticks
more boxes on our requirements/features wish-list.

Also, its pretty important to consider the capabilities and resources of your
team...

\- Can my team and I become proficient with the service/library/whatever
quickly?

------
athenot
> Does the code have tests? Can you run them? Do they pass? Tests establish
> that the code’s basic functionality is correct, and they signal that the
> developer is serious about keeping it correct.

This is one thing I thoroughly miss from Perl's CPAN: modules there have
extensive testing, thanks to the CPAN Testers Network. It's not just a
green/red badge but reporting is for the version triplet { module version,
perl version, OS version }. I really wish NPM did the same.

Here's an example:
[http://deps.cpantesters.org/?module=DBD::mysql](http://deps.cpantesters.org/?module=DBD::mysql)

~~~
SomeHacker44
My opinion...

That implies too much faith in tests. Tests are no better or worse than any
other code. In fact, writing good tests is an art and most people cannot think
about every corner case and don’t write tests that cover every code path.

So, unless you audit the tests they add no practical additional layer of
trust, IMO, to just using the “package” with or without tests.

~~~
nobody271
Many times I've had the most use for a test that didn't fit into the
conventional unit test format but I didn't try to get it approved because I
didn't want to get into a dogmatic argument about what a test should or
shouldn't be. A lot of what I worry about doesn't get tested well using unit
tests.

~~~
woolvalley
Why not call it an integration or e2e test and be done with it?

------
jancsika
Modest proposal: do the opposite of everything suggested in this article.
After all, if you spend all your time inspecting your dependencies, what was
the point of even having them in the first place?

This will ensure that maximum time possible is spent implementing new
features. _Everyone_ on your team can pitch in to accelerate this goal. Even
non-technical outsiders can give valuable feedback. At the same time, this
ensures minimum time spent fiddling about in a desperate attempt to secure the
system and slowing everyone else down. Besides, unless you're already a
fortune 500 company, _no one_ on your team knows how to do security at all.
(And even then the number of experts on your team is probably still
dangerously close to zero.)

The software you ship will obviously be less secure than if you had focused
any time at all on security. However, the utility of your software will
_skyrocket_ compared to what it would have been if you had sat around worrying
about security. So much that your userbase is essentially _forced_ to use your
software because nothing else exists that even has a fraction of its feature
set.

Sooner or later the insecurity will catch up with you. But this is the best
part-- your software has so many features it is now a _dependency_ of nearly
everything else that exists. There is no chess move left except to sit down
and somehow _actually_ secure it so that arbitrarily tall stacks of _even less
secure_ software can keep being build atop it without collapsing like a house
of cards.

And it's at this point that the four or five people in the world who actually
understand security step in and sandbox your software. Hey, now it's more
secure than a system built by a cult of devs tirelessly inspecting every
little dependency before they ship anything. Problem solved.

------
et1337
Worse than package dependency is platform dependency. My code runs on top of
10 million lines of Kubernetes insanity that no one really understands,
including the thousands of authors who wrote it. In theory, that means at the
drop of a hat I can switch to a different cloud, kubectl apply, and presto!
Platform independence. In reality, every cloud is slightly different, and we
now depend on and work around a lot of weird quirks of Kubernetes itself.
We're stuck with what we've got.

------
austincheney
Easily explained in two points.

1\. Convenience at cost to everything else. Easier is generally preferred over
simplicity. If the short term gains destroy long term maintenance/credibility
they will solve for that bridge when they come to it at extra expense.

2\. Invented Here syndrome. Many JavaScript developers would prefer to never
write original code (any original code) except for as a worst case scenario.
They would even be willing to bet their jobs on this.

~~~
RealDinosaur
For me (Javascript Developer), you have to stand on the shoulders of giants if
you want to compete. Any code you re-invent is code you have to maintain.

I've found though, some engineers love to create everything from scratch, and
this greatly hinders their ability to hire/fire as everything is proprietary,
and usually not documented.

Most decisions are pretty grey, but for me, choosing to handle stuff yourself
is never a good choice. In the same way as no-one should ever try and create
Unity from scratch, no-one should try to create React from scratch. You simply
can't compete with the support and effort of a global development team.

If you wanna learn though, that's a different kettle of fish. Reinvent the
wheel all day. Just don't use it in production.

~~~
loup-vaillant
> _no-one should ever try and create Unity from scratch_

Some game developers do write their game engines from scratch. I know of at
least one successful example: Jonathan Blow, with _Braid_ (2D engine) and _The
Witness_ (3D engine). Note that in both games, a generic engine wouldn't have
worked, or at least would have required such an amount of customisation that
it's not clear it would have cost less, or looked and felt as good. Sure,
don't go rebuild a generic engine from scratch. But a custom one, tailored to
a very specific use case? That's not such an obvious no-no.

Another example would be Monocypher¹, my crypto library. Why would I write
such a thing when we already have Libsodium? The reason is, I saw that I could
do better for my use case: an opinionated, easy to use, portable package. The
result is _massively_ simpler than Libsodium. I don't care that I cannot
compete with the support and effort of Libsodium team. I made sure I didn't
need to.

[1]: [https://monocypher.org/](https://monocypher.org/)

~~~
RealDinosaur
Braid and Witness _could_ have been written in Unity though.

I'd argue that dealing with high level concepts such as game/level design and
art direction and low level stuff like graphics and rendering simultaneously
is insane. I don't know how Jon Blow did it, but personally being able to
abstract away all that low level stuff makes the design process way easier for
me.

There was a recent game, 'Return of the Obra Dinn', which went the opposite
way. It was the dev's first game with Unity, and he attributed most of his
success to the Engine.

It doesn't look like a Unity game, it doesn't play like a Unity game, and it
has won several game of the year awards.

~~~
loup-vaillant
Come to think of it, there's _Antichamber_ , a non Euclidean labyrinth based
on Unreal Engine (4, I believe).

As for how Jon Blow did it, I suspect having his own engine let him explore
gameplay ideas more readily than using a generic one. The time travelling in
Braid and all its variations would be pretty hard to bolt on a generic engine:
it's not just rewind, it's _partial_ rewind, with some entities being immune
to the rewind. There's even a level where time goes forward and backward
depending on the _position_ of the main character. Go right, forward. Go left,
backwards.

For The Witness, it's a bit more subtle, but about a third of the game
required pretty crazy 2D projective analysis of the 3D world (the
"environmental puzzles", don't look them up if you don't want spoilers). While
it didn't en up being central to the game, it was basically the starting
point.

The engines of Jonathan Blow's games are more central to their gameplay than
for most games. Still bloody impressive, but probably less unnecessary than
one might originally think. Also, Jonathan Blow has pretty strong opinions
about game development, and I got the feeling that he disagrees with most
generic engines out there. Working with them would probably caused suffering,
whose cost he didn't want to pay. (Speaking for myself, my productivity drops
pretty sharply when I spot stuff I too strongly disagree with, _and I can 't
fix it_.)

------
jsty
It might be that data protection regulations start to 'encourage' movement in
this area regards more careful consideration of the software dependency chain.
If you pull in a malicious dependency which results in personal information
being exfiltrated, I doubt the "we pulled in so many third party dependencies
it was infeasible to scrutinise them" defence is going to mitigate the fines
by very much.

~~~
DoctorOetker
that is the ideal path, but sadly most things indicate the system prefers the
opposite path, especially if we look at "responsible disclosure" where the
contributor is expected to give a _centralized_ temporary secrecy agency
advance warning, and we blindly have to trust them not to weaponize what
essentially amounts to an endless stream of 0days (or trust them not to turn a
selective blind eye to malicious exfiltration of these 0days)

------
rossdavidh
I like (and basically agree with) the article, but I have to think it
basically does a good job of pointing out the problem, and a bad job of
suggesting a solution. The sheer number of dependencies of most commercial
software now, and the ratio of backlog-to-developers, basically insures that
the work required to check all your dependencies does not normally get done.

Hypothesis: it will require a massive failure, that causes the ordinary
citizen (and the ordinary really, really rich citizen) to notice that
something is wrong, before it changes much.

Hypothesis 2: after that happens, the first language whose dependency manager
handles this problem well, will move up greatly in how widely it's used.

~~~
alkonaut
For a 100 man year project we have accumulated around a dozen external
dependencies and only two of them are transitive (one for zipping and one for
logging).

I think that’s fairly reasonable and about what I’d expect.

So as you might have guessed it’s not a node project, but that’s my point -
perhaps the idea of dependencies is manageable so long as the platform allows
you to keep it reasonable. Meaning, at the very least, a good standard
library.

------
bluetech
I think object-capabilities are one way to have much safer code reuse. Suppose
a dependency exports a class UsefulService. In current languages, such a class
can do anything - access the filesystem, access the network, etc. Suppose
however that the language enforces that such actions can only be done given a
reference to e.g. NetworkService, RandomService, TimeService,
FilesystemService (with more or less granularity). Therefore if UsefulService
is declared with `constructor(RandomService, TimeService)`, I can be sure it
doesn't access any files, or hijacks any data to the network - nor do any of
its transitive dependencies.

The method of sandboxing using OS processes + namespaces and what not is too
heavy and unusable at such granularity.

The method of per-dependency static permission manifests in some meta-language
is also poor.

The method of a single IO monad is too coarse. Also using any sort of `unsafe`
should not be allowed (or be its own super-capability).

Obviously there are many tricky considerations. [For example, it is anti-
modular - if suddenly UsefulService does need filesystem access, it's a
breaking change, since it now must take a FilesystemService. But that sounds
good to me - it's the point after all.] But does any language try to do this?

------
3xblah
The problem I see is not in the fact the develpers choose to rely on third
party software reuse and thus create dependencies, but in how developers
_choose_ which third party software to use. If their judgment fails, the
consequences for the user can be dire.

For example, Google chose to reuse the c-ares DNS library for their
Chromebooks over other available DNS libraries. It is maintained by the same
person who oversees the popular libcurl.

The company issued a challenge and a $100,000 bounty for anyone who could
create a persistent exploit with the Chromebook running in guest mode.

As it happened, the winnning exploit relied on an off-by-one mistake in the
c-ares library.

Users are not in the position to decide which (free, open-source) code is
reused in a mass market corporate product. They must rely on the judgment of
the developers working for the corporation.

On my personal computers, where I run a non-corporate OS, I prefer to use code
from djbdns rather than c-ares for DNS queries. If someone finds an off-by-one
mistake in djbdns, and this has negative consequences for me, it will be my
own judgment that is to blame.

------
Felz
The real dependency problem is that most languages give out way too much trust
by default. Any code can have any side effects.

I'd like ways to guarantee my dependencies have no side effects, like they
were Haskell with no IO/unsafePerformIo, or to aggressively audit and limit
those side effects. Malicious event stream package suddenly wants to use the
network? No.

~~~
beardedwizard
Another way to state this is: accept the state of the world and approach the
problem using an existing methodology - treat code as untrusted and whitelist
execution paths. SElinux and others do this, intrinsic is another product that
uses the same approach for app runtime, I think this is probably the future of
this problem space.

This is zero trust, and this pattern is showing up everywhere (again?).

------
tabtab
There used to be talk about how to increase "reuse" of software, and now that
systems use masses of libraries, the down-sides of heavy but casual reuse are
coming to light.

I'm not sure of an easy answer. Perhaps the libraries can be reworked to make
it easier to only use or extract the specific parts you need, but it's
difficult to anticipate future and varied needs well. Trial and error, and
blood, sweat, and tears may be the trick; but, nobody wants to pay for such
because the benefits are not immediate nor guaranteed.

OOP use to be "sold" as a domain modelling tool. It pretty much _failed_ at
that for non-trivial domains (in my opinion at least), but made it easier to
glue libraries together, and glue we did.

~~~
jerf
It's not _that_ hard. You just need to think of dependencies as something that
has non-zero benefits _and_ non-zero costs. The problem is that, as usual,
whereever you've got a "zero" showing up in your cost/benefits analysis,
you're overlooking _something_. Sometimes it's minor and negligible stuff, but
sometimes it's not. Act accordingly.

One thing that I believe we will come to a consensus on is that there is a
certain fixed cost of a dependency, analogous to the base cost of a physical
store to manage the stock of _anything_ that appears on the shelves no matter
how cheap the individual item may be, and that a dependencies will need to
overcome that base cost to be worthwhile. I suspect that the requisite
functionality is generally going to require in the low hundreds of lines at a
minimum to obtain, and that we're going to see a general movement away from
these one-line "libraries".

I say generally more than a few hundred lines because there are some
exceptional cases, such as encryption algorithms or some very particular data
structures like red-black trees, where they may not be a whole lot of lines
per se, but they can be very dense, very details-oriented, very particular
lines. Most of our code is not like that, though.

~~~
tabtab
Re: _It 's not that hard_

Do you mean creating libraries that are flexible and partitioned well for
_future_ needs? I do find that hard and almost no library maker I know of gets
it right the first time. Analysis of current needs is difficult; analysis of
future needs is extra difficult. Experience helps, but is still not powerful
enough. The future continues to surprise the heck of out me. Tell God to slow
things down ;-)

~~~
jerf
No, I mean that it's not _that_ hard to do some due diligence when picking a
dependency. You just need to get over the idea that it's something you don't
need to do.

No, you're not going to read every single line, but you ought to be running
through the basics outlined by Russ in his post. If you're being paid to code
and you're not doing those basics, you're being negligent in your professional
duty.

And knowing the internet and its inability to deal with nuance, let me say
again, no, it's not _trivial_. But it's not _that hard_ , either. If a
dependency is worth bringing in, it's bringing you enough value that you ought
to be able to spare the effort of doing the basic due diligence.

------
trhway
15 years ago adding an external module was an endeavor involving approval
forms, lawyers, etc. so that it frequently were much easier just to develop
required functionality yourself. These days i still shudder seeing how the
build goes somewhere, downloads something (usually you notice it only when
whatever package manager being used for that part of the build didn't find the
proxy or requires very peculiar way of specifying it - of course at the
companies with transparent proxies people didn't notice even that ) ...
completely opaque in the sense that even if i spend some time today looking
into what is downloaded and where from, tomorrow another guy would just add
another thing ...

------
jayd16
Is the package management story significantly worse for js/node than other
languages or is it just a meme? If it actually does have more issues, why? Are
the npm maintainers less rigorous than, maven central (for example)?

Java is lucky enough to have a lot of very solid Apache libraries built with
enterprise money. Is the culture different for js and npm?

~~~
aaaaaaaaaab
Java/.NET/C++/etc. people don’t have the urge to publish every other line of
code they deem “useful”. They also don’t have the urge to import said one-
liners when writing a helper method in 15 seconds is perfectly adequate.

~~~
saagarjha
> Java/.NET/C++/etc. people don’t have the urge to publish every other line of
> code they deem “useful”.

I do for my code sometimes, except I make GitHub Gists out of them instead of
putting them on NPM.

------
baq
> Adapting Leslie Lamport’s observation about distributed systems, a
> dependency manager can easily create a situation in which the failure of a
> package you didn’t even know existed can render your own code unusable.

Gold right here. Makes me wonder what Lamport’s TLA+ could be used for in the
problem area.

------
jrochkind1
> We do this because it’s easy, because it seems to work, because everyone
> else is doing it too, and, most importantly, because it seems like a natural
> continuation of age-old established practice.

And because we literally could not be creating software with the capabilities
we are at the costs it is being produced without shared open source
dependencies.

I guess this is the same thing as "it's easy", but it's actually quite a
different thing when you say it like this.

------
BinaryIdiot
Dependencies are such a huge pain but I kinda liked the way we handled it when
I did contracting work for the NSA years ago. Essentially we told them
_exactly_ what dependencies we needed, including subdependencies, and we
audited them the best we could and then we included them. To avoid this
headache meant we were less incentivized to just pull in a module for every
little thing and, instead, write our own where necessary or used modules that
had less subdependencies.

I think we're ready for a new class of dependencies. Dependencies that have
little to no subdependencies. Dependencies that you can more easily audit
because of fewer subdependencies.

Also, we need less building of JavaScript code in npm packages. Instead, let
people access the raw code so they can not only do tree shaking but they can
examine the code that is running versus the code that may be in git. You can
still include it and minify it with your stuff. This would also mean you could
have larger libraries that do more stuff because you'd only include what you
use (think how many Java libraries work except you could pull out what you
need).

I don't think there is a good software / npm solution. I think we need to
change the way we work with dependencies entirely.

------
mberning
I am reminded of this gem: [https://www.mikeperham.com/2016/02/09/kill-your-
dependencies...](https://www.mikeperham.com/2016/02/09/kill-your-
dependencies/)

------
jackfoxy
I ran across a great quote that sums up the situation.

 _...functionality is an asset, but code is a liability._

[http://widgetsandshit.com/teddziuba/2010/10/taco-bell-
progra...](http://widgetsandshit.com/teddziuba/2010/10/taco-bell-
programming.html)

Don't know if the OP came up with it himself, but he is now a candidate for
the California State Senate.

------
quantumwoke
I much prefer Java's model of software dependency consisting of (for the most
part) well-documented, large, feature-filled libraries distributed in an
easily discoverable and maintainable manner (maven/gradle/...) to the
dependency hell that is modern JS libraries. Hopefully newer languages like
rust don't succumb to the same trap.

~~~
willtim
I once took a close look at a Java Web service application running internally
at a large bank. It depended on over 3000 jar files, most of them likely
transitive. When I queried the rationale of this, the dev team just shrugged
it off as common Java practice. I do not think Java is in a significantly
better place than the JS world with regard to dependencies.

~~~
rhacker
I am curious if it was actually 3000, or if that is embellished. I've been in
about 6 java shops from Nike to startups, and the number is USUALLY around
100.

The reason is really simple - Jar files used to be required to download
manually to add as a dependency. So there was a history of about 15 years of
doing it the hard way. After maven was introduced, it took a while before OSS
libs started adding other libs. Most of the time OSS libs are just including
other Jars from their own organization.

~~~
willtim
It's possibly an embellished number, I cannot remember the actual number but
it was significantly larger than 100. I seem to have remembered it as 3000.
The project had many frameworks: spring, glassfish, camel among others.

~~~
rhacker
Gotcha, I'm guessing 300 range. That is quite a bit for Java actually - so
still counts as pretty bad. Contextually here's a fairly complicated program
in our current stack (which is all JS), the node_modules folder has 722
dependencies in it right now. Edit: I was replying to your first edition of
the reply. If it's truly 3000 that's quite insane. That being said, the
projects with a few hundred would have Glassfish, CXF, Spring, etc...

------
m0zg
Yes, that's why I package as much stuff as I can into a hermetic Bazel build,
including Python modules (and yes, I build Python programs in Google PAR
format using Subpar). They're all stored in my own cloud bucket, the entire
transitive closure can be tracked down, and they don't change underneath me
willy-nilly. For C++ cross-builds I also package toolchains in a similar
fashion. You could also package a toolchain for the host if you'd like, I just
don't bother. And I package test data likewise. The build isn't 100% hermetic,
but I'd say about 90%. I feel pretty good about this set-up and recommend it
to others. Grabbing random packages (and worse, their transitive closures)
from the internet as a part of the build sounds insane to me.

------
romeisendcoming
Article kind of mangles the relationship between software reuse (which _has_
been here for a long time) and specific language library, etc..management.

many years ago now systems administrators were tasked with providing a safe
and sane environment for end user and developers by performing the exact due
diligence that is described in this article. In the 'move fast and break
things age' all this has been thrown to the wind and everyone decries the
language manager code sprawl and breakage. Of necessity enterprises revert to
'immutability' as if it was a desirable and necessary deployment
characteristic. This is an ugly time in IT.

------
mrdoops
The way I see it, our over-dependency (sorry for overloading the phrase) on
Javascript as the de-facto web language has the pendulum far in one direction.
How much longer can we keep this up? What's the maximum capacity of a
developer ecosystem before dependency-hell and framework churn reaches
critical mass? This is still a complicated information system - how far can it
scale? What's the breaking point?

There's so much amateur work and muddied merit-sense-making of what's good
software, who to listen to, and how to move forward - my feeling is that
pendulum is just about at peak.

~~~
k__
For me this sounds like FUD.

Sure, you can install a bunch of deps for every small problem, but you don't
have to.

If you just take a bit time to think, you can roll your own solution for 90%
of the deps, which are tiny packages anyway.

~~~
mrdoops
But what of the newbie developer? Is he/she going to just roll their own
dependencies and do so in a way that's tenable? Green developers make up most
of the category.

I guess I was trying to approach a few concerns beyond just dependencies:
learning curve, conventions/standards, framework volatility, and merit assess-
ability of ideas.

The more people involved (popularity), the greater the difficulty to parse the
merit of an idea without pre-existing competence. How easy is it for a new
developer to find a cogent way of doing things in Javascript land compared to
a smaller more specific ecosystem? In the smaller ecosystem the experts are
easier to determine due to a smaller population, whereas in Javascript-land
there's so many people, opinions, articles, and conventional disparities; a
much more challenging exercise.

~~~
k__
In PHP people would (often badly) reinvent the wheel on every project.

In JS people would install packages for every small problem they have.

Neither is good.

I always check if I can write it myself in reasonable time, if not, I install
a package for it.

I'd install React, but I'd write the navigation myself.

I'd install a video-player, but I'd write a SVG animation myself.

etc.

------
simonjgreen
Along the same lines is Docker Hub. Blindly building your own images via
dockerfiles that pull from others images should warrant serious consideration,
especially given those images can be updated at any time.

------
jorangreef
"Dependency managers can often provide statistics about usage"

Using module usage statistics as a proxy for trust is not always a good idea.

For example, I confirmed with the security team of npm that they do not audit
module download statistics, i.e. no detection of gaming the system through
multiple downloads from a given IP.

It's quite possible for a module to have 10,000 weekly downloads, all
generated by a cron curl script run by the module's author.

I wouldn't be surprised if this was the case for not a few modules on npm,
especially to develop trust for later exploits.

------
adgasf
Great article.

Some thoughts (mostly informed from design of Buckaroo
[https://github.com/LoopPerfect/buckaroo/](https://github.com/LoopPerfect/buckaroo/)
):

\- Cost of creating a package must be low (ideally the package just lives in
source control). This encourages code reuse and therefore testing.

\- Verification of changes must be easy. Git is a great tool for this - we can
review patches between versions, rather than whole versions at once.

\- It should be easy to extract dependency graph (including transitive deps)
so that you analyze who you are trusting.

\- There must be a verifiable chain from package source to package bundle (NPM
fails here, do you really know the source code reviewed on GitHub is what went
into the bundle on the NPM registry)? Better yet, have no bundles at all, just
source code + build instructions.

\- Reproducible installations (usually implemented via lock-files) are
critical. Many package managers have lock-files that do not actually give
reproducible installs. Beware!

\- Package builds must be isolated from each-other (otherwise one package
might tamper with another; I believe this is possible in NPM packages)

------
rdiddly
This is excellent. Not only for the subject matter but the quality of writing.
I often take an article like this, distill it into my own (usually fewer)
words and save it as a text file. This one I kept "distilling" only to
realize, nope, nope, the way he said it was more exact/precise/correct.

------
iand
This is probably a prelude to a deeper discussion of the module notary service
that the Go project intends to run. First announced in this post from the end
of last year:
[https://blog.golang.org/modules2019](https://blog.golang.org/modules2019)

------
monksy
On dependencies:

There aren't a lot of tools out there to keep you 100% up to date and to keep
moving.

There is maven-dependencies that can auto upgrade, however, that's just a
simple version upgrade and may have issues with non-standard versioning. Also
it doesn't help with transitive dependency conflicts.

We need good tools to alert and stop transitive dependency conflicts in their
tracks. Versions helps with this, but it doesn't tell you much.

What we do need: Jenkins dependency triggers for the projects. We need
something that will automatically work wtih SCM and CI to create commits based
on new found dependencies. If there is something that changed your tests
should confirm if it works or not.

~~~
riyakhanna1983
What about existing tools, such as Synk, OSSIndex?

~~~
monksy
That's good for reporting. There should be an automated approach to keep
projects up to date.

------
richardwhiuk
If you can easily write a replacement for it, then the cost of depending on it
is very small - because the worst case for the fix is replacing it....

~~~
jeremycw
Worst case is more along the lines of a bad actor makes malicious changes to
the dependency which you then unwittingly deploy to prod potentially
compromising your entire system.

~~~
cortesoft
Or if the dependency disappears and breaks deploys

[https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/](https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/)

~~~
Nasrudith
Really I couldn't help but facepalm when I heard about that. "Haven't those
people heard of local caching?!"

Really and also freeze the version number to what you know will work while you
are at it. Unless it is an actual security and/or standardization important
component (say SSH) it can wait until you know what it will do and that it
won't break anything. It is good for bloat avoidance, security, and
reliability.

------
zzo38computer
Reducing the number of dependencies can avoid many of the problems, and makes
it easier to examine the code, as well as less likely to cause problems (of
several kinds). Many code has too much dependencies. Whether writing in
JavaScript or C or something else, I will usually not use many external
libraries; most commonly none at all.

------
rurban
I wouldn't qualify zlib as trusted high-quality code. He really needs to look
deeper.

------
profalseidol
There's a talk from Rich Hickey about this.

~~~
LandR
Got a link?

~~~
profalseidol
I think it's this one:

[https://www.youtube.com/watch?v=oyLBGkS5ICk](https://www.youtube.com/watch?v=oyLBGkS5ICk)

HN Post:
[https://news.ycombinator.com/item?id=13085952](https://news.ycombinator.com/item?id=13085952)

------
yyyymmddhhmmss
I am pretty new to development, and I keep trying to prove myself wrong over
my apprehension to willy-nilly accumulate dependencies just because “the time
savings add up”.

Before starting any new project, I research and try all the existing similar
projects I can find. I can predict their stability with overwhelming precision
just by glancing at the dependencies, so the few projects I have built use
only the most vanilla version of mainstream dependencies.

And another result of this observation has been that I have come to devalue
the word of devs with that happy-go-lucky approach to dependency accumulation.
It seems to correlate with the exaggerated optimism that persists around
everything in the development community. I’d like to be more optimistic just
like everyone else, but ignoring debt like this doesn’t seem like the right
way to do it.

------
ilaksh
I'll just go ahead and take the downvotes/burial/lectures/ridicule whatever
but I need to say it anyway. I've been programming for thirty years and in my
opinion effective code reuse with npm is one of the greatest achievements in
the history of software engineering. It's not perfect but it should be
appreciated more and the issues are being overblown.

