Case Study 2: The Left-Pad Incident — When a Tiny Package Breaks the Internet
Eleven Lines of Code That Took Down Thousands of Projects
On March 22, 2016, a developer named Azer Koculu unpublished 273 of his packages from npm, the JavaScript package registry. One of those packages was called left-pad. It was 11 lines of code. Its sole purpose: pad the left side of a string with spaces or zeros.
Within minutes, builds started breaking worldwide — npm saw hundreds of failures per minute. Projects as prominent as the Babel compiler and Facebook's React could no longer be installed, because they depended (directly or transitively) on left-pad. For about two and a half hours, until npm restored the package, a significant portion of the JavaScript ecosystem couldn't be installed from scratch.
This incident became one of the most discussed events in software engineering history. While it happened in the JavaScript/npm ecosystem (not Python/PyPI), the lessons apply directly to every package manager, including pip.
What Actually Happened
The chain of events started with a trademark dispute, not a technical issue.
Azer Koculu had published a package called kik on npm. The messaging company Kik Interactive contacted npm, claiming the name infringed on their trademark. npm sided with the company and transferred the kik package name to Kik Interactive without Koculu's consent.
Koculu was furious. In protest, he unpublished all 273 of his packages from npm — including left-pad, a utility so small that most people would write its functionality inline:
// The entire left-pad module (simplified)
module.exports = function leftPad(str, len, ch) {
str = String(str);
ch = ch || ' ';
while (str.length < len) {
str = ch + str;
}
return str;
};
This function pads a string to a minimum length. That's it. In Python, you'd write "42".rjust(5, "0") and get "00042". It's a built-in string method — not even worth writing your own function, let alone installing a package.
But in the JavaScript ecosystem of 2016, left-pad had been downloaded nearly 2.5 million times in the month before it was removed. Not because it did anything complex, but because the JavaScript community had developed a culture of extreme modularity — publishing and depending on packages for even the most trivial operations.
The Cascade
When left-pad disappeared, here's what happened:
-
Direct dependents broke. A popular package called
line-numbersdepended onleft-pad. Whenleft-padvanished,line-numberscouldn't install. -
Transitive dependents broke. Babel, the JavaScript compiler used by millions of projects, depended on
line-numbers. Whenline-numberscouldn't install, Babel couldn't install. -
Everything that depended on Babel broke. React and thousands of other projects used Babel in their builds. Their builds failed because Babel couldn't install because
line-numberscouldn't install becauseleft-padwas gone.
The dependency tree looked like a pyramid balanced on a toothpick. Tens of thousands of projects depended (transitively) on 11 lines of code written by one person who could delete it with a single command.
Why Python Is Partly Protected
PyPI, Python's package index, has two protections that help — though, unlike npm today, it still lets owners delete their projects, so neither one would have stopped a left-pad-style deletion outright:
- Filenames can never be reused. Once a file such as
requests-2.31.0-py3-none-any.whlhas been uploaded, nobody can upload a different file under that name, even after deleting the original. A pinned version can disappear, but it can't be silently swapped for different code. - Yanking, a gentler alternative to deletion. Since 2019 (PEP 592), maintainers can "yank" a bad release instead of deleting it. pip skips a yanked release for new installs, but a requirement pinned to that exact version (
==) can still install it, so existing projects keep working.
Additionally, Python's culture of dependency management differs from the JavaScript ecosystem in important ways:
Python has a richer standard library. Functions like str.rjust(), str.zfill(), os.path.join(), and collections.Counter are built into Python. The JavaScript standard library was historically much smaller, which drove developers to npm for basic functionality. Python developers are less likely to install a package for something the standard library already handles.
Python has a different modularity culture. Python packages tend to be larger and more feature-complete. The requests library provides a comprehensive HTTP client. The equivalent in the JavaScript ecosystem circa 2016 was often assembled from dozens of micro-packages, each doing one small thing.
That said, Python is not immune to these risks. Consider this (simplified) dependency tree for a typical data science project using pandas 2.x:
your_project
├── pandas (requires numpy, python-dateutil, pytz, tzdata)
│ ├── numpy
│ ├── python-dateutil (requires six)
│ │ └── six
│ ├── pytz
│ └── tzdata
├── matplotlib (requires numpy, pillow, cycler, contourpy, ...)
│ ├── numpy (already required by pandas — same or compatible version?)
│ ├── pillow
│ ├── cycler
│ └── contourpy
└── requests (requires urllib3, certifi, charset-normalizer, idna)
├── urllib3
├── certifi
├── charset-normalizer
└── idna
Even this simplified tree holds 15 distinct packages for three direct dependencies, and the real number is higher: matplotlib also pulls in fonttools, kiwisolver, packaging and pyparsing, bringing the total to 19. If any one of them introduced a breaking change, removed itself from PyPI, or was compromised by a malicious actor, your project would be affected.
Supply Chain Attacks: The Modern Threat
The left-pad incident was accidental — a frustrated developer making a point. But it revealed a vulnerability that malicious actors have since exploited deliberately.
Typosquatting: In 2017, Slovakia's national computer security team found ten malicious packages on PyPI with names close to real ones — urlib3 instead of urllib3, setup-tools instead of setuptools. In 2019, a package called python3-dateutil (imitating the real python-dateutil) was caught stealing SSH and GPG keys, with help from jeIlyfish — "jellyfish" with a capital I in place of the first lowercase l. Users who made a typo in pip install could unknowingly install malware.
Dependency confusion: In 2021, security researcher Alex Birsan demonstrated that many companies' internal packages could be hijacked by publishing a public PyPI package with the same name. When pip install company-internal-tool ran, pip would sometimes pull from PyPI instead of the company's internal registry, executing malicious code.
Compromised maintainer accounts: In May 2022, an attacker took over the PyPI package ctx by registering its maintainer's expired email domain and resetting the account password. The new versions they published sent users' AWS credentials to the attacker. npm has suffered the same kind of attack on popular packages.
These aren't theoretical concerns. PyPI's safety team processed more than 2,000 malware reports in 2025 alone.
The Lessons
The left-pad incident and its aftermath taught the software industry several critical lessons:
1. Dependencies are liabilities, not just assets. Every package you install is code you're trusting to be correct, secure, and maintained. That trust should be earned, not given freely.
2. Trivial dependencies are the most dangerous. A package that does something genuinely complex (like pandas) is worth its dependency cost. A package that does something you could write in three lines is adding risk for no real benefit. In Python, always check whether the standard library has what you need before reaching for pip.
3. Transitive dependencies matter. You might carefully evaluate the three packages you install directly, but each of those packages has its own dependencies, and those have dependencies too. Understanding your full dependency tree is important for security-sensitive projects.
4. Lock files protect you. A requirements.txt with pinned versions acts as a snapshot: you know exactly what you had, and an == pin still installs a release even after it's been yanked. Tools like pip-compile from pip-tools go further, locking transitive dependencies and recording hashes.
5. The bus factor. "Bus factor" is the morbid but practical question: how many people need to get hit by a bus before a project is unmaintainable? For left-pad, the bus factor was 1. For critical dependencies, you want packages maintained by teams or organizations, not lone individuals.
What Changed After Left-Pad
The incident led to concrete changes across the industry:
- npm changed its unpublish policy. Once a package has been public for a short window, it can no longer be unpublished if other packages depend on it.
- PyPI added gentler tools. The "yank" mechanism (PEP 592, 2019) gives maintainers an alternative to deleting a release outright.
- Lock files became standard. npm introduced
package-lock.jsonin 2017. Python tools likepip-toolsand Poetry gained adoption. - Security scanning and hardening. GitHub added security alerts for vulnerable dependencies and, later, Dependabot. PyPI introduced Trusted Publishing in 2023 and made two-factor authentication mandatory for maintainers from 2024. Companies invested in supply chain security.
- Cultural shift toward fewer dependencies. Both the JavaScript and Python communities began questioning whether every micro-utility needed to be a separate package.
The Python Developer's Takeaway
When you run pip install something, you're adding that package — and all of its transitive dependencies — to your project's attack surface and maintenance burden. That doesn't mean you should avoid packages. It means you should:
- Check the package evaluation checklist (section 23.6.1) before adding a dependency.
- Use
pip freezeto record exactly what you installed. - Prefer the standard library for simple tasks.
- Periodically audit your dependencies for known security vulnerabilities (the
pip-audittool, installed withpip install pip-audit, or GitHub's Dependabot). - Understand that your
requirements.txtis a security document as much as it is a convenience tool.
The left-pad incident was a wake-up call for the entire software industry. Eleven lines of code, written by one person, deleted in a moment of frustration, broke thousands of projects worldwide. It demonstrated that the modern software ecosystem is a web of trust — and that web is only as strong as its weakest link.
Discussion Questions
-
The left-pad function was 11 lines of code. In Python, the same functionality is built into
str.rjust(). Why do you think the JavaScript community developed a culture of micro-packages while Python didn't? -
After the left-pad incident, should package registries prevent any package from being unpublished? What are the arguments for and against this policy?
-
If you discovered that one of your project's transitive dependencies had a security vulnerability, what steps would you take? How would virtual environments and
requirements.txthelp you? -
The "bus factor" concept suggests that packages maintained by individuals are riskier than those maintained by organizations. But many of the most important open-source packages started as individual projects. How should the community balance supporting individual maintainers with ensuring package reliability?
-
Python's standard library follows the philosophy of "batteries included" — providing a rich set of built-in modules. What are the advantages and disadvantages of this approach compared to a minimal standard library that relies on third-party packages?