I am wondering if I am missing something obvious here and would value any opinion.
I am wondering if I am missing something obvious here and would value any opinion.
If you're looking at the NPM ecosystem and saying, "the number of dependencies is problematic because it takes a long time to review them", I agree with you. If you're looking at the Go ecosystem and saying, "there are fewer dependencies, so I don't need to review them", then that's a security antipattern.
The better way to phrase this to your team is that you need to review dependencies, period. The NPM ecosystem isn't problematic because it introduces a new requirement to review dependencies, it's problematic because reviewing dependencies is harder. You can use NPM all you want for security-sensitive code. You just have to spend the extra time to review dependencies, which means bringing in new dependencies will be much slower than in a shallower ecosystem like Go's.
That's the point of OP's article. You can't skip reviewing dependencies in any ecosystem, in part because all of our ecosystems across the board have crap sandboxing and permissions, but also because of license issues and quality issues like the author found. There is no shortcut for that, whether you're using Ruby, Go, Python, whatever. You have to review your dependencies.
The dependencies themselves are generally smaller and more self-contained. Arguably it is easier to review since the side effects and cross-module behaviors are much more pronounced (it's generally deemed a "bad thing" by the community for modules to make unnecessary global actions)
Instead, phrase it as, "well obviously we need to review our dependencies, so why use an ecosystem where that's hard?" Don't phrase it as a security problem, phrase it as a development time and overhead problem.
Or better yet, let your team use NPM as long as they review the dependencies, and if your dependency tree is really a problem, your team will naturally become annoyed by the extra work and will avoid large dependencies on their own without a lot of extra prompting.
My biggest issue is with things in the node ecosystem is breakage, especially React libraries. I was following a NetNinja tutorial series on Youtube. I had to use a Firebase-Redux library for a combined React/Redux/Firebase series. There was a security vulnerability with something down the stack only a few months after the tutorial was out. So to use the updated version I had to use the beta version of a Firebase library which broke the tutorial code despite the library change being a point release like 1.4.0-beta2. Either 1.3.0 to 1.4.0 or 1.2.0 to 1.4.0 would be consider a breaking release for this particular dev.
We have a large group of dependencies chained upon each other with maintainers that have different support commitments and styles. React might be the de facto JS system, but it relies on a large system of these 3rd party packages to do deeper functionality which can so easily break things. I already had to peg React itself to an older version to use this tutorial when it was months old.
I realized at this point how much of a pain it would be to maintain this code in my portfolio if I did something with it. Let alone a paid product. I stopped learning React at this point (or at least learning React+Redux+Firebase).
Ruby sorta has this issue too, but it feels the Node ecosystem is much more accelerated. The Rails framework dominates the gem ecosystem so much usually gems break functionality by the version of Rails they support. So gems often will not update or warn of breaking changes based on the Rails version or ActiveRecord which follows Rails versioning. This makes gem dependencies much more manageable.
The node ecosystem does not have a large dominating framework like Rails. Even if you considered React that framework, Facebook breaks it whenever they please. 16.4 which sound like a minor version update break things. While Rails 4.2 was supported for 4 years. jQuery might be years before it breaks support.
I was explaining why to a friend, and it appears that no-one takes this threat seriously, not even in secure/financial apps.
Like you, I wonder if I'm missing something obvious. Why does the npm dependency trust nightmare give me the screaming heeby-jeebies but everyone else thinks it's all perfectly fine?
People who don't worry about it tell themselves that nothing that matters very much is coded in javascript, obvious exceptions notwithstanding.
Maybe the only practical way left to avoid it is to avoid javascript for things that matter, and to avoid products constructed with javascript for uses that matter. This might be an unpopular observation, but that does not make it wrong.
It is possible that WASM can fix much of this, at the cost of exposing ourselves to what might be extreme risks inherent in WASM itself. And of course, to whatever dependencies are brought in for the language we compile to WASM.
I think that's the right balance. clap 3 is and should be treated as a totally different package from clap 2.
Nonetheless I have run into the diamond dependency problem in practice. Their solution is unprincipled and there's no way to turn off the default behavior.
I agree! But it shouldn't fail at compile time with a vexing error message. It should fail to resolve dependencies when calculating a build plan.
I agree that the error message sucks, for what it's worth.
The second biggest flaw is that it couples package management to a programming language, despite those being almost completely unrelated concerns.
Yes it's frustrating. I wish it was more decoupled, maybe with package standards like Haskell tried to do with Cabal.
It makes it hard to use Rust as a drop-in replacement for C. I can add a few C files to a Haskell project without much problem, but most rust code goes via cargo.
We're a long way from it being that simple. There are "hello world" examples, but nothing close to a working GUI in Go-WASM yet.
I'd love a clone of Vue in Go-WASM. Just saying, if anyone out there wants to give it a try ;)
But the repetition in "heebie-jeebies" looks more appealing. Even if grammatically unlikely.
I'm confused now.
Is "heebie-jeebies" intrinsically plural (like "sheep"), and therefore the pluralisation doesn't matter?
Is there even such a thing as a single "heeby-jeeby"?
Heebie-jeebies are best thought of as an affliction, like hives or bedbugs, characterized by shuddering and head-ducking.
My guess is it started as a euphemism for hebephrenia, an obsolete affliction associated with youthful anxiety.
No saying whether that is really where the New Evil Empire got its name.
They play lip service to it being secure, and/or license compliant
A got a taste of it yet again this past weekend when I commented here on HN about GPL and AGPL being licenses that corporations often have concerns with.
I think that's pretty standard for our industry, even outside the often comically lazy world of JS.
Here's a comparison of popular bundlers using a visualization tool I found online:
Webpack - https://npm.anvaka.com/#/view/2d/webpack
Parcel - https://npm.anvaka.com/#/view/2d/parcel
Rollup - https://npm.anvaka.com/#/view/2d/rollup
Granted this only covers the initial package for each of these, there are usually a bunch of extra plugins or cli packages that you'll need to work with each of these but I also found the dependency tree of each of the added Rollup plugins to be less than the other 2.
here's rollup-plugin-babel: https://npm.anvaka.com/#/view/2d/rollup-plugin-babel
But it's not satisfactory because I can't unit test my components properly, or minify/obfuscate properly.
I'm looking at browserify to help with that, but that's going to break my whole build system so I need to find the appropriate time to do that.
Or there's Elm... I keep looking at Elm and wondering if that would solve a lot of these problems...
By adopting any semblance of a modern front-end stack, you're going to be pulling in a huge dependency tree. So, your choices are:
A) Don't do modern front-end development. This, despite being very popular with the HN crowd, is a challenging option. Losing the power of popular frameworks makes it difficult to recruit new developers, slows down developer velocity, and can cause tech debt to accrue more quickly.
B) Validate every dependency. For a skilled security reviewer, this may be possible. For an average developer however, reviewing thousands of dependencies is unlikely to be a good use of time. There are some improvements, automated tools that can be run, but the unfortunate reality is that the NPM attack surface is massive and I don't know of any techniques for producing any truly reliable security assessment.
C) Depend only on packages that you trust and trust them to validate their dependencies. Obviously, if this transitive trust breaks down at any layer then a vulnerability is introduced.
In practice, I've only ever seen individuals and companies do C. I think if there was some tooling around B, that would be the best option. I see a lot of room for disruption here - if there was a way to assign trust to a dependency and then a package-manager level construct that would only allow trusted dependencies to be installed, that might work.
I think the ship has sailed on A.
PS: I came to statically typed languages as an adult, I actually disliked those languages before I had the lightbulb moment, so I think I might even be more qualified than everyone who hasn't managed to enjoy both sides ;-)
- pip for Python,
- NPM,
- Nuget
- and Maven (and Ant)
Maven is by far my favorite, despite XML.
In fact, give how small pom files are and how little you have to deal with them (if you know what you are doing) I find it amazing how many comments I have had to read about "XML-hell" etc.
Another problem with npm: every package gets its own copy of its dependencies. Not sure if Rust does that.
It does something halfway in between, which is still a security liability.
Naturally it should be easy to specify a whitelist of licenses. (Of course then one has to decide whether to trust the package.json-s.)
That said, security review is hard for any ecosystem. Go probably has inherent advantages compared to the JS ecosystem, simply by virtue of being younger, having a real standard library, being more focused (no browser vs nodeJS issues) etc.
PS: there are projects that aim to do collaborative audit/review for Rust ( https://github.com/crev-dev/cargo-crev ) there should be something like that for the JS world. also there's the NPM "report vulnerability" feature.
But the whole dependency hell, left-pad and the likes are a real turnoff. So what I'd probably end up with is coding in pure Js without a package manager or "build system". Basically like you did PHP in the 90s. The question is whether I'd end up with anything remotely sane (already assuming it doesn't involve any crypto or similarly complex).
Also Yarn is very handy. (Fast, simple, correct.)
If you need leftPad, maybe just "vendor" it manually.
You're confusing the tool for the language the tool operates on, akin to claiming modern C++ is Visual Studio.
TypeScript otoh is just ES with types. It moves in the completely opposite direction, adding just enough syntax to get a proper type system in place (a pretty good one to boot).
If anything I would expect it’s syntax additions to be adopted as the standard and directly supported by runtimes. Despite static typing not being that useful for interpreters.
Unless runtimes will completely phase out es, focusing on wasm. (But that will take decades, so not really an either or thing)
The ECMAScript standardization is process is invaluable, after all TS builds upon it. But TS provides a saner subset (and adds a very productive type system), without that I wouldn't touch "modern" JS even with a stick.
Quoting the TypeScript website:
> TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
There is not a single feature from JavaScript that TypeScript removes.
If you want to have 0 dependencies, you will have to write a lot of code to do things that would just be a line or two in most other languages.
So JS community should throw left-pad in the window and declare underscore.js as a library of choice, so every other library will depend on it and that's about it.
My advice is to avoid most of the JS build tools (i.e. grunt, gulp) by using npm scripts (basically shell commands defines in `package.json`) to trigger build actions, or to start smaller, more specialised tools. At least then you will understand how your build works and won't have an extra layer of buggy plugins screwing things up.
Also being conservative and critical of the dependencies you take on is a great idea. Many smaller dependencies are not worth it. Bigger popular libraries like lodash are often a better deal. Unfortunately this micro-module philosophy and "publish random trash to npmjs.com" attitude has created a huge amount of crud packages.
I've had success organising a bigger project into multiple node packages inside the same git repository and using yarn's workspaces feature to tie it altogether. I avoid the complexity of publishing those (private) packages on npmjs.com. yarn's workspaces make it possible for my main app to find its (internal) package dependencies in the same git repo. is certainly possible to do JS development in a more controlled way.
My advice is to avoid most of the JS build tools (i.e. grunt, gulp) by using npm scripts (basically shell commands defines in `package.json`) to trigger build actions, or to start smaller, more specialised tools. At least then you will understand how your build works and won't have an extra layer of buggy plugins screwing things up.
Also being conservative and critical of the dependencies you take on is a great idea. Many smaller dependencies are not worth it. Bigger popular libraries like lodash are often a better deal.
I've had success organising a bigger project into multiple node modules inside the same git repository and using yarn's workspaces feature to tie it altogether. I avoid the complexity of publishing those (private) modules on npmjs.com. yarn's workspaces make it possible for my main app to find its (sub-)module dependencies in the same git repo.
https://medium.com/code-adventures/farewell-node-js-4ba9e7f3...
In any case you can’t avoid proper auditing and pen-testing. Even if your app code is rock solid, it doesn’t mean your infrastructure is safe. Or even your office building or the data centre serving the code. Or your employees and their credentials.
All it takes is a decision to use mongodb or redis without auth and to expose the ports to the internet, and the most secure runtime in the world won’t save you then.
Or a bit of social engineering so the attacker is making perfectly legit requests in your perfectly secured app.
The npm model is a shitshow (multiple versions of one dependency). People defend it for obviously self-interested or lazy reasons.
In the JVM ecosystem, projects are only allowed in the Maven Central repository if they have their license documented in a machine readable fashion there; it's then trivial to check what licenses you're depending on (and there are several plugins available for doing so). I'm amazed other ecosystems don't offer the same.
Since a dependency can generally do anything your application has privileges for, widely depended-on libraries are an attractive target. A cattle approach means more dependencies, and it being easier for new ones to sneak in.
It depends on your context. Sometimes, dependencies are not pets, but weights on your airplane.
There's nothing wrong until something goes wrong an now you're royally screwed. With zillion dependencies you are at a mercy of zillion maintainers, and none of them has any obligation to you. They can break backwards compatibility in patch releases, introduce subtle behavior changes, steer the project in an unexpected direction or abandon it altogether.
In total, I find it hard to deny how productive the NPM ecosystem can be, despite my philosophical objections to the way the community is run. Am I crazy here?
What I feel they are missing is a community process to consolidate things. You don't need three generations of ten incompatible solutions for a given problem - after some iterations, things should consolidate into one or two more or less standardized libs that don't break existing code at every damn point release.
I don't find churning out code admirable, and I also don't think I've seen any true innovation come out of the NPM scene (bar innovation in the browser/JS space itself, which I think isn't a good measure as it's mostly just working around limitations that shouldn't be there in the first place).
People move billions with a node.js application we develop and the company will eventually be liable if the system is compromised through a targeted attack.
On a different note, I think the ecosystem moves too fast, packages and versions are getting deprecated and new ones getting released constantly. I have the feeling that the whole ecosystem is targeted towards building small MVP apps, not relying a long-term business on it. Maybe I am too harsh here, but that is a frustration growing for years now. I am happy to be proven wrong.
Are there other reproducibility concerns I should be worrying about? Are you thinking npm modules with native code or that (this does happen!) actively pull other stuff during build? Most of those do their own pinning but agree the whole thing is messy.
That is a scary point of view. We have forgotten that there is no such thing as a zero-cost abstraction, apparently, and shovelware developers are now employed by enterprises and write enterprise software...
CPUs aren't getting faster. Software is getting slower a LOT faster than hardware is getting faster, these days, and people are still apparently perfectly fine with adding dependency upon dependency upon abstraction upon abstraction and it's adding up extremely quickly.
A good first step to addressing this is to favor a small copy & paste operation over a small dependency. Lots of people will shriek at the idea of this, but I promise you, all the problems with copying and pasting code are nowhere nearly as severe as all the problems with dependencies. Working to avoid problems you don't have results in creating problems that you definitely do have.
... Which also happen keep their own cattle that you're still responsible for.
It's not so bad in languages with solid standard libraries. In Python projects I might have 20 direct deps, ~50 indirect.
In a real JS project I'm building, I have 17 direct, 3829 indirect. The JS standard library is so damned thin that everything pulls in some random version of the kitchen sink.
yarn list | sed -E 's/.*─ //' | sort -u | wc -l # minus 2
In situations like that your job of auditing licenses, updates, sec issues, etc balloons exponentially with each new dependency.Tooling should absolutely be used, but it still doesn't perform the job of working out whether or not you want to upgrade a component, or whether or not you're likely to have suffered a security breach, or how to report on how well audited your dependencies are.
Dependencies are, if they are to be useful at all, all different. Dependencies are suppliers, in the business sense. Having lots of dependencies loaded at runtime is like a modern just-in-time giant supply chain; it lets you take advantage of efficiencies in exchange for being more brittle.
Or they are like BOM items on a circuit board. Part of the original drive to "componentise" software came from people experienced in electronic engineering; you don't have to reinvent the transistor, you just buy them at a cost of a few dollars for a reel of thousands. But experienced designers will still try to:
- choose more-common components wherever possible
- ensure there are multiple sources for a component
- reduce the overall number of BOM lines, which reduces supply risk and inventory cost
The software world would go completely bananas if the cost for dependencies was not exactly zero. Imagine having to license left-pad.
That doesn't make any sense. That analogy works for mostly-identical computers, where if your software won't run on one computer you can just use another mostly-identical computer.
Almost by definition dependencies are not interchangeable. You can't replace a routine to pad strings with a routine that is a web server. Or a matrix multiplier. Even dependencies that do the same overall job almost never have the same API. Heck, even the same dependency often ends up with a different incompatible API over time as versions change.
> There's nothing wrong with having zillions of them; what you need is good tools to manage them in bulk.
Every added dependency is a risk. Each unintentional vulnerability in each dependency increases the number of vulnerabilities that might be exploitable in your system. And that's just the unintentional vulnerabilities.
Practically all systems provide no useful sandboxing between dependencies, so if any one of your transitive dependencies is malicious, then your entire system is malicious.
Every new dependency also brings in potential license issues, per this article. I think it's unacceptable to have a scarefest about the AGPL, GPL, or LGPL; for a vast number of applications those licenses are just fine. The bigger risks are software that has no license at all, which are a legal risk for any project that uses them until governments change international treaties involving copyright (which is not likely any time soon). But it's certainly true that various licenses are not acceptable for certain situations, and every new dependency increases the risks of licensing problems.
Having no dependencies is absurd; it's uneconomic to build everything from scratch. But every time you add a dependency you need to think about the trade-off; it is sometimes wise to not reuse something.
How do you manage reputation and relationships in bulk? There are transactional costs here. Dependencies created by maintainers with impeccable reputations are a much smaller risk than arbitrary dependencies created by arbitrary maintainers.
This seems to apply to security in general. Security these days is doing the minimum amount needed to check some boxes that you are now secure. I suspect a lot of this is driven by incentives. There is few negatives to an individual to use bad security over good security, and the costs of good security means less of the 'good stuff' being developed which results in worse reviews and less prestige. And if people are hacked, the blame is primarily placed on the hackers with little danger to the developers and often even less danger to the company (they might have to spend 5% of the budget they saved on PR to repair their image).
I'm not sure how to fix the issue with prioritization of security. My first guess would be by changing the incentives to companies so they bear the liability in identity theft instead of the user (the very concept of identify theft is a trick to blame the end consumer instead of either the business leaking data or the business giving away money without verifying if data is accurate).
Times are changing, so all the power to you if you actually review it. Otherwise the advantage will remain a potential, never to be realised.