Small world with high risks: a study of security threats in the NPM ecosystem
blog.acolyer.org
blog.acolyer.org
Linux distros have many, many years of experience curating packages at scale. It is crazy to me that the node community just totally disregards all of the best practices learned by the Linux distros in favor of practices that are lazy and unambiguously dangerous.
To this day, if you can add a new repository, it is fully trusted and can provide trojaned updates to any core package. Bump the version number to something higher than what the core distro would use and you can persist for quite a long time. I remember pointing this out at a Nokia project (would have been no later than early 2008) as an attack vector via apt repositories.
We wanted to prevent a rogue or compromised repository from being able to provide an update to, say, glibc or libstdc++. Never got to work on that - Elopcalypse took it all down.
Invent idiot-proof security and someone will invent a better idiot. Even if you have a "trusted=1" flag for repos, you can be sure people will set external repos to be just as trusted as the core ones without a second thought as soon as they stop them from doing what they want to do (whatever that is, or however in/sane it is).
It's maybe not the most elegant option, but for security-conscious sysadmins you could already today disable all repos ad hoc and run your "system wide update" only from certain whitelisted repos, while still allowing specific one-off package installs from third party repos.
By adding a third party repo you have already said you trust that repo to install software, probably as root, on your machine. It doesn't get much worse than that from a security standpoint anyway right, will "trust flags" really make much of a difference to the wider security issue?
Sorry if I'm making too many assumptions here or thinking out loud.
It's not a security feature, but a convenience one for forbidding repositories from updating the packages from the ones you trust more (with correctness). Yes, a binary flag would be useless. What the GP wants also does not add any security, but it's an important part of a stable system that language-specific repositories have been overlooking for a while.
That was the thing - you couldn't.
I don't remember which way the level hierarchy went, but the idea was that rpm could not be executed directly even by root (LSM prevented that), and all package installation logic was confined to Zypper/libzypp. Packages and repos could declare their security level. Trying to pull in an upgrade to a LEVEL(core) package from a LEVEL(media) repository would fail. Zypper simply would not allow to install a package from a lower-security repository over an installed package that had come from a higher-security origin. Manually added LEVEL(core) repos would have been ignored.
There were a few layers of applied crypto, package signatures and immutable keyrings involved too. I had a prototype Zypper/libzypp with package-level signature verification almost ready just before my vacation and planned to polish it to an RFC after coming back. Best laid plans... During those two weeks the entirety of Nokia's linux development had been axed, maemo killed and thousands of good embedded systems engineers were suddenly looking for new jobs.
Funny thing, though. Zypper's source code was almost pleasant to work with after having seen the Lovecraftian horrors that made up libapt.
Just take an average startups web project node_modules directory, what's inside there? Hundreds and hundreds of packages of which most are dependencies of other packages. Anyone could have written it! Novice devs swear by using Typescript, but at the same time using hundreds of black boxes that can easily contain stuff way more damaging that a string applied to a number..
Remember left-pad? That was an easy one to fix, but still caused damage at large scale. What about a vulnerability in a larger and more complex package, owned by some bad party?
I'm currently investigating rollup because it only has 3 dependencies (2 of which are @types)
That sort of defeats the purpose of switching from webpack.
And for libraries: Why do you even need a bundler? Can you not distribute the library as multiple files?
For your other question it depends on your library. If you’re only targeting ESM or CJS sure, if you need a UMD or want separate outputs for separate targets a bundler like rollup can help prevent a lot of pointless boilerplate.
Rollup does have some issues with webapps since it's so heavily oriented toward JS-as-entrypoint, but tbh Webpack has the same issues; it just works around them by layering some extra complexity on top in the form of plugins (which you can actually do relatively easily in rollup too, though it's still not ideal in either situation).
Edit: Just measured those dependencies for comparison.
- rollup: 3 dependencies (all top-level, none have subdependencies. 2 are just typings which contain no executable code)
- webpack: 425 dependencies (23 top-level)
- parcel: 837 dependencies (57 top-level)
I haven't used parcel, so can't comment on its function. By the sounds of their website they're focused on speed, so if performance is your only concern they might be a good shout. If you're looking to reduce your security surface area, their approach to dependency management seems pretty irresponsible.
So I guess I’ll change my suggestion and say just use rollup
"Move fast and break things."
The whole Javascript ecosystem depends on high churn rapid adoption of "new" technologies. Stopping to check things would slow this down. Higher-QA technologies get outcompeted by lower-QA technologies.
While both are still relatively strong, it seems that JS's recent (~8y) surge in popularity over established mainstays like Java may stands counter to that point.
Given that the frontend has to be written in Javascript, it's tempting to use it for the backend as well.
"Screw your wheel, we can invent invent our own" is not exactly a surprising strategy when it comes from the language/development community that is mostly building framework after framework that gets abandoned in a year or two.
To just use whatever libraries. On all the node.js projects I have worked on, none has had any security aspects when importing a new library. That is a bit scary since many libraries has a ton of dependencies themselves.
Not once have I even heard a discussion about a security topic or perhaps maybe vetting dependencies and similar stuff.
Also if you look close, a lot of commonly required modules in the big projects are just used once in an act of "code"-spam by interested parties...
EDIT: just have a look at this: https://github.com/eslint/eslint/commit/55bc35dcd2dc3987cc77... - let's use cross-spawn, if you could use this node-native function (available for about a 100 years...) https://nodejs.org/api/child_process.html#child_process_chil...
Quite a few packages cover basic functions, such as type identification, array handling, etc... which have only a few lines of relevant JS code but are used by many other packages. That seems to unnecessarily increase the risk to my project of the module disappearing or becoming a security threat. There's something to be said for rolling your own helper functions.
foo = require("excitingModule", {fs: true, net: true, os: true});
It wouldn't be that hard to implement either.And if you think creating an interpreter in the interpreter is the solution, I'm quite sure the thing will not be secure if it is a natural child of the JS ecosystem.
deno --allow-net https://deno.land/std/examples/echo_server.ts
This would be the same as running something with an unprivileged user: sudo -u otheruser node echo_server.js
Deno however takes it to a whole new level by running server code directly from the web =) deno --allow-net=0.0.0.0:8000 https://deno.land/std/examples/echo_server.ts
Or even provide a list of addresses: deno --allow-net=0.0.0.0:8000,localhost https://deno.land/std/examples/echo_server.ts ip netns exec networkname node script.js
Still only on the entire app though, the idea was how to restrict single modules and their dependencies, not your whole app! Something like: const foo = requires("bar", {fs: true, net: "0.0.0.0:8000", os: true});"Altruism is a fine motive, but if you want results, greed works much better." - Henry Spencer
* Datadog is using both TUF and in-toto to defend against attacks between developers and end-users of its Agent integrations [3].
* Docker Content Trust is protecting users from a compromise of Docker Hub itself [4] using a version of TUF [5].
* Uptane is a version of TUF that is being standardized to protect software updates for automobiles [6].
* PyPI is considering adopting TUF to protect users of Python packages [7].
* Cloud Native Application Bundles is standardizing the use of TUF and in-toto to protect users of cloud-native applications [8].
Disclosure: I am a security engineer at Datadog, and am involved with both TUF and in-toto.
[1] https://theupdateframework.com
[3] https://www.datadoghq.com/blog/engineering/secure-publicatio...
[4] https://success.docker.com/article/docker-hub-user-notificat...
[5] https://blog.docker.com/2015/08/content-trust-docker-1-8/
[7] https://github.com/pypa/warehouse/issues/5247
[8] https://github.com/deislabs/cnab-spec/blob/master/300-CNAB-s...
What is the vetting process for other package managers?