Socket:
- Sep 15 (First post on breach): https://socket.dev/blog/tinycolor-supply-chain-attack-affect...
- Sep 16: https://socket.dev/blog/ongoing-supply-chain-attack-targets-...
StepSecurity – https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-p...
Aikido - https://www.aikido.dev/blog/s1ngularity-nx-attackers-strike-...
Ox - https://www.ox.security/blog/npm-2-0-hack-40-npm-packages-hi...
Safety - https://www.getsafety.com/blog-posts/shai-hulud-npm-attack
Phoenix - https://phoenix.security/npm-tinycolor-compromise/
Semgrep - https://semgrep.dev/blog/2025/security-advisory-npm-packages...
One thing I was thinking of was sort of a "delayed" mode to updating my own dependencies. The idea is that when I want to update my dependencies, instead of updating to the absolute latest version available of everything, it updates to versions that were released no more than some configurable amount of time ago. As a maintainer, I could decide that a package that's been out in the wild for at least 6 weeks is less likely to have unnoticed malware in it than one that was released just yesterday.
Obviously this is not a perfect fix, as there's no guarantee that the delay time I specify is enough for any particular package. And I'd want the tool to present me with options sometimes: e.g. if my current version of a dep has a vulnerability, and the fix for it came out a few days ago, I might choose to update to it (better eliminate the known vulnerability than refuse to update for fear of an unknown one) rather than wait until it's older than my threshold.
There are no real solutions to the problem, except for reducing exposure somewhat by limiting yourself to a mostly frozen subset of packages that are hopefully vetted more stringently by more people.
THEN use artifactory on top of that.
That's boring and slow though. Whatever I want my packages and I want them now. Apart of the issue is the whole industry is built upon goodwill and hope.
Some 19 year old hacked together a new front end framework last week, better use it in prod because why not.
Occasionally I want to turn off my brain and just buy some shoes. The Timberland website made that nearly impossible last week. When I gave up on logging in for free shipping and just paid full price, I get an email a few days later saying they ran out of shoes.
Alright. I guess Amazon is dominant for a reason.
Standard libraries should include everything needed to interact with modern systems. This means HTTP parsing, HTTP requests, and JSON parsing. Some laguages are excellent (like python), while some are half way there (like go), and some are just broken (Rust).
External libraries are for niche or specialized functionality. External libraries are not for functionality that is used by most modern software. To put your head in the ground and insist otherwise is madness and will lead to ridiculous outcomes like this.
This is great when the stdlib is well-designed and kept current when new standards and so on become available, but often "batteries included" approaches fail to cover all needs adequately, are slow to adopt new standards or introduce poorly designed modules that then cannot be easily changed, and/or fail to keep up-to-date with the evolution of the language.
I think the best approach is to have a stdlib of a size that can be adequately maintained/improved, then bless a number of externally developed libraries (maybe even making them available in some official "community" module or something with weaker stability guarantees than the stdlib).
I find it a bit funny that you specifically say HTTP handling and JSON are the elements required when that's only a small subset of things needed for modern systems. For instance, cryptography is something that's frequently required, and built-in modules for it often suck and are just ignored in favor of external libraries.
EDIT: actually, I think my biggest issue with what you've said is that you're comparing Python, Go, and Rust. These languages all have vastly different design considerations. In a language like Python, you basically want to be able to just bash together some code quickly that can get things working. While I might dislike it, a "batteries included" approach makes sense here. Go is somewhat similar since it's designed to take someone from no knowledge of the language to productive quickly. Including a lot in the stdlib makes sense here since it's easier to find stuff that way. While Rust can be used like Python and Go, that's not really its main purpose. It's really meant as an alternative to C++ and the various niches C/C++ have dominated for years. In a language like that, where performance is often key, I'd rather have a higher quality external library than just something shoved into the stdlib.
Really, good stdlib still allows you to use better suited 3rd party libraries. Lack of good stdlib doesn't add anything.
> Standard libraries should include everything needed to interact with modern systems. This means HTTP parsing, HTTP requests, and JSON parsing.
There is another way. Why not make the standard library itself pluggable? Rust has a standard library and a core library. The standard library is optional, especially for bare-metal targets.
Make the core library as light as possible, with just enough functionality to implement other libraries, including the interfaces/shims for absolutely necessary modules like allocators and basic data structures like vectors, hashmaps, etc. Then move all other stuff into the standard library. The official standard library can be minimal like the Rust standard library is now. However, we should be able to replace the official standard library with a 3rd party standard library of choice. (What I mean by standard library here is the 'base library', not the official library.) Third party standard library can be as light or as comprehensive as you might want. That also will make auditing the default codebase possible.
I don't know how realistic this is, but something similar is already there in Rust. While Rust has language features that support async programming, the actual implementation is in an external runtime like Tokio or smol. The clever bit here is that the other third party async libraries don't enforce or restrict your choice of the async runtime. The application developer can still choose whatever async runtime they want. Similarly, the 3rd party standard library must not restrict the choice of standard libraries. That means adding some interfaces in the core, as mentioned earlier.
It works well, but the downside of that approach is people complaining about how abstract things are.
Where do you draw the line though? It seems like you mostly spend your time writing HTTP servers reading/writing JSON, but is that what everyone else also spends their time doing? You'll end up with a standard library weighing GBs, just because "most developers write HTTP servers", which doesn't sound like a better solution.
I'm willing to stick my head the other way, and say I think the languages today are too large. Instead, they should have a smaller core, and the language designed in a way that you can extend the language via libraries. Basically more languages should be inspired by Lisps and everything should be a library.
If you have a well vetted base library, that is frequently reviewed, under goes regular security and quality checks, then you should be minimally concerned about the quality of code that goes on top.
In a well designed language, you can still export just what you need, or even replace parts of that standard library if you so choose.
This approach even handles your question: as use cases become more common, an active, invested* community (either paying or actively contributing) can add and vet modules, or remove old ones that no longer serve an active purpose.
But as soon as you find yourself "downloading the web" to get stuff done, something has probably gone horribly wrong.
That's exactly npm's problem, though. What everybody is avoiding to say is that you need a concept of "trusted vendors". And, for the "OSS accelerates me" business crowd, that means paying for the stuff you use.
But who would want that when you're busy chasing "market fit".
I don't think that's the problem with npm. The problem with npm is that no packages are signed, at all, so it ends up trivial for hackers to push new package versions, which they obviously shouldn't be able to do.
That is, how does signing prevent publishing of malware, exactly?
Yeah, of course. Also if they hosted their private key for the signature on their public blog, anyone could use it for publishing.
But for the sake of the argument, why don't we assume people are correctly using the thing we're talking about?
Doing it the right way would create friction, developers might need to actually understand what the code is doing rather than pulling in random libraries.
Try explaining to your CTO that development will slow down to verify the entire dependency chain.
I'm more thinking C# or Java. If Microsoft or Oracle is providing a library you can hope it's safe.
You *could* have a development ecosystem called Safe C# which only comes with vetted libraries and doesn't allow anything else.
I'm sure other solutions already exist though.
This is a standard practice in most places I have worked, CI/CD only allowed to use internal repos, and libraries are only added after clearance.
There are also tools that break CI/CD based on CVE reports from existing dependencies.
Then let's add friction. Developers understanding code is what they should be doing.
CTOs understand the high cost of ransomware and disruption of service.
Is java.util.logging.Logger not that great?
Sure, yet everyone that used it had a good night rest when Log4J exploit came to be.
If you want an actual solution, look for differences. If you somehow end up figuring out its about type of people using those, then there is no easy technical solution.
So, databases? Which then begs the question, which - Postgres, MySQL, SQLite, MS SQL, etc.? And some NoSQL, because modern systems might need it.
That basically means you need to pull in everything and the kitchen sink. And freeze it in time (because of backwards compatibility). HTML, HTTP parsing, and SHA1024 are perfectly reasonable now; wait two decades, and they might be as antiquated as XML.
So what your language designers end up, is having to work on XML parsing, HTTP, JSON libraries rather than designing a language.
If JS way is madness, having everything available is another form of madness.
Granted some (JDBC) more useful than the others. Although JDBC is more of an API and less of a library.
As for XML, JAXP was a common way to deal with it. Yes, there’s Xstream etc, but it doesn’t mean any of standard XML APIs are obsolete.
The thing is, you don't have to be this undiscerning to end up with tons of packages.
Let's init a default next.js project. How many dependencies are there?
react, react-dom, next, typescript, @types/node, @types/react, @types/react-dom.
OK so 7... seems like a lot in some sense but its still missing many reasonable dependencies. Some sort of styling solution (tailwind, styled components, etc). Some sort of http client or graphql. And more. But lets just use the base dependencies as an example. Is 7 so bad? Maybe, maybe not, but you need to go deeper. How many packages are there?
55. What are they? I have no idea, go read the lock file I guess.
All of this while being pretty reasonable.
I could just be projecting my own bad experiences with "security" folks (in quotes as I can't speak to their qualifications). My other big gripe is when they don't recongnize UX as a vital part of security (if their solution is unsuable, it won't be used).
Security: we put the ‘no’ in ‘innovation’.
I was shocked when I found out that at some of the most profitable shops, most of their code is just a bunch of different third-party libraries badly cobbled together, with only a superficial understanding of how those libraries work.
Essentials tools such as Jest add 300 packages on their own.
You already have hundreds to thousands of packages installed, fretting over a few more for that DatePicker or something is pretty much a waste of time.
Even more weird in the EU where things like Cyber Resilience Act mandate patching publicly known vulnerabilities. Cool, so let's just stay up2date? Supply-chain vuln goes Brrrrrr
This explains a lot. Really, this is the great reason of why the society is collapsing as we speak.
"There should be no DRM in phones" - "You Are An Obstruction To Making Profit".
"People should own their devices, we must not disallow custom software on it" - "YAAOTMP"
"sir, the application will weigh 2G and do almost nothing yet, should we minify it or use different framework?" - "YAAOTMP".
"Madame, this product will cost too much and require unnecessary payments" - "YAAOTMP"
Etc. etc. Like in this "Silicon Valley" comedy series. But for real, and affecting us greatly.
I think this is a good argument for reducing your dependency count as much as possible, and keeping them to well-known and trustworthy (security-wise) creators.
"Not-invented-here" syndrome is counterproductive if you can trust all authors, but in an uncontrolled or unaudited ecosystem it's actually pretty sensible.
This is an eco system that has taken code reuse to the (unreasonable) extreme.
When JS was becoming popular, I’m pretty sure every dev cocked an eyebrow at the dependency system and wondered how it’d be attacked.
Not even that actually. Actually the wheel is reinvented over and over again in this exact ecosystem. Many packages are low quality, and not even suitable to be reused much.
There's also the flavor of frontend developer that came from the backend and sneers at actually having to learn frontend because "it's not real development"
What kind of code does this developer write?
But the middle management might actually praise the developer, because they "get the job done" with the minimal effort (so "efficient"!).
We once had the rule HTML should be purely semantic and all styling should be in CSS. It was brilliant, even though not everything looked as fancy as today.
https://www.w3.org/WAI/WCAG22/Techniques/#client-side-script
Also, when was that semantic HTML rule? You make it sound like ancient history, but semantic HTML has only been a thing since HTML5 (2008).
If you're using HTML/CSS sensibly then it's accessible from the get-go by dint of the browser being accessible.
> Also, when was that semantic HTML rule? You make it sound like ancient history, but semantic HTML has only been a thing since HTML5 (2008).
HTML5 added a million new tags, but HTML4 had plenty of semantic tags that people regularly ignored and replaced with <div>, for example <p>, <em>, <blockquote>...
That is straight up untrue. Some ARIA patterns require developers to implement focus management and keyboard access from scratch.
For example, ”Correct implementation of the tree role requires implementation of complex functionality that is not needed for typical site navigation that is styled to look like a tree with expandable sections.”
But sometimes you do need that kind of widget for something else.
Or are you talking about a situation where the developer has implemented a custom component (aka "done something") which doesn't use the native focus system and therefore requires additional work to make accessible?
The native focus system typically works just fine, but JS is needed for keyboard interactions and to set things like aria-activedescendant.
For ca. ten years, the advice was to pointlessly "replace <i> with <em> and <b> with <strong>" and it utterly screwed over most new web developers' understanding of semantic tags. There are many reasons to use italics (and they vary between languages) but "emphasis" is just one of them, and none of the others ever materialized as tags.
It would have been far better to have recommended <i class="emphasis"> and <i class="media-title"> and <i class="snarky-aside"> etc. than to have added the <em> tag and said "just use it instead of <i>".
I'm not saying the ideal frontend dev writes no JS. I'm saying they write as little as possible. Some times you need JS, nothing wrong with that. The vast majority of the time you don't. And if you do I'd say it's a self-imposed requirement (or a direct/indirect result of a self imposed requirement) most of the time.
For me as a mostly backend dev, this was actually quite easy to achieve. Tiny modification of the backend API, some work in the frontend using JS to remove hints that should not show when JS is running, and voila, it works. Of course my pages are very simple in nature, but the same principles can be applied to larger pages. One could even link/direct to different pages, depending on the user running JS or not, and then have a workflow without JS and one with JS. It is all possible and only a matter of wanting to make an effort. Of course, modern JS frameworks do not really encourage this kind of design. Though server side rendering becomes more popular these days, I don't think we are quite there yet.
A page that is blank when not running JS has exactly zero accessibility.
The customer “ooh”s and “aah”s at said fancy animations running on the salesman’s top of the line macbook pro and is lured in, only realising too late that they’ve been bitten in the ass by the enormous amount of bloat that makes it run like a potato on any computer that costs less than four thousand dollars.
And US/EU laws are written by clueless bureaucrats whose most recent experience with technology is not even an electric typewriter.
What’s your point?
We "in the know" might agree, but we're not going to get it sold.
The designer, in my experience, is totally fine with just using a normal select element, they don't demand that I reinvent the drop-down with divs just to put rounded corners on the options.
Nobody cares about that stuff. These are minor details, we can change it later if someone really wants it. As long as we're not just sitting on our hands for lack of work I'm not putting effort into reinventing things the browser has already solved.
Let me assure you, devs were skeptical about all this well before AI.
A lot of the culture is built by certain people who make a living out of package maximalism.
More packages == more eyballs == more donations.
They have an agenda that small packages are good and made PRs into popular packages to inject their junk into the supply chain.
Yes, I remember thinking at the time "how are people not ashamed to install this?"
NPM is good for building your own stack but it's a bad idea (usually) to download the Internet. No dep system is 100% safe (including AI, generating new security vulns yay).
I'd like to think that we'll all stop grabbing code we don't understand and thrusting it into places we don't belong, or at least, do it more slowly, however, I also don't have much faith in the average (especially frontend web) dev. They are often the same idiots doing XYZ in the street.
I predict more hilarious (scary even) kerfuffles, probably even major militaries losing control of things ala Terminator style.
In fact, when we did have Java in the browser it was loaded with security issues primarily because of the much greater complexity of the Java language.
Anyways, I think you are missing the forest for the trees if you think this is a Java vs JavaScript comparison, don't worry it's also possible to produce junk enterprise code too...
Just amusing watching people be irrationally scared of one language/ecosystem vs another without stopping to think why or where the problems are coming from.
The issue here is not Java or it's complexity. The point is also not Java, it's incidental that it was popular at the time. It's people acting irrationally about things and jumping ship for an even-worse system.
Like, yes, if that really were the whole attack surface of JS, sure nobody would care. They also wouldn't use it...and nothing we cared about would use it either...
But the reason Java is more secure than JavaScript in the context of supply chain attacks is fourfold:
1. Maven packages don't have install scripts. "Installing" a package from a Maven repository just means downloading it to a local cache, and that's it.
2. Java code is loaded lazily on demand, class at a time. Even adding classes to a JAR doesn't guarantee they'll run.
3. Java uses fewer, larger, more curated libraries in which upgrades are a more manual affair involving reading the release notes and the like. This does have its downsides: apps can ship with old libraries that have unfixed bugs. Corporate users tend to have scanners looking for such problems. But it also has an upside, in that pushing bad code doesn't immediately affect anything and there's plenty of time for the author to notice.
4. Corporate Java users often run internal mirrors of Maven rather than having every developer fetch from upstream.
The gap isn't huge: Java frameworks sometimes come with build system plugins that could inject malware as they compile the code, and of course if you can modify a JAR you can always inject code into a class that's very likely to be used on any reasonable codepath.
But for all the ragging people like to do on Java security, it was ahead of its time. A reasonable fix for these kind of supply chain attacks looks a lot like the SecurityManager! The SecurityManager didn't get enough adoption to justify its maintenance costs and was removed, partly because of those factors above that mean supply chain attacks haven't had a significant impact on the JVM ecosystem yet, and partly due to its complexity.
It's not clear yet what securing the supply chain in the Java world will look like. In-process sandboxing might come back or it might be better to adopt a Chrome-style microservice architecture; GraalVM has got a coarser-grained form of sandboxing that supports both in-process and out-of-process isolation already. I wrote about the tradeoffs involved in different approaches here:
https://blog.plan99.net/why-not-capability-languages-a8e6cbd...
[1] https://medium.com/graalvm/writing-truly-memory-safe-jit-com...
Just because dependencies do a lot more than you need, doesn't mean you should automatically reach for the smallest dependency that fits your needs.
If you need 5 of the dozens of Lodash functions, for instance, it might be best to just install Lodash and let your build step shake out any unused code, rather than importing 5 new dependencies, each with far fewer eyes and release-management best practices than the Lodash maintainers have.
Whether to take a dependency is a tricky thing that really comes down to engineering judgement- the thing that you (the developer) are paid to make the calls on.
It’s not even a judgement call at this point. It’s more aligned with buckling your seatbelt, pointing your car off the road, closing your eyes, flooring it and hoping for a happy ending.
Or At the time of adding you can add a NOTE or FIXME comment stating where you copied it from. A quick grep for such keyword can give you a nice overview of nice to have stuff. You can also add a ticket with all the details if you're using a project management tool and resuscitate it when that hypothetical moment happens.
But again, if the discussion is going to be that specific, then you would need to provide actual examples, so that we could judge, whether we would implement that ourselves or it would be difficult to do so. Note, that often it is also not required for ones use-case, to have a 100% matching behavior either. The goal is not to duplicate lodash. The purpose of the extracted or reimplemented function would still be ones own project, where the job of that function might be much more limited.
https://github.com/lodash/lodash/blob/main/dist/lodash.js#L7...
So you also need to copy isArrayLikeObject, baseDifference and baseFlatten.
For baseDifference, you also need to copy arrayMap and baseUnary.
For baseFlatten, you also need to copy arrayPush.
For isArrayLikeObject, you also need to copy isArrayLike and isObjectLike.
For isArrayLike, you also need to copy isLength and isFunction.
For isFunction, you also need to copy isObject and baseGetTag.
For baseGetTag, you also need to copy getRawTag and objectToString.
I don’t have time to dig any deeper, just use tree-shaking ffs.
Tree shaking however, will not help you, if you have to first install a library using NPM. It will only help you reduce overhead in the code served to a browser. Malicious code can run much earlier, and would be avoided, if you rewrite or extract relevant code from a library, avoiding to install the library using NPM. Or is there some pre-installation tree shaking, that I am unaware of? That would actually be interesting.
npm install stdlib
…but double checked before and @stdlib/stdlib has 58 dependencies, so the joke preempted me.If you’re gonna reimplement only thr code you need from a dependency, it’s hard to know of the stuff you’re leaving out how much is just extra stuff you don’t need and how much might be security fixes that may not be apparent to you but the dependency by virtue of being worked upon and used by many people has fixed.
In both cases, the solution is similar: try to restrict access to vital systems only to those you trust,so that you have less need to audit their every move.
Your system administrators can access the server room, but the on-site barista can't. Your HTTP server is trusted enough to run in prod, but a color-formatting library isn't.
Your employees are carefully vetted before hiring. You've got their names, addresses, and social security numbers. There's someone you're able to hold accountable if they steal from you or start breaking everything in the office.
This seems more like having several random contractors who you've never met coming into your business in the middle of night. Contractors that were hired by multiple anonymous agencies you just found online somewhere with company names like gkz00d or 420_C0der69 who you've also never even spoken to and who have made it clear that they can't be held accountable for anything bad that happens. Agencies that routinely swap workers into or out of various roles at your company without asking or telling you, so you don't have any idea who the person working in the office is, what they're doing, or even if they're supposed to be there.
"To make thing easier for us we want your stuff to require the use of a bunch of code (much of which does things you don't even need) that we haven't bothered looking at because that'd be too much work for us. Oh, and third parties we have no relationship with control a whole bunch of that code which means it can be changed at any moment introducing bugs and security issues we might not hear about for months/years" seems like it should be a hard sell to a boss or a client, but it's sadly the norm.
Assuming that something is going to go wrong and trying to limit the inevitable damage is smart, but limiting the amount of untrustworthy code maintained by the whims of random strangers is even better. Especially when the reasons for including something that carries so much risk is to add something trivial or something you could have just written yourself in the first place.
Sounds very similar to how global SIs staff enterprise IT contracts.
You only need to rewrite what you use, which for many (probably most) libraries will be 1% or less of it
Another 20% or more is https://pypi.org/project/requests/ and its dependencies — this is an extremely popular project despite that the standard library already provides the ability to make HTTPS connections (people just hate the API that much). One of requests' dependencies is certifi, which is basically just a .pem file in Python package form. The vendored requests has not seen any tree-shaking as far as I can tell.
This sort of thing is a big part of why I'll be able to make PAPER much smaller.
There is no need to rewrite dependencies. Sometimes it just so happens that a project can live without outputting fancy colorful text to stdout, or doesn't need to spread transitive dependencies on debug utilities. Perhaps these concerns should be a part of the standard library, perhaps these concerns are useless.
And don't get me started on bullshit polyfill packages. That's an attack vector waiting to be exploited.
a lor of these dependencies are higher order function definitions, which never change, and could be copy/pasted around just fine. they're never gonna change
When you code in a high-security environment, where bad code can cost the company millions of dollars in fines, somehow you find a way.
The sibling commenter is correct. You write what you can. You only import from trusted, vetted sources.
No need to. But also no need to pull in a dependency that could be just a few lines of own (LLM generated) code.
... and now you've switched the attack vector to a hostile LLM.
A few lines of code can be audited.
Creating two problems, where there was one.
The true threat here isn't the immediate dependency though, it's the recursive supply chain of dependencies. "trustworthy" doesn't make any sese either when the root cause is almost always someone trustworthy getting phished. Finally if I'm not capable of auditing the dependencies it's unlikely I can replace them with my own code. That's like telling a vibe coder the solution to their brittle creations is to not use AI and write the code themselves.
In both cases, actually doing the work and writing a function instead of adding a dependency or asking an AI to write it for you will probably make you a better coder and one who is better able to audit code you want to blindly trust in the future.
It's still neither realistic nor helpful advice.
If they are not, something is bad and the dependency should be "reduced" if at all possible.
I wonder to which extent is the extreme dependency count a symptom of a standard library that is too minimalistic for the ecosystem's needs.
Perhaps this issue could be addressed by a "version set" approach to bundling stable npm packages.
I always tried to keep the dependencies to a minimum.
Another thing you can do is lock versions to a year ago (this is what linux distros do) and wait for multiple audits of something, or lack of reports in the wild, before updating.
(Big fan of version pinning in basically every context, too)
Use NPM Package Cooldown Check
The NPM Cooldown check automatically fails a pull request if it introduces an npm package version that was released within the organization’s configured cooldown period (default: 2 days). Once the cooldown period has passed, the check will clear automatically with no action required. The rationale is simple - most supply chain attacks are detected within the first 24 hours of a malicious package release, and the projects that get compromised are often the ones that rushed to adopt the version immediately. By introducing a short waiting period before allowing new dependencies, teams can reduce their exposure to fresh attacks while still keeping their dependencies up to date.
Having secrets in a different security context, like root/secretsuser-owned secret files only accessible by the user for certain actions (the simplest way would be eg. sudoers file white listing a precise command like git push), which would prevent arbitrary reads of secrets.
The other part of this attack, creating new github actions, is also a privilege, normal users dont need to exercise that often or unconstrained. There are certainly ways to prevent/restrict that too.
All this "was a supply chain attack" fuzz here is IMO missing the forest for the trees. Changing the security context for these two actions is easier to implement than supply chain analysis and this basic approach is more reliable than trusting the community to find a backdoor before you apply the update. Its security 101. Sure, there are post-install scripts that can attack the system but that is a whole different game.
It is insane to me how many developers update dependencies in a project regularly. You should almost never be updating dependencies, when you do it should be because it fixes a bug (including a security issue) that you have in your project, or a new feature that you need to use.
The only time this philosophy has bitten me was in an older project where I had to convince a PM who built some node project on their machine that the vulnerability warnings were not actually issues that affected our project.
Edit: because I don't want to reply to three things with the same comment - what are you using for dependencies where a) you require frequent updates and b) those updates are really hard?
Like for example, I've avoided updating node dependencies that have "vulnerabilities" because I know the vuln doesn't affect me. Rarely do I need to update to support new features because the dependency I pick has the features I need when I choose to use it (and if it only supports partial usage, you write it yourself!). If I see that a dependency frequently has bugs or breakages across updates then I stop using it, or freeze my usage of it.
In NPM world, there's so much churn that it would be comical if not for the security aspects.
That one is a funny example in this context. If you were drifting far behind on updates, so far that you were still on the obsolete log4j 1.x, you were immune to that vulnerability (log4shell). That obsolete log4j version had other known vulnerabilities, but most of them on rarely used optional components, and none of them affected basic uses of it to log to the console or disk. And even better, there were so many people using that obsolete log4j version, that a binary compatible fork quickly appeared (reload4j) which just removes the vulnerable components (and fixes everything that wasn't removed); it takes 10 minutes to update to it, or at worst 1 hour if you have to tweak your dependencies to exclude the log4j artifact.
(And then it happened again, this time with Spring (spring4shell): if you were far behind on updates, so far that you were still on the very old but still somewhat supported Java 8, you were immune to that vulnerability.)
Otherwise, I agree with the sentiment that too many people try to update the world too often. Keeping up with runtime updates as often as possible (node.js is more trusted than any given NPM module) and updating only when dependencies are no longer compatible is a better middle ground.
There really is no good solution other than to reduce the surface area for vulnerabilities by reducing the total amount of code you depend on (including third-party code). In practice, this means using as few dependencies as possible. If you only use one or two functions from lodash or some other helper library, you're probably better off writing or pulling in those functions directly instead.
This has bitten me so many times (usually at large orgs where policy is to be conservative about upgrades) that I can't even consider not upgrading all my dependencies at least once a quarter.
For Python's uv, you can do something like:
> uv lock --exclude-newer $(date --iso -d "2 days ago")
That's the secret lots of enterprises have relied on for ages. Don't be bleeding edge, let the rest of the world gineau pig the updates and listen for them to sound the alarm if something's wrong. Obviously you do still need to pay attention to the occasional, major, hot security issues and deal with them in a swift fashion.
Another good practice is to control when your updates occur - time them when it's ok to break things and your team has the bandwidth to fix things.
This is why I laughed hard when Microsoft moved to aggressively push Windows updates and the inevitable borking it did to people's computers at the worst possible times ("What's that you said? You've got a multi-million dollar deliverable pitch tomorrow and your computer won't start due to a broken graphics driver update?). At least now there's a "delay" option similar to what you described, but it still riles me that update descriptions are opaque (so you can't selectively manage risk) and you don't really have the degree of control you ought to.
Measures like this also aren't meant to be "final solutions" either, but stop-gaps. Slowing the spread can still be helpful when a large scale attack like this does occur. But I'm also not entirely sure how much that weighs against potentially slowing the discovery as well.
Ultimately this is still a repository problem and not a package manager one. These are merely band-aids. The responsibility lies with npm (the repository) to implement proper solutions here.
> The responsibility lies with
And I agree that if everyone did this, it would slow down finding issues in new releases. Not really sure what to say to that... aside from the selfish idea that if I do it, but most other people don't, it won't affect me.
It would not solve for a bad actor gaining trust over years, then contributing seemingly innocent code that contains an exploitable bug with enough plausible deniability to remain on the team after it is patched.
I do wish there were some lists of compromised versions, that package managers could disallow from.
> In most cases, such attacks are discovered quickly and the malicious versions are removed from the registry within an hour.
By delaying the infected package availability (by "aging" dependencies), we're only delaying the time, and reducing samples, until it's detected. Infections that lay dormant are even more dangerous than explosives ones.
The only benefit would be if, during this freeze, repository maintainers were successfully pruning malware before it hits the fan, and the freeze would give scanners more time to finish their verification pipelines. That's not happening afaik, NPM is crazy fast going from `npm publish` to worldwide availability, scanning is insufficient by many standards.
From what I can tell, even a few hours of delay for actually pulling dependencies post-publication to give security tools a chance to find it would have stopped all (?) recent attacks in their tracks.
It's best to never use `^` and always specify exact version, but many maintainers apparently can't be bothered with updating their dependencies themselves so it became the default.
And larger dependencies that can be trusted in larger blocks. I'll bet half of a given projects dependencies are there to "gain experience with" or be able to name drop that you've used them.
Less is More.
We used to believe that. And then W3C happened.
I think Github's Dependabot can help you here. You can also host your own little instance of DependencyTrack and keep up to date with vulnerabilities.
You can do it:
https://github.blog/changelog/2025-07-01-dependabot-supports...
https://docs.renovatebot.com/configuration-options/#minimumr...
https://www.stepsecurity.io/blog/introducing-the-npm-package...
For example, whenever a new version of a package is released, it's published to the repository but not allowed to be installed for at least 48 hours, and this gives time to any third-party observers to detect a malware early.
You can do this with npm (since version 6.9.0).
To only get registry deps that are over a week old:
$ npm install --before="$(date -v -7d)"
Source: Darcy Clarke - https://bsky.app/profile/darcyclarke.me/post/3lyxir2yu6k2sYou need an EDR and code repo scanner. Exploring this as a technical problem of the infrastructure will accomplish. The people that create these systems are long gone and had/have huge gaps in their capabilities to stop creating these problems.
The problem with this approach is you need a certain number of guinea pigs on the bleeding edge or the outcome is the same (just delayed). There is no way for anyone involved to ensure that balance is maintained. Reducing your surface area is a much more effective strategy.
The signal desktop app is an electron app. Presumably it has the same problem.
Does anyone know of any reasonable approaches to using npm securely?
“Reduce your transitive dependencies” is not a reasonable suggestion. It’s similar to “rewrite all the Linux kernel modules you need from scratch” or “go write a web browser”.
I would love to have something like that "in the open"…
Oh please, do not compare writing bunch of utilities for you "app" with writing a web browser.
It's common among grizzled software engineering veterans to say "Check in the source code to all of your dependencies, and treat it as if it were your own source code." When you do that, version upgrades are actual projects. There's a full audit trail of who did what. Every build is reproducible. You have full visibility into all code that goes into your binary, and you can run any security or code maintenance tools on all of it. You control when upgrades happen, so you don't have a critical dependency break your upcoming project.
Perhaps I’m just ignorant of web development, but why not? We do so with our desktop software.
https://docs.renovatebot.com/configuration-options/#minimumr...
https://docs.renovatebot.com/presets-default/#enablevulnerab...
Depending on a commercial service is out of the question for most open source projects.
agpl is a no go for companies not intending to ever contribute anything back. good riddance.
Criticism: "You should shower every day"
Defense: "OH, maybe I should shower every hour, to the point where my skin dries and I can't get my work done because I'm in the shower all day."
No, there's a pretty standard way of doing things that you can care to learn, and it's very feasible, people shower every day during the week, sometimes they skip if they don't go out during weekends, if it's very cold you can skip a day, and if it's hot you can even shower twice. You don't even need to wash your hair every day. There's nuance that you can learn if you stop being so defeatist about it.
Similarly, you can of course install stripe-js since it's vendored from a paid provider with no incentive to fuck you with malware and with resources to audit dependency code, at any rate they are already a dependency of yours, so adding an npm package does not add a vendor to your risk profile.
Similarly you can add react-hook-form if it's an official react package, however if it isn't, then it's a risk, investigate who uploads it, if it's a random from github with an anime girl or furry image in their profile, maybe not. Especially if the package is something like an unofficial react-mcp-dotenv thing where it has access to critical secrets.
Another fallacy is that you have to rewrite the whole dependency you would otherwise import. False. You are not going to write a generic solution for all use cases, just for your own, and it will be tightly integrated and of higher quality and less space (which helps with bandwidth, memory and CPU caching), because of it. For god's sake, you used an example relating to forms? We've had forms since the dot com boom, how come you are still having trouble with those? You should know them like the back of your hand.
Example:
Argument: People should be able to build whatever they want on their own property.
Reductio ad Absurdum position: I propose to build the world's largest Jenga tower next to your house.
Note that this does not take into account any counter arguments such as 'if it falls on me you will still be liable for negligence', but it makes a point without violating the logic of the original argument. To violate that logic would indeed be a straw man.
This is very dead internet theory, but not automated, someone copied my comment, gave it to chatgpt, and returned the chatgpt answer, presumably passing it off as their own, but in effect we are talking with chatgpt lol.
But I think for untrusted third party code, it's much better to copy the code by hand, that way you are really forced to audit it. There really isn't much of an advantage to copying an install.sh script compared to just downloading a running the .sh, whereas writing the actual .sh commands on the command line (and following any other URLs before executing them) is golden.
I don't think this is a particularly unreasonable take; I'm a relative novice to the JS ecosystem, and I don't feel this uncomfortable taking on dependencies as I do in pretty much any other ecosystem I participate in, even those (like Rust) where the dependency counts can be high.
I acknowledge that it is my responsibility to drive safely, and I take that responsibility seriously. But I still wear a seat belt and carry auto insurance.
To clarify - I dont think it is naive to assume the software is as-is with all responsibilities on the user since that is exactly what lawyers have made all software companies say that for over 50 years.
How can we promise to "do better" when shit like "no author or distributor accepts responsibility to anyone for the consequences of using it or for whether it serves any particular purpose or works at all" is in the legal agreement of the software you are using?
Making someone agree to that while simultaneously on the side making promises that the software works is used car salesman gimmicks. The only things that matters is what you put in writing.
One way or another that will end.
Free Software will have the same responsibilities. If you write software, negligently, and it causes damage, you will be liable
I should not be able to make a Crypto wallet that is easy to hack and distribute it without consequence
This will be a very good thing
We know how to make secure 4eliable software (some of us) but nobody will pay for it
The general solution is to do what Debian does.
Keep a stable distro where new packages aren't added and versions change rarely (security updates and bugfixes only, no new functionality). This is what most people use.
Keep a testing/unstable distro where new packages and new versions can be added, but even then added only by the distro maintainer, NOT by the package developers. This is where the audits happen.
NPM, Python, Rust, Go, Ruby all suffer from this problem, because they have centralized and open package repositories.
Adding friction to the sharing of code doesn't absolve developers from their decision to blindly trust a ridiculous amount of third-parties.
Vulnerabilities in Linux distro packages obviously happen. But a single developer cannot push code directly into for example Debian and compromise the world.
This is what happens when nobody pays for anything and nobody feels they have a duty to do good work for free.
Weirdly, some of the worst CVE I can think of were with enterprize software.
If packages had to be cryptographically signed by multiple verified authors from a per-organization whitelist in order to enter distribution, that would cut down on the SPOF issue where compromising a single dev is enough to publish multiple malware-infested packages.
How can you guarantee a long trusted developer doesn't have a gun pointed to their head by their authoritarian govt?
In our B2B shop we recently implemented a process where developers cannot add packages from third party sources - only first party like meta, google, spring, etc are allowed. All other boilerplate must be written by developers, and on the rare occasion that a third party dependency is needed it's copied in source form, audited and re-hosted on our internal infrastructure with an internal name.
To justify it to business folks, we presented a simple math where I added the man-hours required to plug the vulnerabilities with the recurring cost of devsecops consultants and found that it's cheaper to reduce development velocity by 20-25%.
Also devsecops should never be offshored due to the scenario I presented in my second statement.
* You are trusting large numbers of trustworthy developers.
* You have established a means of validating their trustworthiness: only trust reputable "first-party" code.
I think what you're doing is a pretty good system. However, there are ways to include work by devs who lack "first-party" bona-fides, such as when they participate in group development where their contributions are consistently audited. Do you exclude packages published by the ASF because some contributions may originate from troublesome jurisdictions?
In any case, it is not necessary to solve the traitorous author problem to address the attack vector right in front of us, which is compromised authors.
If you add jest, the popular test runner by Meta, that's adding 300 packages to your dependency graph.
And here we don't yet have a bundler, linter, code formatter, or even web framework.
So good luck with minimizing those dependencies.
For what it's worth, back when I was active at the ASF we used to vote on releases — you needed at least 3 positive votes from a whitelist of approved voters to publish a release outside the org and there was a cultural expectation of review. (Dunno if things have changed.) It would have been very difficult to duplicate this NPM attack against the upstream ASF release distribution system.
"Large numbers of trustworthy dependency authors in your town can't wait to show you their hottest code paths! Click here for educational livecoding sessions!"
Establishing a false identity well enough to fool a FOSS author or organization is a lot of work. Even crafting a spear phishing email/text campaign doesn't compare to the effort you'd have to put in to fool a developer well enough to get offered publishing privileges.
Of course it's possible, but so are beat-them-with-a-five-dollar-wrench attacks.
I do not know about NPM. But in Rust this is common practice.
Very hard to avoid. The core of Rust is very thin, to get anything done typically involves dozens of crates, all pulled in at compile time from any old developer implicitly trusted.
Most projects will have a healthy 5-20 dependencies though, with very little nested modules.
At this rate, there's a non-zero chance that one of the transitive dependencies is SQLite itself.
Those numbers are way off their actual number.
That JLR got their factories hacked, rather than customer cars, is less bad for sure. But it's still pretty bad.
Also, before arguing that code generators should get a pass as they don't “end up in the final product”, you really should read “Reflections on trusting trust” by Ken Thompson.
That's bullshit, pure and simple. If you pull in a deeply nested dependency like icu_normalizer it has 30 dependencies, OMGHAXOZRS. I'm doing this, so I don't have to spend a day going through the library.
Except of the 30 depedencies crates, there are 10 from ICUX repository, and then you have almost standard dependencies like proc-macro/syn/quote crates from dtolnay, `zerofrom` from Google. `smallvec` from the Servo project, and yoke from... checks notes... from ICUX.
The only few remaining crates here are `write16`, `utf8_iter` and `utf16_iter` that are written from hsivonen, who is also a ICUX contributor.
So even for 30 dependencies, you actually depend on proc-macro/syn/quote which are foundational crates. Few crates from Google, few crates from Servo, and three crates written by another ICUX contributor.
We started with 30 dependencies and ended up with 3 strangers
But how do we scale that to 1000 dependencies, and every one of their updates? What tools are there to help us, and does the community at large use them?
What I really don't like, and why I wrote that it's a culture issue, is the lightness with which these decisions are often made.
My most popular library has about a dozen dependencies. The README states clearly and “above the fold” what are the core deps (3, no transitive). Every other dependency is either first party, or optional and justified with a comment in the deps file (if you don't use the optional feature, it doesn't end up in your deps file).
There's also a generated BLOB. The generation of the BLOB is reproducible in your own environment, and its provenance attestated.
Those are all risks, that I'm passing on to my users, but I do my best to mitigate them, and communicate this clearly to them.
Isn't it actually the case that you started with 3 strangers, but 27 of them were relatively easy (still took some time) to figure out as safe?
You could of course investigate individuals commits, but that's probably an overkill.
For example, an upstream bumps a version of a lint tool and/or changes style across the board. Often these are labelled "chore". While I agree it's nice to have consistent style, in some projects it seems to be the majority of the changes between releases. Due to the difficulty in auditing this, I consider this part of the software supply chain problem and something to be discouraged. Unless there's actually reason to change code (eg. some genuine refactoring a human thinks is actually needed, a bug fix or new feature, a tool exposed a real bug, or at least some identifiable issue that might turn into a bug), it should be left alone.
> Unless there's actually reason to change code (eg. some genuine refactoring a human thinks is actually needed, a bug fix or new feature, a tool exposed a real bug, or at least some identifiable issue that might turn into a bug), it should be left alone.
The corollary to this is "Unless there's actually a need for new features that a new version provides, your existing dependency should be left alone". In other words things should not be automatically updated. This is unfortunately the crazy path we've gone down, where when Package X decides to upgrade, everyone believes that "the right thing to do" is for all its dependencies to also update to use that and so on down the line. As this snowballs it becomes difficult for any individual projects to hold the line and try to maintain a slow-moving, stable version of anything.
Edit: also, consider how much of https://github.com/Wilfred/difftastic/commits/master/ is just noise in itself. 15k commits for a project that appears to only be about four years old.
While the crates ecosystem is certainly not immune to supply chain attacks this over generalization is not justified.
There are several features that make crates.io more robust than npm. One of them is that vulnerable versions can be yanked without human intervention. Desperate comments from maintainers like this one[1] from just a few days ago would not happen with crates.io.
There are also features not provided by crates.io that make the situation better. For example you could very easily clone the repo and run
cargo vet
to check how many of the packages had human audits. I'd done it if I was on a computer, but a quick glance at the Cargo.lock file makes me confident that you'd get a significant number.FWIW npm used to allow unpublishing packages, but AFAIK that feature was removed in the wake of the left-pad incident [1]. Altho now with all the frequent attacks, it might be worth considering if ecosystem disruption via malicious removal of pacakge would be lesser of two evils, compared to actual malware being distributed.
In both JavaScript and Rust, it's normal/encouraged to just add a tiny dependency to the package manager. The communities even pride themselves, that they have such good package managers to allow this.
It's this "yeah, there is a crate for this tiny function I need, let's just include it" mentality that makes the ecosystem vulnerable.
People need to be responsible for whatever they include, either pay the price by checking all versions up front, or pay it by risking shipping a vulnerable program that it's much harder to retract than a JavaScript frontend.
They collect package managers like funko pops.
I'm not quite sure about the goal. Maybe some more C# dev kit style rug-pulls where the ecosystem is nominally open-source but MS own the development and distribution so nobody would bother to compete.
Things like: Once a package has more than [threshold] daily downloads for an extended period of time, it requires 2FA re-auth/step-up on two separate human-controlled accounts to approve any further code updates.
Or something like: for these popular packages, only a select list of automated build systems with reproducible builds can push directly to NPM, which would mean that any malware injector would need to first compromise the source code repository. Which, to be fair, wouldn't necessarily have stopped this worm from propagating entirely, but would have slowed its progress considerably.
This isn't a "sacrifice all of NPM's DX and decentralization" question. This is "a marginally more manual DX only when you're at a scale where you should be release-managing anyways."
Someone could pony up the cash to send out a few thousand yubikeys for this and we'd all be a lot safer.
If we want decentralized package management for node/javascript, you need to dump NPM - why not something like Go's system which is actually decentralized? There is no package repository/registry, it's all location based imports.
And true 2FA means you can't automate publishing from github's CI. Python is going the other direction. There is a fake 2FA that is just used to generate tokens and there is a preferential channel to upload to pypi via github's CI.
But in my opinion none of this helps with security. But it does help to de-anonymise the developers, which is probably what they really want to do, without caring if those developers get hacked and someone else uses their identity to do uploads.
(I did a talk at minidebconf last year in toulouse about this).
If implemented like this, it's completely useless, since there is actually no 2fa at all.
Anyway the idea of making libre software developers work more is a bad idea. We do it for fun. If we have to do corporate stuff we want a corporate salary to go with.
Except most projects have 1 developer… Plus, if I develop some project for free I don't want to be wasting time and work for free for large rich companies. They can pay up for code reviews and similar things instead of adding burden to developers!
Linux distros can't even provide all the apps users want, that's why freshmeat existed and we have linuxbrew, flatpak, Ubuntu multiverse, PPA, third party Debian repositories, the openSUSE Buildservice, the AUR, ...
There is no community that has the capacity to audit and support multiple branches of libraries.
The problem with this approach is that frameworks tend to "expire" pretty quickly and you can't run anything for too long on Debian until the framework is obsolete. What I mean by obsolete is Debian 13 ships with Golang 1.24, A year from now it's gonna be Golang 1.26 - that is not being made available in trixie. So you have to find an alternative source for the latest golang deb. Same with PHP, Python etc. If you run them for 3 years with no updated just some security fixes here and there, you're gonna wake up in a world of hurt when the next stable release comes out and you have to do en-masse updates that will most likely require huge refactoring because syntax, library changes and so on.
And Javascript is a problem all by itself where versions come up every few months and packages are updated weekly or monthly. You can't run any "modern" app with old packages unless you accept all the bugs or you put in the work and fix them.
I am super interested in a solution for this that provides some security for packages pushed to NPM (the most problematic repository). And for distributions to have a healthy updated ecosystem of packages so you don't get stuck who knows for how long on an old version of some package.
And back to Debian, trixie ships with nginx 1.26.3-3+deb13u1. Why can't they continuously ship the latest stable version if they don't want to use the mainline one?
One idea I've had is that publishing is open as today, but security firms could offer audit signatures.
So a company might pay security firms and only accept updates to packages that have been audited by by 1,2,3 or more of their paid services.
Thus money would be paid in the open to have eyes on changes for popular packages and avoid the problem of that weird lone maintainer in northern Finland being attacked by the Chinese state.
Unfortunately most people don't want old software that doesn't support newer hardware so most people don't end up using Debian stable.
Most people don't want old software because they don't want old software.
They want latest features, fixes and performance improvements.
Or is this just a "don't use Linux" gripe?
"old" is a strange way to spell "new, unstable, and wormed".
I want old software. Very little new features are added to most things i care about, mostly it is just bloat, AI slop, and monthly subscription shakedowns being added to software today.
1. Using an operating system with no package management 2. Poor developer discipline, i.e. developers always trying to use the latest version of a package.
So now we have lots of poorly implemented language package managers, docker containers on top being used as another package management layer (even though that's not their primary purpose but many people use the like that) and the security implications of pulling in lots of random dependencies without any audit.
Developing towards a stable base like Debian would not be a pancea, but alliviate the problems by at least placing another audit layer in between.
1. You don't want to tie your software to the OS. Most people want their software to be cross-platform. Much better to have a language-specific package manager because I'm using the same language on every OS. And when I say "OS" here, I really mean OS or Linux distro, because Linux doesn't have one package manager.
2. OS package managers (where they even exist), have too high a bar of entry. Not only do you have to make a load of different packages for different OSes and distros, but you have to convince all of them to accept them. Waaay too much work for all but the largest projects.
You're probably going to say "Good! It would solve this problem!", but I don't think the solution to package security is to just make it so annoying nobody bothers. We can do better than that.
However we are talking in the context of NPM packages which by the vast majority would be running inside a container on some server. So how could that software not use a stable Debian base for example.
And arguing that package management is to complicated is a bit ridiculous considering how many workloads are running in docker containers which I'd argue are significantly more complex
Even if we make every project create packages in every package manager, it still wouldn't add any auditing.
Linux distros could still stand to improve here in a bunch of ways, and it seems that a well-designed package ecosystem truly doesn't need such hooks at the level of the package manager at all. But this kind of auditing is one of the useful functions of downstream software distros for sure.
I don't know how you avoid this problem
Can you point me to Go's centralized package repository?
For what it's worth, our code is on GitLab
Then what's the difference between git and npm, cargo, pypi, mvn et al?
In practice, little difference between Go's use of Github and Python's use of PyPI. Someone at Microsoft with root access could compromise everyone.
That's why I'm putting emphasis on it, because to Go it is.
And to languages that actually have centralized package repositories it isn't. There is a difference between code and packages and Go simply does not have the latter (in the traditional sense - what Go calls a package is a collection of source files in the same directory that are compiled together within a module (a module is a collection of packages (again, code) that are released, versioned, and distributed together. Modules may be downloaded directly from version control repositories or via proxy servers)).
To the other languages mentioned above, packages may have binaries, metadata and special script hooks. There is a package manager like pip , cargo or npm and if you want to install one, you won't have to specify a URL because there is a canonical domain to go to.
Go just knows code and it'll use git, hg or even svn. And if you want to claim that lots of open-source code being on GitHub makes it special, then
> GitHub is every single programming language's centralized package repository
and
> Someone at Microsoft with root access could compromise every user of every single programming language
95% of Python packages are installed from PyPI, but just like Go can also install from non-Github sources, Python supports installing from other non PyPI indexes[0] or even from a Git repository directly[1] like Go.
> what Go calls a package is a collection of source files in the same directory
What is it that you imagine Python or NPM packages consist of? Hint: A Python .whl file is just a folder in a zip archive (Python also supports source distributions directly analogous to Go)
[0] https://docs.astral.sh/uv/concepts/indexes/
[1] https://thelinuxcode.com/install-git-repository-branch-using...
So "GitHub is every single programming language's centralized package repository, because lots of code is hosted there" ?
> Python supports installing from other non PyPI indexes > 95% of Python packages are installed from PyPI, but just like Go can also install from non-Github sources, Python supports installing from other non PyPI indexes[0] or even from a Git repository directly[1] like Go.
And yet there is a clear difference between source distributions and pip/npm/rubygem/cargo packages - and between tooling/ecosystems that ONLY support the former and those that MAY use either and unfortunately mostly use the latter.
> What is it that you imagine Python or NPM packages consist of?
Something like a script that runs as part of the package that downloads a tarball, modifies package.json, injects a local bundle.js and runs npm publish (see this post). Usually also hosted at the default, centralized, authoritative source run by the maintainers of the package management tool.
But I'm repeating myself.
> (or do they? do they just use https to check out?)
Maybe try it out or read the docs first.
I'm closing with this:
> NPM, Python, Rust, Go, Ruby all suffer from this problem, because they have centralized and open package repositories.
is either wrong or disingenuously misleading, requiring nothing to apply to every single thing, depending on how you slice your definitions. It does not hold any water, that is my entire argument.
The reason these compromised packages typically don't make it in to e.g. Debian is because this all tends to be discovered quite quickly, before the package maintainer has a chance to update it.
Just wondering: while this is less of an attack surface, it's still a surface?
Kinda like Stackoverflow for reviews, with optional identification and such.
And honestly an LLM can strap a "probably good" badge on things with cheap batch inference.
At the end of the day, it’s all a URL.
You’re asking for a blessed set of URLs. You’d have to convince someone to spend time maintaining that.
That said, I would guess the 'bigger conversation' is that it is much harder to tpyo <<import "github.com/DataaDog/datadog-api-client-go/v2/api/datadogV2">> than $(npm i dataadog) or similar in a "flat" package namespace (same for its $(uv pip install dataadog) friend)
None of those cited ones fix the dependency lineage issue, proving that release 1.1 was authored by the same chain of custody as release 1.0 of any given package. One can opt in to gpg verified dependencies in Maven, but it is opt-in. The .jar artifacts can also be cryptographically signed, but the risk that's trying to drive down is tamperproofing and not lineage, AFAIK
The problem comes when you want to upgrade your dependencies. How do you know that they are trustworthy on first use?
If you ask these people, distributions are terrible and need to die.
Python even removed PGP signatures from Pypi because now attestation happens by microsoft signing your build on the github CI and uploading it directly to pypi with a never expiring token. And that's secure, as opposed to the developer uploading locally from their machine.
In theory it's secure because you see what's going in there on git, but in practice github actions are completely insecure so malware has been uploaded this way already.
Benefit from this feature.
These attacks may just be the final push I needed to take server rendering (without js) more seriously. The HTMX folks convinced me that I can get REALLY far without any JavaScript, and my apps will probably be faster and less janky anyway.
However, processes and practices around NodeJS and npm are in dire need of a security overhaul. leftpad is a cultural problem that needs to be addressed. To start with, snippets don't need to be on npm.
Sure, there will be a step/stage that will require access to NPM publish credentials to publish to NPM. But why does this stage need to execute any code except a very small footprint of vetted code? It should just pickup a packaged, signed binary and move it to NPM.
The compilation/packaging step on the other hand doesn't need publishing rights to NPM. Ideally, it should only get a filesystem with the sources, dependencies and a few shared libraries and /sys or /proc dependencies it may need to function. Why does some dependency downloading need access to your entire filesystem? Maybe it needs some allowed secrets, but eh.
It's certainly a lot of change into existing pipelines and ideas, and it's certainly possible to poke holes into there if you want things to be easy. But it'd raise the bar quite a bit.
This said, less potential vendors supplying packages 'may' reduce exposure, but doesn't remove it.
Either way, not running the bleeding edge packages unless it's a known security fix seems like a good idea.
- npm should require 2FA disallow tokens for publishing. This is an option, but it should be a requirement.
- npm should require using a trusted publisher and provenance for package with over 100k downloads a week and their dependencies.
- Github should require a 2FA step for automated publishing
- npm should add a cool down period where if won't install brand new packages without a flag
- npm should stop running postinstall scripts.
- npm should have an option to not install packages without provenance.
I would bet that you'll find a third party `leftpad` implementation in org.apache.commons or in Spring or in some other collection of utils in Java. The difference isn't the need for 3rd party software to fix gaps in the standard library - it's the preference for hundreds of small dependencies instead of one or two larger ones.
JS apps, despite the HN narrative, have a much stronger incentive to reduce bundle/“executable” size compared to most other software, because the expectation is for your web app to “download” nearly instantly for every new user. (Compare to nearly any other type of software, client or server, where that’s not an expectation.)
JS comes with exactly zero tools out of the box to make that happen. You have to go out of your way to find a modern toolchain that will properly strip out dead code and create optimized scripts that are as small as possible.
This means the “massive JS library which includes everything” also depends on having a strong toolchain for compiling code. And while may professional web projects have that, the basic script tag approach is still the default and easiest way to get started… and pulling in a massive std library through that is just a bad idea.
This baseline — the web just simply having different requirements around runtime execution — is part of where the culture comes from.
And because the web browser traditionally didn’t include enough of a standard library for making apps, there’s a strong culture of making libraries and frameworks to solve that. Compare to native apps, where there’s always an official sdk or similar for building apps, and libraries like boost are more about specific “lower level” language features (algorithms, concurrency, data structures, etc) and less about building different types of software like full-blown interactive applications and backend services.
There are attempts to solve this (Deno is probably the best example), but buy-in at a professional level requires a huge commitment to migrate and change things, so there’s a lot of momentum working against projects like that.
That's built in to server side and browser.
> You can't expect people to re-write everything over and over.
That’s the excuse everyone is giving, then you see thousands of terminal libraries and calendar pickers.
So yes, the sprawling deps tree and culture is the problem. We would need to start reducing dependencies of the basic tools first. Otherwise it seems rather pointless to bother app developers with reducing dependencies.
I’d never worked in any other ecosystem, and I wish I realized that advice was specific to JS culture
In other languages, you'd have a few dependencies on larger libraries providing related functionality, where the Javascript culture is to use a bunch of tiny libraries to give the same functionality.
Call me crazy but I think agentic coding tools may soon make it practical for people to not be bogged down by the tedium of implementing the same basic crap over and over again, without having to resort to third party dependencies.
I have a little pavucontrol replacement I'm walking Claude Code through. It wanted to use pulsectl but, to see what it could do, I told it no. Write your own bindings to libpulse instead. A few minutes later it had that working. It can definitely write crap like leftpad.
The isolated context is gone and a single instance of code talking to an individual client has access to your entire database. It’s a completely different threat model.
I'm not quite sure what that would mean, but if it solves the problem for browsers, why not for server?
And even on client side, the sandboxing helps isolate any malicious webpage, even ones that are accidentally malicious, from other webpages and from the rest of your machine.
If malicious actors could get gmail.com to run their malicious JS on the client side through this type of supply-chain attack, they could very very easily steal all of your emails. The browser sandbox doesn't offer any protection from 1st party javascript.
But in practice, to do useful things server-side you generally need quite a few permissions.
> However, processes and practices around NodeJS and npm are in dire need of a security overhaul. leftpad is a cultural problem that needs to be addressed. To start with, snippets don't need to be on npm.
Traditional JS is the reason we have all of these problems around NodeJS and npm. It's a lot better than it was, but a lot of JS tooling came up in the time when ES5 and older were the standard, and to call those versions of the language lacking is... charitable. There were tons of things that you simply couldn't count on the language or its standard library to do right, so a culture of hacks and bandaids grew up around it. Browser disparities didn't help either.
Then people said, "Well, why don't we all share these hacks and bandaids so that we don't have to constantly reinvent the wheel?", and that's sort of how npm got its start. And of course, it was the freewheeling days of the late 00s/early 10s, when you were supposed to "move fast and break things" as a developer, so you didn't have time to really check if any of this was secure or made any sense. The business side wanted the feature and they wanted it now.
The ultimate solution would be to stop slapping bandaids and hacks on the JS ecosystem by making a better language but no one's got the resolve to do that.
E.g. there is a built in function that takes elements pairwise from a list! That level of minutia being included feels nuts having come from other languages.
What I'm wondering if it would help the ecosystem, if you were able to rather load raw snippets into your codebase, and source control as opposed to having them as dependencies.
So e.g. shadcn component pasting approach.
For things like leftPad, cli colors and others you would just load raw typescript code from a source, and there you would immediately notice something malicious or during code reviews.
You would leave actual npm packages to only actual frameworks / larger packages where this doesn't make sense and expect higher scrutiny, multi approvals of releases there.
I see this odd take a lot - the automatic narrowing of the scope of an attack to the single ecosystem it occurred in most recently, without any real technical argument for doing so.
What's especially concerning is I see this take in the security industry: mitigations put in place to target e.g. NPM, but are then completely absent for PyPi or Crates. It's bizarre not only because it leaves those ecosystems wide open, but also because the mitigation measures would be very similar (so it would be a minimal amount of additional effort for a large benefit).
I ask because think the directionality is backwards here: I’ve been involved in packaging ecosystem security for the last few years, and I’m generally of the opinion that PyPI has been ahead of the curve on implementing mitigations. Specifically, I think widespread trusted publishing adoption would have made this attack less effective since there would be fewer credentials to steal, but npm only implemented trusted publishing recently[1]. Crates also implemented exactly this kind of self-scoping, self-expiring credential exchange ahead of npm.
(This isn’t to malign any ecosystem; I think people are also overcorrect in treating this like a uniquely JavaScript-shaped problem.)
[1]: https://github.blog/changelog/2025-07-31-npm-trusted-publish...
Indeed, crates.io implemented PyPI's trusted publishing and explicitly called out PyPI as their inspiration: https://blog.rust-lang.org/2025/07/11/crates-io-development-...
But NPM has a much, much bigger problem on the client side, that makes many of these mitigations almost moot. And that is that `npm install` will upgrade every single package you depend on to its latest version that matches your declared dependency, and in JS land almost everyone uses lax dependency declarations.
So, an attacker who simply publishes a new patch version of a package they have gained access to will likely poison a good chunk of all of the users of that package in a relatively short amount of time. Even if the projects using this are careful and use `npm ci` instead of `npm install` for their CI builds, it will still easily get developers to download and run the malicious new version.
Most other ecosystems don't have this unsafe-by-default behavior, so deploying a new malicious version of a previously safe package is not such a major risk as it is in NPM.
They do, BUT.
Dependency versioning schemes are much more strictly adhered to within JS land than in other ecosystems. PyPi is a mishmash of PEP 440, SemVer, some packages incorrectly using one in the format of the other, & none of the 3 necessarily adhering to the standard they've chosen. Other ecosystems are even worse.
Also - some ecosystems (PyPi again) are committing far worse offences than lax versioning - versionless dependency declaration. Heavy reliance on requirements.txt without lockfiles where half the time version isn't even specified at all. Astral/Poetry are improving the situation here but things are still bad.
Maven land is full of plugins with automated pom.xml version templating that has effectively the same effect as lax versioning, but without any strict adherence to any kind of standard like semver.
Yes, the situation in JS land isn't great, but there are much worse offenders out there.
Overall, publishing a new malicious version of a package is a much lesser problem in virtually any ecosystem other than NPM; in NPM, it's almost an automatic remote code execution vulnerability for every NPM dev, and a persistent threat for many NPM packages even without this.
By default npm will create a lock file and give you the exact same version every time unless you manually initiate an upgrade. Additionally you could even remove the package-lock.json and do a new npm install and it still wouldn't upgrade the package if it already exists in your node_modules directory.
Only time this would be true is if you manually bump the version to something that is incompatible, or remove both the package-lock.json and your node_modules folder.
> The point is still different. In PyPI, if I put `requests` in my requirements.txt, and I run `pip install -r requirements.txt` every time I do `make build`, I will still only get one version of requests - the latest available the first time I installed it.
Only because your `make build` is a custom process that doesn't use build isolation and relies on manually invoking pip in an existing environment.
Ecosystem standard build tools (including pip itself, using `pip wheel` — which really isn't meant for distribution, but some people seem to use it anyway) default to setting up a new virtual environment to build your code (and also for each transitive dependency that requires building — to make sure that your dependencies' build tools aren't mutually incompatible, or broken by other things in the envrionment). They will read `requests` from `[project.dependencies]` in your pyproject.toml file and dump the latest version in that new environment, unless you use tool-specific configuration (or of course a better specification in pyproject.toml) to prevent that. And if your dependencies were only available as sdists, the build tool would even automatically, recursively attempt to build those, potentially running arbitrary code from the package in the process.
I'm going to assume this is you running this locally to generate releases, presumably for personal projects?
If you're building your projects in CI you're not pulling in the same version without a lockfile in place.
Please elaborate on this. I'm a long-time Java developer and have never once seen something akin to what you're describing here. Maven has support for version ranges but in practice it's very rarely used. I can expect a project to build with the exact same dependencies resolved today and in six months or a year from now.
I know this is possible with custom plugins but I've mainly just seen it using maven wrapper & user properties.
You'd have to go out of your way to make your project as bad as you're describing.
As well, both Dependabot and Renovate in isolated environments withour secrets or privileges, need to be manually approved, and have minimum publication ages before recommending a package update to prevent basic supply chain attacks or lockfile corruption from a pinned package version being de-published (up to a 3 day window on NPM).
> In short, the main differences between using npm install and npm ci are:
> The project must have an existing package-lock.json or npm-shrinkwrap.json.
> If dependencies in the package lock do not match those in package.json, npm ci will exit with an error, instead of updating the package lock.
- NPM Install without modifying the package-lock.json https://www.mikestreety.co.uk/blog/npm-install-without-modif...
- Why does "npm install" rewrite package-lock.json? https://stackoverflow.com/questions/45022048/why-does-npm-in...
- npm - How to actually use package-lock.json for installing based on locked versions? https://stackoverflow.com/questions/47480617/npm-how-to-actu...
2&3. NPM 5 had a bug almost a decade ago. They literally link to it in both of those pages. Here[^1] is a developer repeating how I've said its supposed to work.
It would have taken you less work to just try this in a terminal than search for those "citations".
[^1]: https://github.com/npm/npm/issues/17979#issuecomment-3327012...
The way you describe it working doesn't even pass a basic "common sense" check. Just think about what you're saying: despite having a `package-lock.json`, every developer who works on a project will get every dependency updated every time they clone it and get to work?
The entire point of the lockfile is that installations respect it to keep environments agreed. The only difference with `clean install` is that it removes `node_modules` (no potential cache poisoning) and non-zero exits if there is a conflict between `package.json` and `package-lock.json`.
`install` will only update the lockfile where the lockfile conflicts with the `package.json` to allow you to make changes to that file manually (instead of via `npm` commands).
But don't brush off "special status" of NPM here. It is unique in that JS being language of both front-end and back-end, it is much easier for the crooks to sneak in malware that will end up running in visitor's browser and affect them directly. And that makes it a uniquely more attractive target.
1- thoroughly and fully analyze any dependency tree you plan to include 2- immediately freeze all its versions 3- never update without very good reason or without repeating 1 and 2
in other words: simply be professional, face logical consequences if you aren't. if you think one package manager is "safer" than others because magic reasons odds are you'll find out the hard way sooner or later.
I bet it's hundreds.
Jest alone adds 300 packages.
Consequently I doubt that you in fact "thoroughly and fully" analyzed all your dependencies.
Unless what you're shipping isn't a feature rich app, what you proposed seems entirely unrealistic.
I'm in the C ecosystem mostly. Is one NPM package the equivalent of one object file? Can NPM packages call internal functions for their dependencies instead of relying so heavily on bringing in so many external ones? I guess it's a problem either way, internal dependencies having bugs vs supply chain attacks like these. Doesn't bringing in so many dependencies lead to a lot of dead code and much larger codebases then necessary?
No. The closest thing to a package (on almost every language) is an entire library.
> Can NPM packages call internal functions for their dependencies instead of relying so heavily on bringing in so many external ones?
Yes, they can. They just don't do it.
> Doesn't bringing in so many dependencies lead to a lot of dead code and much larger codebases then necessary?
There aren't many unecessary dependencies, because the number of direct dependencies on each package is reasonable (on the order of 10). And you don't get a lot of unecessary code because the point of tiny libraries is to only import what you need.
Dead code is not the problem, instead the JS mentality evolved that way to minimize dead code. The problem is that dead code is actually not that much of an issue, but dependency management is.
personally i keep dependencies at a minimum and are very picky with them, partly because of nr1, but as a general principle. of course if people happily suck in entire trees without supervision just to print ansi colors on the terminal or, as in this case, use fancy aliases for colors then bad things are bound to happen. (tbf tinycolor has one single devDependency, shim-deno-test, which only requires typescript. that should be manageable)
i'll grant you that the js ecosystem is special, partly because the business has traditionally reinforced the notion of it being accessory, superficial and not "serious" development. well, that's just naivety, it is as critical a component as any other. ideally you should even have a security department vetting the dependencies for you.
As far as I know crates.io has everything that npm has, plus
- strictly immutable versions[1]
- fully automated and no human in the loop perpetual yanking
- no deletions ever
- a public and append only index
Go modules go even further and add automatic checksum verification per default and a cryptographic transparency log.
Contrast this with docker hub for example, where not even npm's basic properties hold.
So, it is more like
docker hub ⊂ npm ⊂ crates.io ⊂ Go modules
[1] Nowadays npm has this arguably too
Cargo lockfiles contain checksums and Cargo has used these for automatic verification since time immemorial, well before Go implemented their current packaging system. In addition, Go doesn't enforce the use of go.sum files, it's just an optional recommendation: https://go.dev/wiki/Modules#should-i-commit-my-gosum-file-as... I'm not aware of any mechanism which would place Go's packaging system at the forefront of mitigation implementations as suggested here.
I'm not referring to mitigations in public repositories (which you're right, are varied, but that's a separate topic). I'm purely referring to internal mitigations in companies leveraging open-source dependencies in their software products.
These come in many forms, everything from developer education initiatives to hiring commercial SCA vendors, & many other things in between like custom CI automations. Ultimately, while many of these measures are done broadly for all ecosystems when targeting general dependency vulnerabilities (CVEs from accidental bugs), all of the supply-chain-attack motivated initiatives I've seen companies engage in are single-ecosystem. Which seems wasteful.
- Axios and Jest have "native" options now (fetch and node --test). fetch is especially nice because it is the same API in the browser and in Node (and Deno and Bun).
- Redux is self-contained.
- React itself is sort of self-contained, it's the massive ecosystem that makes React the most appealing that starts to drive dependency bloat. I can't speak to Svelte.
(ETA: Also, you may not need much for a mocks library because JS' Proxy meta-object isn't that hard to work with directly.)
NPM is special in the same way as Windows is special when it comes to malware: it's a more lucrative target.
However, the issue here is that - unlike Windows - targetting NPM alone does not incur significantly less overhead than targetting software registries more broadly. The trade-off between focusing purely on NPM & covering a lot of popular languages isn't high, & imo isn't a worthwhile trade-off.
Not sure how he actually got the number; this was just a frustrated Slack message like 4 years ago
A sibling comment mentions we could have been using Cargo workspaces wrong... So, maybe?
But you definitely aren't finding hundreds of versions of `regex` in the same dependency tree.
Zed is not a typical Rust project; it's a full fledged editor that includes a significant array of features and its own homegrown UI framework.
Funny that text editor is being presented here as some kind of behemoth, not representative of typical software written in Rust. I guess typical would be 1234th JSON serialization library.
I would actually say ripgrep is not especially typical here. I put a lot of energy into keeping my dependency tree slim. Many Rust applications have hundreds of dependencies.
We aren't quite at thousands of dependencies yet though.
Thank you for your honesty, and like you and I said, you put a lot of energy into keeping the dependency tree slim. This is not as common as one would like to believe.
Hundreds? Yes, absolutely. That's common.
In retrospect, I should have kept a list of these projects. I probably have not deleted these directories though, so I probably still could make a list of some of these projects.
Supply chain attacks happen at every layer where there is package management or a vector onto the machine or into the code.
What NPM should do if they really give a shit is start requiring 2FA to publish. Require a scan prior to publish. Sign the package with hard keys and signature. Verify all packages installed match signatures. Semver matching isn’t enough. CRC checks aren’t enough. This has to be baked into packages and package management.
While technically true, I have yet to see Go projects importing thousands of dependencies. They may certainly exist, but are absolutely not the rule. JS projects, however...
We have to realize, that while supply chain attacks can happen everywhere, the best mitigations are development culture and solid standard library - looking at you, cargo.
I am a JS developer by trade and I think that this ecosystem is doomed. I absolutely avoid even installing node on my private machine.
Assuming 'go mod tidy' is periodically run go.mod should contain all dependencies (which in this case seems to be shy of 300, still a lot).
> cat go.sum |awk '{print $1}' | sort |uniq |wc -l
431
> wc -l go.sum
1156 go.sum
That's really the core issue. Developer-signed packages (npm's current attack model is "Eve doing a man-in-the-middle attack between npm and you," which is not exactly the most common threat here) and a transparent key registry should be minimal kit for any package manager, even though all, or at least practically all, the ecosystems are bereft of that. Hardening API surfaces with additional MFA isn't enough; you have to divorce "API authentication" from "cryptographic authentication" so that compromising one doesn't affect the other.
In a hypothetical scenario where npm supports signed packages, let's say the user is in the middle of installing the latest signed left-pad. Suddenly, npm prints a warning that says the identity used to sign the package is not in the user's local database of trusted identities.
What exactly is the user supposed to do in response to this warning?
Now imagine you're another developer who needs to install a specific NPM package published by someone overseas who has zero vouches by anyone in your web of trust. What exactly are you going to do?
In reality, forcing package publishers to sign packages would achieve absolutely nothing. 99.99 % of package consumers would not even bother to even begin building a web of trust, and just blindly trust any signature.
The remaining 0.01 % who actually try are either going to fail to gain any meaningful access to a WoT, or they're going to learn that most identities of package publishers are completely unreachable via any WoT whatsoever.
How does 2FA prevent malware? Anyone can get a phone number to receive a text or add an authenticator to their phone.
I would argue a subscrption model for 1 EUR/month would be better. The money received could pay for certification of packages and the credit card on file can leverage the security of the payments system.
That is, if some attacker create some dummy trivial but convenient package and 2 years latter half the package hub depends on it somehow, the attacker will just use its legit credential to pown everyone and its dog. This is not even about stilling credentials. It’s a cultural issue with bare blind trust to use blank check without even any expiry date.
As another subthread mentioned (https://news.ycombinator.com/item?id=45261303), there is something which can be done: auditing of new packages or versions, by a third party, before they're used. Even doing a simple diff between the previous version and the current version before running anything within the package would already help.
NPM has also been sending out nag emails for the last 2+ years about 2FA. If anything, that constituted an assist in the attack on the Junon account that we saw a couple weeks ago.
<https://docs.npmjs.com/configuring-two-factor-authentication...>
https://github.blog/changelog/2022-11-01-high-impact-package...
At some point people need to realize and go back to writing vanilla js, which will be very hard.
The rust ecosystem is also the same. Too much dependence on packages.
An example of doing it right is golang.
Honestly I wish Python worked this way too. The reason people use Requests so much is because urllib is so painful. Changes to a first-party standard library have to be very conservative, which ends up leaving stuff in place that nobody wants to use any more because they have higher standards now. It'd be better to keep the standard library to a minimum needed more or less just to make the REPL work, and have all of that be "builtin" the way that `sys` is; then have the rest available from the developers (including a default "full-fat" distribution), but in a few separately-obtainable pieces and independently versioned from the interpreter.
And possibly maintained by a third party like Boost, yeah. I don't know how important that is or isn't.
Of course that limits my job search options, but I can't feel comfortable signing off on any project that includes more dependencies than I can count at a glance.
Lists of things that won't happen. Companies are filled with node_modules importers these days.
Even worse, now you have to check for security flaws in that JS that's been written by node_modules importers.
That or there could someone could write a standard library for JS?
There is a difference, but it's not an order of magnitude and neither is a true island.
Granted, deciding not to use JS on the server is reasonable in the context of this article, but for the client htmx is as much a js lib with (dev) dependencies as any other.
https://github.com/bigskysoftware/htmx/blob/master/package.j...
Unless this lack of scrutiny is exclusive to JavaScript ecosystem, then this attack could just as well have happened in Rust or Golang.
Languages without package managers have a lot more friction to pull in dependencies. You usually rely on the operating system and its package-manager-humans to provide your dependencies; or on primitive OSes like Windows or macOS, you package the dependencies with your application, which involves integrating them into your build and distribution systems. Both of those involve a lot of manual, human effort, which reduces the total number of dependencies (attack points), and makes supply-chain issues like this more likely to be noticed.
The language package managers make it trivial to pull in dozens or hundreds of dependencies, straight from some random source code repository. Your dependencies can add their own dependencies, without you ever knowing. When you have dozens or hundreds of unvetted dependencies, it becomes trivial for an attacker to inject code they control into just one of those dependencies, and then it's game over for every project that includes that one dependency anywhere in their chain.
It's not impossible to do that in the OS-provided or self-managed dependency scenario, but it's much more difficult and will have a much narrower impact.
Many who claim to fully analyze all dependencies are probably lying. I did not see anyone in the comments sharing their actual dependency count.
Even if you depend only on Jest - Meta's popular test runner - you add 300 packages.
Unless your setup is truly minimalistic, you probably have hundreds of dependencies already, which makes obsessing over some more rather pointless.
I just went to crates.io and picked a random newly updated crate, which happened to be pixelfix, which fixes transparent pixels in pngs.
It has six dependencies and hundreds of transient dependencies, may of which appear to be small and highly specific a la left-pad.
https://crates.io/crates/pixelfix/0.1.1/dependencies
Maybe this package isn't representative, but it feels pretty identical to the JS ecosystem.
Just defending Rust.
> 5 remaining dependencies have lots of dependencies of their own.
Mostly well-known crates like rayon, crossbeam, tracing, etc.
Any Rust project I have ever compiled pulled in over 1000 dependencies. Recently it was Zed with its >2000 dependencies.
Edit: Ghostty is a good counter-example that is open source. https://github.com/ghostty-org/ghostty/tree/main/pkg
JS library authors in general could decide to write their own (or carefully copy-paste from libraries) utility functions for things rather than depend on a huge mess of packages. This isn't always a great path; obviously reinventing the wheel can come with its own problems.
So yes, I'd agree that the ecosystem encourages JS/TS developers to make use of the existing set of libraries and packages with deep dependency trees, but no one is holding a gun to anyone's head. There are other ways to do it.
This works for deps of deps as well, so anything in your node_modules has access to this hook.
It's a terrible idea and something that ought to be removed or replaced by something much safer.
While npm is a huge and easy target, the general problem exists for all package repositories. Hopefully a supply chain attack mitigation strategy can be better than hoping attackers target package repositories you aren't using.
While there's a culture prevalent in Javascript development to ignore the costs of piling abstractions on top of abstractions, you don't have to buy into it. Probably the easiest thing to do is count transitive dependencies.
But it will cut a large portion of it.
If you can sneak malware into a JavaScript application that runs in millions of browsers, that's a lot more useful that getting a some number servers running a module as part of a script, who's environment is a bit unknown.
Javascript really could do with a standard library.
Eh... This over-generalises a bit. That can be said of anything really, including native desktop applications.
Have fun, seems like a misguided reason to do that though.
A. A package hosted somewhere using a language was compromised!
B. I am not going to program in the language anymore!
I don't see how B follows A.
> Server-side-rendering without JavaScript is just back to the stuff Perl and PHP give you.
As well as Ruby, Python, Go, etc.
HTMX is JavaScript.
Unless you meant your own JavaScript.
You all really need to stop using this term when it comes to OSS. Supply chain implies a relationship, none of these companies or developers have a relationship with the creators other than including their packages.
Call it something like "free code attacks" or "hobbyist code attacks."
“code I somehow took a dependency on when copying bits of someone’s package.json file”
“code which showed up in my lock file and I still don’t know how it got there”
I can't think of an instance where I ran npm install and didn't run some process shortly after that imported the packages.
EDIT: oh I scrolled down a bit further and see you said the exact same thing in a top-level comment hahah, my bad
This matters because dependencies are often installed in a build or development environment with access to things that are not available when the package is actually imported in a browser or other production environment.
Like, for rust, you can have a build.rs file that gets executed when your crate is compiled, I don't think it's sandboxed.
Or also on other languages that will get run on development machines, like python packages (which can trigger code only on import), java libraries, etc...
Like, there is the post install script issue or course, but I feel like these attacks could have been just as (or almost as) effective in other programming languages, but I feel like we always only hear about npm packages.
What has been the community reaction? Has allowing scripts been scalable for users? Or could it be described as people blindly copying and pasting allow commands?
I am involved in Python packaging discussions and there is a pre-proposal (not at PEP stage yet) at the moment for "wheel variants" that involves a plugin architecture, a contentious point is whether to download and run the plugins by default. I'd like to find parallels in other language communities to learn from.
https://docs.npmjs.com/cli/v11/using-npm/changelog#1100-pre0...
Not much has changed since then. The best counter-example I know is esbuild, which is a fully featured bundler/minifier/etc that has zero external dependencies except for the Go stdlib + one package maintained by the Go project itself:
https://www.npmjs.com/package/esbuild?activeTab=dependencies
https://github.com/evanw/esbuild/blob/755da31752d759f1ea70b8...
Other "next generation" projects are trading one problematic ecosystem for another. When you study dependency chains of e.g. biomejs and swc, it looks pretty good:
https://www.npmjs.com/package/@biomejs/biome/v/latest?active...
https://www.npmjs.com/package/@swc/types?activeTab=dependenc...
Replacing the tire fire of eslint (and its hundreds to low thousands of dependencies) with zero of them! Very encouraging, until you find the Rust source:
https://github.com/biomejs/biome/blob/a0039fd5457d0df18242fe...
https://github.com/swc-project/swc/blob/6c54969d69551f516032...
I think as these projects gain more momentum, we will see similar things cropping up in the cargo ecosystem.
Does anyone know of other major projects written in as strict a style as esbuild?
These kind of projects usually are pretty great because they aim to work with CGO_ENABLED=0 so the libs are very portable and work with different syscall backends.
Additionally I really like to go mod vendor my snapshot of dependencies which is great for short term fixes, but it won't fix the cause in the long run.
However, the go ecosystem is just as vulnerable here because of lack of signing off package updates. As long as there's no verification possible end-to-end when it comes to "who signed this package" then there's no way this will get better.
Additionally most supply chaib attacks focussed on the CI/CD infrastructure in the past, because they are just as broken with just as many problems. There needs to be a better CI/CD workflow where signing keys don't have to be available on the runners themselves, otherwise this will just shift the attack surface to a different location.
In my opinion the package managers are somewhat to blame here, too. They should encourage and mandate gpg signatures, and especially in git commits when they rely on git tags for distribution.
I'm interested in knowing whether there's something intrinsic to Go that encourages such a culture.
IMO, it might be due to the fact that Go mod came rather late in the game, while NPM was introduced near the beginning of NodeJS. But it might be more related to Go's target audience being more low-level, where such tools are less ubiquitous?
I think the culture was set from the top. Also, the fairly comprehensive standard library helps a lot. C# was in a similar boat back when I used it.
I've also seen something similar with Java, with its culture of "pure Java" code which reimplements everything in Java instead of calling into preexisting native libraries. What's common between Java and Go is that they don't play well with native code; they really want to have full control of the process, which is made harder by code running outside their runtime environment.
~13 years ago I needed to do DTLS (TLS-over-UDP) from a Java backend, something that would be exposed to the public internet. There were exactly zero Java DTLS implementations at the time, so I chose to write JNI bindings to OpenSSL. I was very unhappy with this: my choices were to 1) accept that my service could now segfault -- possibly in an exploitable way -- if there was a bug in my bindings or in OpenSSL's (not super well tested) DTLS code, or 2) write my own DTLS implementation in Java, and virtually guarantee I'd get something wrong and break it cryptographically.
These were not great choices, and I wished I had a Java DTLS implementation to use.
This is why in my Rust projects, I generally prefer to tell my dependencies to use rustls over native (usually OpenSSL) TLS when there's an option between the two. All the safety guarantees of my chosen language just disappear whenever I have to call out to a C library. Sure, now I have to worry about rustls having bugs (as a much less mature implementation), but at least in this case there are people working on it who actually know things about cryptography and security that I don't, and they've had third-party audits that give me more confidence.
Java doesn't have constant time guarantees, so for at least the cryptographic part you have to call to a non-Java library, ideally one which implements the cryptographic primitives in assembly (unfortunately, even C doesn't have constant time guarantees, though you can get close by using vector intrinsics).
I think it's because the final deliverable of Go projects is usually a single self-contained binary executable with no dependencies, whereas with Node the final deliverable is usually an NPM package which pulls its dependencies automatically.
Built applications do not pull dependencies at runtime, just like with golang. If you want to use a library/source, you pull in all the deps, again just like golang.
Generic methods are somewhat an antipattern to how the language was designed from the start. That is kind of the reason they're not there yet, because Go maintainers don't want boxing in their runtime, and also don't want compile time expansions (or JIT compilation for that matter).
So I'd argue that this way of handling compilation is more low level than other VM based languages where almost everything is JITed now.
There are plenty of people in the community who would help reduce the number of dependencies, but it really requires the maintainers to make it a priority. Otherwise the only way to address it is to switch to another solution like oxlint.
Way less dependencies too.
Jest pulls in 300 by the way.
Why don't you share with us what your project does and how many packages are present?
So I simply avoid the whole problem altogether in my current project. But aside from the JS stuff, the backend is in Python and I avoid adding dependencies from PyPI wherever possible. For example I had the choice of going with Pydantic and dataclasses and whatnot, but I resisted that, and came up with a quite minimalistic way to type check JSON documents, that is contained in one short module and easily extensible. Does it go to the same length as pydantic? No, it doesn't. If it did, I would be a genious. But it is quite sufficient for type safety in my project.
Keeping things simple is possible, if we set our minds to it. Sometimes one cannot avoid a big dependency, sure, but in many cases we actually can! We just need to beat that beast of habit of quickly adding a familiar dependency without thinking about the cost.
Surely you see how that might be relevant to the discussion where you appeared to give advice on how to solve the npm dependency graph problem.
That you're not using npm or other node package managers at all is the key information here. Not that it's invalid, but it's a very different setup.
I am not convinced you ever checked how many packages were actually present in your projects, as you shared no specifics.
I assume you just did not check, and may have had hundreds of packages installed despite avoiding adding dependencies willy-nilly. This invalidates your suggestion.
Those are the workspace dependencies, not the dependencies of the specific crates you may use within the project. You have to actually look closer to find that out, most of `swc-` crates have shallow dependency trees.
As in any random major project with focus on not having dependencies? SQLite comes to mind.
And it runs a post-install: node install.js
So I do really have to trust it or read all the code.
The Semgrep blog under "Additional NPM Registry Security Advice / Reducing Run Scripts" says "reducing" not "ignoring". I need to check if there are still "run scripts" even with this setting.
Also I need to check if there is the same class of vulnerabilities in other package managers I use, like emacs(1) (M-x package-install), mvn(1) (Maven, Java), clj(1) (deps.edn, Clojure), luarocks(1) (Lua), deps(1) (deps.fnl, Fennel), nbb(1) (deps.edn, Node.js babashka). Although some do not have "run scripts" feature, I need to make sure.
Aikido says: > We were alerted to a large-scale attack against npm...
Socket says: > Socket.dev found compromised various CrowdStrike npm packages...
Ox says: > Attackers slipped malicious code into new releases...
Safety says: > The Safety research team has identified an attack on the NPM ecosystem...
Phoenix says: > Another supply chain and NPM maintainer compromised...
Semgrep says: > We are aware of a number of compromised npm packages
The easiest way for you to use our product to be protected is actually using one of our free open source tools. https://www.npmjs.com/package/@aikidosec/safe-chain
This is a wrapper around npm etc that will prevent you from installing malware
And then vendors from Socket, Aikido, and Step all seem to have detected it via their upstream malware detection feeds - Socket and Aikido do AI code analysis, and Step does eBPF monitoring of build pipelines. I think this was widespread enough it was noticed by several people.
A lot of fingers in a lot pies
> The entire attack design assumes Linux or macOS execution environments, checking for os.platform() === 'linux' || 'darwin'. It deliberately skips Windows systems
If I were the conspiracy-minded sort I might jump to some wild conclusions here.
I mean it says something the developed the Linux Subsystem for Windows, but it’s an optional install.
So don’t expect PowerShell to be like a UNIX shell. It isn’t, and wasn’t meant to be one. It’s different, on purpose :)
I'm a die hard linux user, and some years ago took a windows gig on a whim. I find powershell fantastic and the only thing that makes my role bearable. Now, one of the first things i install on Linux is powershell.
MS doesn't care
Clever name... but I would have expected malware authors to be a bit less obvious. They literally named their giant worm after a giant worm.
> At the core of this attack is a ~3.6MB minified bundle.js file
Yep, even malware can be bloated. That's in the spirit of NPM I guess...
I modified the script slightly based on some of the comments in the thread and my own usage patterns:
#!/usr/bin/env bash
#
# See: https://news.ycombinator.com/item?id=45034496
bin=$(basename "$0")
echo "==========================="
echo "Wrapping $bin in bubblewrap"
echo "==========================="
exec bwrap \
--bind ~/.cache ~/.cache \
--bind "${PWD}" "${PWD}" \
--dev /dev \
--die-with-parent \
--disable-userns \
--new-session \
--proc /proc \
--ro-bind /etc/ca-certificates /etc/ca-certificates \
--ro-bind /etc/resolv.conf /etc/resolv.conf \
--ro-bind /etc/ssl /etc/ssl \
--ro-bind /usr /usr \
--setenv PATH /usr/bin \
--symlink /usr/bin /bin \
--symlink /usr/bin /sbin \
--symlink /usr/lib /lib \
--symlink /usr/lib64 /lib64 \
--tmpfs /tmp \
--unshare-all \
--unshare-user \
--share-net \
/usr/bin/env "$bin" "$@"
Put this in `~/.local/bin` and symlink it to `~/.local/bin/npm` and `~/.local/bin/yarn` (and make sure `~/.local/bin` is first in your `$PATH`). I've been using it to wrap npm and yarn successfully in a few projects. This will protect you against some attacks that use postinstall scripts to do nefarious things outside the project.Dangerous times we live in.
Pardon my ignorance, but couldn't a malicious actor just redefine $PWD before calling a npm script?
Of course, if your malicious actor has access to your environment already, they can redefine PWD, but that's assuming you're already compromised. This bwrap script is to avoid that malicious actor running malicious install scripts in the first place.
However, I don't think it protects you against stuff like `npm install compromised-executable && node_modules/.bin/execute-compromised-executable` – then you'd have to bwrap that second call as well. Or just bwrap bash to get a limited shell.
If you have a Linux system nearby, set it up and run `pnpm bash`, and then walk around the system and look at what you can see and do. (Not much.)
I've always worked at companies where we use third party open source libraries utilities and its true that they get less-than-ideal amount of auditing when they get updated but at least we're not constantly pushing updates of to our customers solely for the sake of using the latest version. In fact usually they're out of date by several years which is also a problem but generally there'll be a guy following the mailing lists for updates in case there's a known exploit that needs to be patched.
The "blind" auto updating to latest versions seems to be also an issue here, simply you cannot trust it enough as there is (seemingly) no security vetting process (I mean if you get obfuscated gibberish pushed into a relatively sanely written codebase it should ring some alarms somewhere).
Normally you'd run tests after releasing new versions of your website but you cannot catch these infected parts if they don't directly influence the behavior of your functionality.
But also, npm was very much (like js you could argue) vibed into existence in many ways, eg with the idea of a lock file (eg reproducible builds) _at all_ taking a very long time to take shape.
You could already specify exact versions in your package.json, same as a Gemfile, but reality is that specifying dependencies by major version or “*” was considered best practice, to always have the latest security updates. Separating version ranges from the lock files, and requiring explicit upgrades was a change in that mindset – and mostly driven by containerization rather than security or dev experience.
Some people had their crypto wallets drained I guess, but as far as I am concerned nothing of any real value was lost.
One could argue that your field saw exploits that did far more damage, no?
Edit: It looks like there's already something similar using sigstore in npm https://docs.npmjs.com/generating-provenance-statements#abou.... My understanding is that its use is not widespread though and it's mostly used to verify the publisher.
If it was provided, it would significantly trim dependency trees of all the small utility libraries.
Perhaps we need a common community effort to create a “distro” of curated and safe dependencies one can install safely, by analyzing the most popular packages and checking what’s common and small enough to be worth being included/forked.
Debian is a common community effort to create a “distro” of curated and safe dependencies one can install safely.
If you want stable, tested versions of software, only getting new versions every few years:
https://packages.debian.org/stable/javascript/
If you want the newer versions of software, less tested, getting new versions continuously:
Joking aside, I don't think there ever really was a lack of initiatives by entities (communities, companies, whatever) to create some sort of standard library (we typically tend to call them frameworks). There's just simply too much diversity, cultures and subcultures within the whole JavaScript sphere to ever get a global consensus on what that "standard" library then should look like. Not to mention the commercial entities with very real stakes in things they might not want to relinquish to some global unity consensus (as it may practically hurt their current bottom line).
There's been a lot of talk here about selecting and auditing dependencies, which is fine and good. But this attack and lots of other supply chain attacks would also be avoided with a better-behaved package manager. Doesn't Deno solve this? Do any other JS package managers do some common-sense sandboxing?
Yes, migration is painful. Yes, granular permissions are more annoying to figure out than anything-can-do-anything. But is either as painful as vendoring/forking your dependencies without the aid of a package manager altogether? If you're really considering just copying and pasting instead of using NPM, maybe you should also consider participating in a saner package ecosystem. If you're ready to do the one, maybe you're ready to do the other.
Guess we didn't dodge this one
Unix had a big scare last year because of XZ Utils.
We'd have to rely on the developer to notice, and check every line of code they ship, which might be the norm but certainly not 100% of cases.
I _know_ many don’t. In fact suggesting doing it is a good way to be looked at like a crazy person and be told something like “this is a yes place not a no place.”
You realize my point right? People are taught to not reinvent the wheel at work (mostly for good reasons) so that's what they do, me and you included.
You ain't gonna be bothered to write html and manual manipulation, the people that will give you libraries to do so won't be bothered reimplementing parsers and file watchers, file watcher writers won't be bothered reimplementing file system utils, file system utils developers won't be bothered reimplementing structured cloning or event loops, etc, etc.
I myself just the other day had the task of converting HTML to markdown, because I don't remember whether it was Jira or Github APIs that returns comments as HTML and despite it being mostly few hours of work that would get us 90% there everybody was in favor of pulling a dependency to do so (with its own dependencies) and thus further exposing our application to those risks.
https://github.com/williamcotton/markdown-to-html-llm
;)
> I myself just the other day had the task of converting HTML to markdown
> you could write an HTML to markdown library in half a day
I try to avoid JS, as it is a horrible language, by design. That does include TS, but it at least is useable, but barely - because it still tied to JS itself.
Still, even I who'd call myself a JavaScript developer also try to avoid desktop applications made with just JS :)
It is full of gotchas that serves 0 purpose nowadays.
Also remember that it is basically a Lisp wearing Java skin on top, originally designed in less than 2 weeks.
Typescript is one of few things that puts safety barrier and sane static error checking that makes JS bearable to use - but it still has to fall down to how JS works in the end so it suffers from same core architectural problems.
What some people see as a fault, others see as a feature :) For me, that's there to prevent entire websites from breaking because some small widget in the bottom right corner breaks, for example. Rather than stopping the entire runtime, it just surfaces that error in the developer tools, but lets the rest to continue working.
Then of course entire web apps crash because one tiny error somewhere (remember seeing a blank page with just some short error text in black in the middle? Those), but that doesn't mean that's the best way of doing things.
> Also remember that it is basically a Lisp wearing Java skin on top
I guess that's why I like it better than TS, that tries to move it away from that. I mainly do Clojure development day-to-day, and static types hardly ever gives me more "safety" than other approaches do. But again, what I do isn't more "correct" than what anyone else does, it's largely based on "It's better for me to program this way".
the issue is that it prevents that, but also allows you to send complete corrupt data forward, that can create horrible cascade of errors down the pipeline - because other components made assumption about correctness of data passed to them.
Such display errors should be caught early in development, should be tested, and should never reach prod, instead of being swept under the rug - for anything else other than prototype.
but i agree - going fully functional with dynamic types beats average JS experience any day. It is just piling up more mud upon giant mudball,
TS is just too much overhead for the marginal gains.
Care to explain why?
My view is this: since you can write plain JS inside TS (just misconfigure tsconfig badly enough), I honestly don’t see how you arrive at that conclusion.
I can just about understand preferring JS on the grounds that it runs without a compile step. But I’ve never seen a convincing explanation of why the language itself is supposedly better.
We fcked up with js, big time and its with us forever now
But apparently they only made it do like 95% of what JS does so you can't actually replace js with it. To me it seems like a huge blunder. I don't give a crap about making niche applications a bit faster, but freeing the web from the curse of JS would be absolutely huge. And they basically did it except not quite. It's so strange to me, why not just go the extra 5%?
The only way to remove Js is to create a new browser that doesn't use it. Fragments the web, yes and probably nobody will use it
https://github.com/wasm-bindgen/wasm-bindgen https://docs.rs/web-sys/latest/web_sys/
This is also being worked on, in the future this 5% glue might eventually entirely disappear:
> Designed with the "Web IDL bindings" proposal in mind. Eventually, there won't be any JavaScript shims between Rust-generated wasm functions and native DOM methods
systems? rust - but it is still far from perfect, too much focus on saving few keystrokes here and there.
general purpose corporate development? c# - despite current direction post .net 5 of stapling together legacy parts of .net framework to .net core. it does most things good enough.
scripting, and just scripting? python.
web? there's only one, bad, option and that's js/ts.
most hated ones are in order: js, go, c++, python.
go is extremely infuriating, there was a submission on HN that perfectly encapsulated my feelings about it, after writing it for a while: https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-...
Rust has `cargo install --locked`, which will use the pinned versions of dependencies from the lockfile, and these lockfiles are published for bin packages to crates.io.
But it seems npmjs doesn't allow publishing lockfiles, neither for libraries nor for CLI tools, so if you try to install let's say @google/gemini-cli, it will just pull the latest dependencies that fit the constraints in package.json. Is that true? Is it really this bad? If you try to install a CLI tool on a bad day when half of npmjs is compromised, you're out of luck?
How is that acceptable at all?
Lots of good ideas since last week, the one I like most being that published packages, especially those that are high in download count, don't actually go publish for a while until after publishing, allowing security scanners to do their thing.
I'm asking in the context of installing a single CLI tool into ~/bin or something. There's no requirement to satisfy all dependencies, because the only dependency I care about is that one CLI tool. All I want is an equivalent of what `cargo install --locked` does — use the top-level lockfile of the CLI tool itself.
Good CLI tools are bundled before release so they are zero-dependency as far as npm is concerned, which is ideal imho for all CLI tools, but many don't do that.
I'll repeat that the bigger problem is that npm has such unfettered access to everything in the user account to begin with. FSM knows it's not strictly an npm problem, it's a Unix problem that's been there since the beginning, just that now, enough of the chickens are coming home to roost that people are starting to notice.
NPM debug and chalk packages compromised (1366 points, 754 comments): https://news.ycombinator.com/item?id=45169657
No direct relation to the specific attack on debug/chalk/error-ex/etc that happened 7 days ago.
The article states that this is the same attackers that got control of the "nx" packages on August 27th, which didn't really get a lot of traction on HN when it happened: https://hn.algolia.com/?dateRange=pastMonth&page=0&prefix=fa...
Adding dependencies comes with advantages and downsides. You need to strike a balance between them. External libraries can help implement things that you better don't implement yourself, so the answer is certainly not "no dependencies". But there are downsides and risks, and the risks grow with the number of dependencies.
In the world of NPM, people think those simple truths don't apply to them and the downsides and risks of dependencies can be ignored. Then you end up with thousands of transitive dependencies.
They're wrong and learn it the hard way now.
node should have shipped "batteries included" after the left-pad incident. There was a boneheaded attachment to small stdlib, which you could put down to youthful innocence, except that it's been almost 10 years.
The TC39 committee which controls the design of JS stdlib and the node maintainers basically both act like the other one doesn't exist.
NPM was never designed with security in mind. It's a dirty hack that somehow became the most popular package manager.
The dependency hell is a reflection of the massive egos of the people involved in the multiple organizations. Python doesn't have this problem because it's all centralized under one org with a single vision.
I would argue that is only one of the many issues with the JS/TS/NPM ecosystem. Many of the other problems have been normalized. The constant security issues are highly visible.
Where did you see that number? Maven central says it has about 18 million [1] packages. Maybe with all versions of those 18 million packages there are about 62 million artifacts?
While the Java ecosystem is vastly larger, in Java (with Maven, Gradle, Bazel, etc.) it is not common to use really small libraries. So you end up with vastly less transitive dependencies in your projects.
It's just javascript being javascript.
The problem with that guy is that the dependencies are useless to everyone except his ego.
NPM packages are used by huge Electron apps like Discord, Slack, VS Code, the holy grail would be to somehow slip something inside them.
In our line-of-business .NET app, we have a logger, a database, a unit tester, and a driver for some specialty hardware. We upgrade to the latest version of each external dependency about once per year (every major version) to avoid accruing tech debt. They're all pinned and locally hosted, nuget exists but we (like most .Net developers) don't use it to the extent that npm devs do. We read the changelogs - all four of them! - and manually update.
I understand that the NPM ecosystem works differently from a "batteries included" .Net environment for a desktop app, but it's not just about where the users are. Line of business code in .Net and Java apps process a lot of important data. Slipping a malicious package into pypi could expose all kinds of juicy, proprietary data, but again, it's less about the existence of a package manager and more about when and how you use it.
> In July 2024, Bittensor users were the victims of an $8 million hack. The Bittensor hack was an example of a supply chain hack using PyPI. PyPI is a site that hosts packages for the Python programming language
https://www.halborn.com/blog/post/explained-the-bittensor-ha...
It's the new pragmatic choice for web apps and so it's everyone is using it, from battle hardened teams to total noobs to people who just don't give a shit. It reminds me of Wordpress from 10 years ago, when it was the goto platform for cheap new websites.
Maybe some of these systems have better protection from counterfeiting, and probably they all should. But as the number of packages you use goes up, the surface area does too. As a Node developer the… permissiveness of the culture has always concerned me.
The trick with playing with fire is understanding how fire works, respecting it, and keeping the tricks small. The bigger you go, the more the danger.
Community is very happy to pick up helper libraries and by the time you get all the way up the tree in a react framework you have hundreds or even thousands of packages.
If you’re sensible you can be fine just like any other ecosystem, but limited because one wrong package and you’ve just ballooned your dependency tree by hundreds which lowers the value of the ecosystem.
Node doesn’t have a standard library and until recently not even a test runner which certainly doesn’t help.
If your sensible with node or Deno* you’ll somewhat insulated from all this nonsense.
*Deno has linting,formatting,testing & a standard library which is a massive help (and a permission system so packages can’t do whatever they want)
Its users don't check who the email is from
How many tokens do you have lying around in your home directory in plain text, able to be read by anything on your computer running as your user?
Zero? How many developers have plain-text tokens lying around on disk? Avoiding that been hammered into me from every developer more senior than me since I got involved with professional software development.
With that said, it's not impossible some tool leaks their secrets into ~/.local, ~/.cache or ~/.config I suppose.
I thought they were referencing the common approach of adding environment variables with plaintext secrets to your shell config or as an individual file in $HOME, which been a big no-no for as long as I can remember.
I guess I'd reword it to "I'm not manually putting any cleartext secrets on disk" or something instead, if we wanted it to be 100% accurate.
Most of them. Mainly on purpose, (.env files) but many also accidentally. (shell history with tokens in the commands)
I recommend Envie: https://github.com/ilmari-h/envie
It's more convenient than having a bunch of .env.prod, .env.staging files laying around, not to mention more secure.
Your program (or your shell) opens. It runs a program to ask the password manager for a secret. Your password manager prompts you to authorize unsealing the secret. You accept or deny. The secret is passed to the program that asked for it. Works very well with 1Password and tools like git, ssh, etc, or simply exporting the secret to an environment variable, either in a script or bashrc file.
Other programs also support OIDC, such as with git credential helper plugins, or aws sso auth.
The extent to which any of this is actually implemented varies wildly between different OSes, ecosystems and tools. On macOS, docker desktop does quite well here. There's also an app called Secretive which does even better for SSH keys - generating a non-exportable key in the CPU's secure enclave. It can even optionally prompt for login password or fingerprint before allowing the key to be used. It's practically almost as secure as using a separate hardware token for SSH but significantly more convenient.
In contrast, most of the time the only thing protecting the keys in your CI vault from being exfiltrated is that the malware needs to know the specific name / API call / whatever to read them. Plenty of CI systems you don't even need that, because the build script that uses the secrets will read them into environment variables before starting the build proper.
A repository that an attacker only needs to get access to once, after which they can perform offline attacks against at their leisure.
A repository that contains the history of changed values, possibly making the latter easier, if you used the same encryption secret for rotated values.
This is an awful idea. Use a proper secret management tool you need to authenticate to using OIDC or Passkeys, and load secrets at runtime within the process. Everything else is dangerous.
Edit: Testing 1Password myself, with 1password desktop and shell, if I have authed myself once in shell, then "spawn" would be able to get all of my credentials from 1Password.
So I'm not actually sure how much better than plaintext is that. Unless you use service accounts there.
The approach also depends on the project. There is a bunch of different approaches and I don't think there is one approach that would work for every project, and sometimes I requires some wrangling but takes 5-10 minutes tops.
Some basic information about how you could make it work with 1Password: https://developer.1password.com/docs/cli/secrets-environment...
Frankly, our desktop OSes are not fit for purpose anymore. It's nuts that everything I run can instantly own my entire user account.
It's the old https://xkcd.com/1200/ . That's from 2013 and what little (Flatpak, etc.) has changed has only changed for end users - not developers.
Is anyone doing this in a "security as a service" fashion for JavaScript packages? I imagine a kind of package escrow/repository that only serves known secure packages, and actively removes known vulnerable ones.
But what you describe is an interesting idea I hadn't encountered before! I assume such a thing would have lower adoption within a relatively fast-moving ecosystem like Node.js though.
The closest thing I can think of (and this isn't strictly what you described) is reliance on dependabot, snyk, CodeQL, etc which if anything probably contributes to change management fatigue that erodes careful review.
> The closest thing I can think of (and this isn't strictly what you described) is reliance on dependabot, snyk, CodeQL, etc which if anything probably contributes to change management fatigue that erodes careful review.
It's not glamorous work, that's for sure. And yes, it would have to rely heavily on automated scanning to close the gap on the absolutely monstrous scale that npmjs.org operates at. Such a team would be the Internet's DevOps in this one specific way, with all the slog and grind that comes with that. But not all heroes wear capes.
This is why package malware creates news, but enterprises mirroring package registries do not get affected. Building a mirroring solution will be pricey though mainly due to high egress bandwidth cost from Cloud providers.
Some other vendors do AI scanning
I doubt anyone would want to touch js packages with manual review.
I think the bare minimum is heavy use of auditjs (or Snyk, or anything else that works this way), and maybe a mandatory waiting period (2-4 weeks?) before allowing new packages in. That should help wave off the brunt of package churn and give auditjs enough time to catch up to new package vulnerabilities. The key is to not wait too long so folks can address CVE's in their software, but also not be 100% at the bleeding edge.
We, at ClickHouse, love big data and it would be super cool download and analyse patterns of all these data & provide some tooling to help with combatting this wide spread issue.
Use pnpm and whitelist just what you need. It disables all scripts by default.
The malware could have been a JS code injected into the module entry point itself. As soon as you execute something that imports the package (which, you did install for a reason) the code can run.
I don't think that many people sandbox their development environments.
To me it's quite unexpected/scary that installing a package on my dev machine can execute arbitrary code before I ever have a chance to inspect the package to see whether I want to use it.
Modern node package managers such as yarn and pnpm allow you to prevent post installs entirely.
Today most of the time you need to make an exception for a package is when a module requires native compilation or download of a pre-built binary. This has become rare though.
Something like, only packages with attestations/signed releases and OIDC-only workflow should allow these scripts.
Worm could propogate through the code itself but I think it would be quite a bit less effective.
Detecting outbound network connection during an npm install is quite cheap to implement in 2025. I think it comes down to tenant and incentives, if security is placed as first priority as it should, for any computing service and in particular for supply chain like package management, this would be built in.
One thing that comes to mind that would make it a months long deabte is the potential breakage of many packages. In that case as a first step just make an eye catching summary post install, with gradual push to totally restriction with something like a strict mode, we've done this before.
Which, reminds me of another long standing issue with node ecosystem toolings, information overload. It's easy to bombard devs with thesis character count then blame them for eventually getting fatigue and not reading the output. It takes effort to summarize what's most important with layered expansion of detail level, show some.
E.g. malware might be executed when you test code which uses the library, or when you run a dev server, or on a deployed web site.
The entire stack is built around trusting a code, letting it do whatever it wants. That's the problem.
I'm still looking at Bun and all the effort they're doing with built-in APIs to reduce (and hopefully eliminate) third party deps. I would prefer using TS for the whole stack if possible but not at the expense of an insecure backend ecosystem.
These security problems happen much less often in other ecosystems. There is nothing even remotely as bad as NPM.
In general, I agree with the idea that writing everything yourself results in a higher quantity of low quality software with security issues and bugs, as well as a waste of developers' time. That said, clearly supply chain attacks are a very real threat that needs to be addressed. I just don't think eliminating package managers is a good solution.
In any case, does anyone have an exhaustive list of all recently compromised npm packages + versions across the recent attacks? We need to do an exhaustive scan after this news...
Its less a technical but rather a moral hurdle. Its probably a bunch of teenagers behind it like how it was with the Mirai Botnet.
My posts way before the issue was created: https://news.ycombinator.com/item?id=45252940 https://www.linkedin.com/posts/daniel-pereira-b17a27160_i-ne...
Apparently they've tried to implement this in JavaScript but the language is generally too flexible to resist a malicious package running in the same process.
We need to be using different languages with runtimes that don't allow privileged operations by default.
Or wonder if GitHub is enforcing 2fa soon because of the NPM CVEs potential to harvest GitHub creds?
But it still doesn't stop infected developer machines to silently update code and wait for the next release patiently.
It would require the diligence of those developers to check every line of code that goes out with a release... which is a lot to ask for someone who fell for a fishing email.
I just checked one of our repos right now and it has a 981 packages. It's not even realistic to vet the packages or to know which one is compromised. 99% of them are dependencies of dependencies. Where do we even get started?
The first thing I do for all of my projects is adding a .npmrc with save-exact=true
Or setup a caching proxy, whatever is easier for your org. I've had good experience with nexus previously, it's pretty heavy but very configurable, can introduce delays for new versions and check public vulnerability databases for you.
It's purely an efficiency problem though, nothing to do with security, which is covered by lock files.
Corporations would love it.
But does yarn or deno suffer from the same issues? That is do they get their packages from npm repositories? I've never used these.
Or is "yarn audit" enough ?
(Of course we would not pipe the link to a shell, and we would read it beforehand :D )
1) fetch calls
2) obfuscation (like sketchy lookup tables and hex string construction)
Like for (1) the hostname should be statically resolvable and immutable. So you can list the hostnames it fetches from as well.
Is this feasible or am I underestimating the difficulty? Javascript seems to have no shortage of static analysis tools.
Unfortunately, eval is still used in a lot of code, so disabling it isn't trivially viable, and with eval present, detecting fetch calls and such statically becomes the halting problem.
They really shouldn't have been stored unencrypted on peoples machines.... Ouch.
more updates soon and PRs welcome.
But also I miss having things like spare time, and sleep, so perhaps the tradeoff wasn't worth it
To my experience 80% of companies do not care about their secrets will/being exposed.
There is this shallow belief that production will never be hacked
Is anybody looking at this?
PyPI's attestations do nothing to prevent this either. A package built from a compromised repository will be happily attested with malicious code. To my knowledge wheels are not required.
I honestly think a forced time spent in pre release (with some emergency break glass where community leaders manually review critical hotfixes) could mitigate 99% of the issues here. Linux packages have been around for ever and have fewer incidents mainly because of the long dev->release channel cooking time.
Can somebody drive this up the chain to people who administer npm?
You can reference that and leave the color commentary at the door.
Every ecosystem has this problem but NPM is the undisputed leader if you count all attacks.
NPM gets a lot of traffic, there might be other package managers out there, in different languages, that may have been infected in the past and simply don't get the same amount of eyeballs.
https://github.com/freakynit/simple-npm-sandbox
Disclaimer: I am not Docker expert. Please review the script (sandbox.js) and raise any potential issues or suggestions.
Thanks..
I've never tried any of them but there's also a few wrappers specifically to do that, such as: https://github.com/berstend/node-safe
Otherwise you're down to docker or virtualisation or creating one system user per project...
~$ npm view @ctrl/tinycolor --json | grep 4.1.1
"4.1.1": "2025-09-15T19:52:46.624Z",
Why can't npm maintainers just implement something similar?
Maybe at least have a default setting (or an option) that packages newer than X days are never automatically installed unless forced? That would at least give time for people to review and notice if the package has been compromised.
Also, there really needs to be a standard library or at least a central community approved library of safe packages for all standard stuff.
In fact this blog post appears to be advertising for a system that secures build pipelines.
Google has written up some about their internal approach here: https://cloud.google.com/docs/security/binary-authorization-...
And if your CI is building and releasing in a sandboxed hermetic environment, then the sandboxes that build and release don't need credentials like AWS_ACCESS_KEY because they can't depend on data from the network. You need credentials for deploying and signing, but they don't need to be present during build time.
Exactly: it can simply commit its code and trigger a CI-only GitHub Actions deploy with no input from the maintainer at all.
By hypothesis the code only deploys from code committed to the main branch (or whatever the blessed branch for CI is). To create a GitHub Action that can deploy the code, the package maintainer must first manually approve and merge the malicious commit.
And the malware spreads by publishing new versions of NPM packages using credentials on the package owner's development machine. If the package owner didn't have credentials with publish access, this wouldn't spread like a worm. And if they had reproducible builds they wouldn't pull a new version of their dependencies from NPM at build time because they'd have to have pinned specific versions with specific hashes to get reproducibility.
Under these hypotheses it can spread, but only if the package owner manually pins a malicious version or manually approves a malicious commit.
Do not let code to have access to things it's not supposed to access.
It's actually that simple. If you implemented a function which formats a string, it should not have access to `readFile`, for example.
Retrofitting it into JS isn't possible, though, as language is way too dynamic - self-modifying code, reflection, etc, means there's no isolation between modules.
In a language which is less dynamic it might be as easy as making a white-list for imports.
The situation gets better in monadic environments (can't readFile without the IO monad, and you cant' call anything which would read it).
Programming languages which are "static" (or, basically, sane) you can identify all imports of a module/library, and, basically, ban anything which isn't "pure" part of stdlib.
If your module needs to work with files, it will receive an object which lets it to work with files.
A lot of programming languages implement object-capability model: https://en.m.wikipedia.org/wiki/Object-capability_model it doesn't seem to be hard at all. It's just programmers have preference for shittier languages, just like they prefer C which doesn't even have language-level array bound checking (for a lack of a "dynamic array" concept on a language level).
I think it's sort of orthogonal to "pure functional" / monadic: if you have unrestricted imports you can import some shit like unsafePerformIO, right? You have another level of control, of course (i.e. you just need to ban unsafePerformIO and look for unlicensed IO) but I don't feel like ocap requires Haskell
This is something I'm trying to polish for my system now, but the idea is: yarn (and bundler and others) needs to talk only to the repositories. That means yarn install is only allowed outbound connections to localhost running a proxy for packages. It can only write in tmp, its caches, and the current project's node_packages. It cannot read home files beyond specified ones (like .yarnrc). The alias to yarn strips the cloud credentials. All tokens used for installation are read-only. Then you have to do the same for the projects themselves.
On Linux, selinux can do this. On Mac, you have to fight a long battle with sandbox-exec, but it's kinda maybe working. (If it gained "allow exec with specified profile", it would be so much better)
But you may have guessed from the description so far - it's all very environment dependent, time sink-y, and often annoying. It will explode on issues though - try to touch ~/.aws/credentials for example and yarn will get killed and reported - which is exactly what we want.
But internally? The whole environment would have to be redone from scratch. Right now package installation will run any code it wants. It will compile extensions with gyp which is another way of custom code running. The whole system relies on arbitrary code execution and hopes it's secure. (It will never be) Capabilities are a fun idea, but would have to be seriously improved and scoped to work here.
When declaring dependencies, you'd also declare the permissions of those dependencies. So a package like `tinycolor` would never need network or disk access.
Clojars (run by volunteers AFAIK) been doing signatures since forever, not sure why it's so difficult for Microsoft to follow their own yearly proclamation of "security is our top concern".
> The NPM CI tokens that don't require 2fa kind of makes it less useful though
Use OIDC to publish packages instead of having tokens around that can be stolen or leaked https://docs.npmjs.com/trusted-publishers
PATH_ELEMENTS := $(subst :, ,$(PATH))
BIND_COMMANDS := $(foreach element, $(PATH_ELEMENTS), --ro-bind-try $(element) $(element))
define BWRAP_BUILD
bwrap \
--unshare-all \
--unshare-user \
--die-with-parent \
--disable-userns \
--ro-bind /usr/ /usr \
--ro-bind /lib64 /lib64/ \
--ro-bind /lib /lib \
--ro-bind /etc/alternatives/ /etc/alternatives/ \
--ro-bind $(CURDIR) $(CURDIR) \
--proc /proc \
--clearenv \
--setenv PATH $(PATH) \
$(BIND_COMMANDS) \
--setenv GOPATH $(GOPATH) \
--ro-bind $(GOPATH) $(GOPATH) \
--setenv TMPDIR $(XDG_CACHE_HOME)/go-build \
--bind $(XDG_CACHE_HOME)/go-build $(XDG_CACHE_HOME)/go-build \
--setenv XDG_CACHE_HOME $(XDG_CACHE_HOME) \
--dev-bind /dev/null /dev/null \
--setenv PNPM_HOME $(PNPM_HOME) \
--bind-try $(PNPM_HOME) $(PNPM_HOME) \
--setenv HOME $(HOME) \
--bind-try $(CURDIR)/ui/.svelte-kit $(CURDIR)/ui/.svelte-kit \
--bind-try $(CURDIR)/ui/build $(CURDIR)/ui/build \
endef
mybin: $(deps)
$(BWRAP_BUILD) go build -trimpath -ldflags $(ldflags) ./cmd/mybin/
Notes: most of the lines after --setenv GOPATH... are specific to my project and tooling. Some of the lines prior are specifically to accommodate my tooling, but I think that stuff should be reasonably general. Lmk if anyone has any suggestions.Stuff like intents "this is a math library, it is not allowed to access the network or filesystem".
At a higher level, you have app sandboxing, like on phones or Apple/Windows store. Sandboxed desktop apps are quite hated by developers - my app should be allowed to do whatever the fuck it wants.
I would have thought it wouldn't be too hard to design a capability system in JS. I bet someone has done it already.
Of course, it's not going to be compatible with any existing JS libraries. That's the problem.
Maybe related to the Russian hacker group Sandworm?
Reminds me of when I went to a tech conference with a Windows laptop and counted exactly two like me among the hundreds of attendees. I was embarrassed then but I'd be laughing now :D
Active NPM supply chain attack: Tinycolor and 40 Packages Compromised
This isn't a JavaScript problem. What, structurally, stops the same thing happening to PyPI? Or the Rust ecosystem? Or Lisp via QuickLisp? Or CPAN?
This whole mess was foreseeable. So what's to be done?
Look. Any serious project needs to start vendoring its dependencies. People should establish big, coarse grained meta-distributions like C++ Boost that come from a trustable authority and that get updated infrequently enough that you can keep up with release notes.
For one, NPM has a really sprawling ecosystem where it's normal to have many dependencies.
I remember that I once tried to get started with angular, and I did an "init" for an empty project and "compile", and suddenly had half a gigabyte of code lying in my directory.
This means that there is a high number of dependencies that are potential targets for a supply chain attack.
I just took a look at our biggest JS/Typescript project at work, it comes in at > 1k (recursive) NPM dependencies. Our biggest Python project has 78 recursive dependencies. They are of comparable size in terms of lines of code and total development time.
Why? Differences in culture, as well as python coming with more "batteries included", so there's less need for small dependencies.
Agreed, but it's a difference of degree (literally --- graph in- and out-degree) not kind.
Common Lisp is not worth it - you are unlikely to hit any high-value production target, there are not many uses and they are tech-savy. Good for us, the 5 remaining users. Also, Quicklisp is not rolling-release, it is a snapshot done one or two times a year.
Nothing much came of it, I don't know.
On the contrary - almost being a decade into Elixir - most of the time, I don't need (and I don't like) using external dependencies. I can just write something myself in a matter of just an hour or so because it's just so easy to do it myself. And everything I've written till date hasn't required an audit or re-write every 6 months or sometimes, even for years.
We all seem to hate the concept of Nazis and yet somehow we have done nothing about the Nazi-est language of them all which literally has no other alternatives to run on web browsers?
This. But the problem seems to go way deeper than npm or whatever package manager is used. I mean, why is anyone consuming a package like colors or tinycolors? Do projects really need to drag in a random dependency to handle these usecases?
There will always be packages that for some people are "but why?" but for others are "thank god I don't have to deal with that myself". Sure, colors and whatnot are tiny packages we probably could do without, but what are you really suggesting here? Someone sits and reviews every published package and rejects it if the package doesn't fit your ideal?
But the issue isn't just about the “thank god I don't have to deal with that myself” perspective. It's more about asking: do you actually need a dependency, or do you simply want it?
A lot of developers, especially newer ones, tend to blur that distinction. The result is an inflated dependency tree that unnecessarily increases the attack surface for malware.
The "ship fast at all costs" mindset that dominates many startups only makes this worse, since it encourages pulling in packages without much thought to long-term risk.
There's some ignorance in your comment. If you read up on debug & chalk supply chain attack, you'll end up discovering that the attacker gained control of the account through plain old phishing. Through a 2FA reset email, to boot.
What exactly do you expect the likes of Microsoft to do if users hand over their access to third parties? Do you want to fix issues or to pile onto the usual targets?
Why are React devs pulling object utils from lodash instead of reimplementing them?
What leads you to believe React is not well suited to simple ecommerce sites?
2. Extensive ecommerce experience including Disney, Carnival Cruises, Booking, TUI, and some of the European leaders in real estate and professional home building tools among the others.
Strongly disagree. React is not about interactivity, but reactivity. If you have to consume an API and update your app based on the responses, React does all the heavy lifting for you without requiring full page reloads.
On top of that, and as a nice perk, React also gives you all the tools you will ever need to optimize perceived performance.
Claiming that a tool designed for reactive programming is not suited for the happy flow of reactive programming is simply fundamentally wrong.
2. Ecommerces are not highly dynamic pages. They are overwhelmingly static content with an occasional configurator/cart/search. All things that can be embedded with whatever library you like (including React), or even better none at all.
3. Seo and performance is what really matters in ecommerces. The only minor exceptions are shops like Amazon or Airbnb, but that's unrelated to their seo and performance.
4. I've been writing React and ecommerces using React and similar with millions of daily users for a decade :)
Time to add developer ID's verification /s
I appreciate that HTTP sounds tangential but my point is the whole stack is designed wrong for what it’s doing and we waste all of our time commenting on and fixing the 1,000 manifestations of that rather just fix the architecture. There are political reasons for that I won’t get into.