After the recent wave of supply chain attacks, I changed one boring default on my servers and dev machines:
Package installs should not run arbitrary setup code unless I explicitly ask for it.
This is not theoretical anymore. The
XZ backdoor
showed how much damage a trusted upstream package can do.
tj-actions/changed-files
showed how quickly CI secrets can leak. The
Axios npm compromise
used a dependency with a postinstall payload. Microsoft’s
Mini Shai-Hulud write-up
is another reminder that attackers are targeting CI/CD credentials directly.
So here is the quick hardening pass I now want on machines that run npm install
or pip install.
npm: disable install scripts and delay new packages
Put this in your user npm config:
npm config set --location=user ignore-scripts true
npm config set --location=user allow-git none
npm config set --location=user min-release-age 7
npm config set --location=user audit true
npm config set --location=user strict-ssl true
npm config set --location=user save-exact true
The two important ones:
ignore-scripts=truestops npm lifecycle scripts such aspreinstall,install, andpostinstallfrom running automatically.min-release-age=7tells npm to avoid package versions published less than seven days ago.
The age gate is not magic. A malicious package can wait. But a lot of registry attacks get noticed in the first hours or days, and I do not want my servers or CI runners to be in the first wave.
You need a recent npm for min-release-age. I moved my server to Node 24.16.0
through nvm, which bundled npm 11.13.0. I did not upgrade npm separately.
pip: wheel-only, hashes, and the same delay
pip is different from npm. It does not have the same general postinstall
lifecycle model, but source builds can still execute package build code. So the
safer default is: wheels only, hashes required, and no fresh uploads.
python3 -m pip config --user set install.only-binary ':all:'
python3 -m pip config --user set install.require-hashes true
python3 -m pip config --user set install.uploaded-prior-to P7D
python3 -m pip config --user set global.no-input true
This makes casual installs fail. That is intentional.
If I type:
python3 -m pip install boltons==25.0.0
I want pip to complain that hashes are missing. Server installs should come from a pinned, hashed requirements file, not from whatever PyPI returns right now.
pip documents both
hash-checking mode
and
uploaded-prior-to.
Change install habits too
Config helps, but habits matter.
For Node projects, prefer:
npm ci
over:
npm install
Keep package-lock.json committed.
For Python projects, install from a locked file with hashes:
python3 -m pip install -r requirements.lock.txt
If a project cannot do this yet, that is useful information. It means the install path still depends on trust and timing.
Keep an explicit exception path
Some packages really do need install scripts. Native modules compile things.
Some tools download platform binaries. Some repos use prepare to install Git
hooks.
Fine. Make that explicit.
Run trusted setup commands yourself:
npm run prepare
Or build native dependencies in a disposable environment with no secrets:
- no SSH agent
- no npm or PyPI token
- no cloud credentials
- no production
.env - limited outbound network
Then copy the artifact into the real environment.
The point is not “never run setup code.” The point is “do not let every transitive dependency run setup code by default.”
Where I would start
Start with servers and CI:
- Disable npm install scripts.
- Add the seven-day npm release-age gate.
- Make pip wheel-only, hash-required, and seven-day delayed.
- Use
npm ciand locked Python requirements. - Move exceptions into a no-secrets sandbox.
This does not solve every supply chain problem. It will not catch malicious code that runs when you import a package. It will not save you from every compromised maintainer account.
But it removes one dangerous default:
Installing dependencies should not automatically execute code on machines full of secrets.
That is a small change with a lot of upside.
References
Related Posts
- 6 min readPost-Deployment Tests: Your Safety Net After the Code Ships
- 5 min readMonitor everything with Healthchecks.io
- 2 min readMake Vercel open source and self-hosted, you get Coolify
- 3 min readAt least do this for securing your Wordpress site as a quick win, use Wordfence
- 2 min readMonitoring your microservice stack with simple ping health checks using Healthchecks.io for free
- 3 min readWhy we are moving from GitLab self-hosted to gitlab.com
Share