惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
B
Blog RSS Feed
美团技术团队
博客园 - 【当耐特】
C
Check Point Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
M
MIT News - Artificial intelligence
aimingoo的专栏
aimingoo的专栏
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 司徒正美
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
N
Netflix TechBlog - Medium
Vercel News
Vercel News
P
Proofpoint News Feed
IT之家
IT之家
I
InfoQ
腾讯CDC
H
Hackread – Cybersecurity News, Data Breaches, AI and More

Anže's Blog

The 15-Year-Old iptables Rule That Broke My DNS Fedidevs 9h Outage Postmortem Letting Claude Upgrade My Raspberry Pi Agents Day Lisbon DjangoCon Europe 2026 Speeding Up Django Startup Times with Lazy Imports Typing Your Django Project in 2026 Claude Fixes User Bug Jekyll to Hugo Migration Advent of Code 2025 🎄 Django bulk_update Memory Issue Migrating Gunicorn to Granian Disable Network Requests When Running Pytest Disable Runserver Warning in Django 5.2 Autogenerating og:images with Jekyll Power Outages and Gunicorn PID Files UV with Django Go-like Error Handling Makes No Sense in JavaScript or Python Packages Do Not Match the Hashes Pip Error Gotchas with SQLite in Production Fedidevs Dev Update #2 Django SQLite Production Config Django Streaming HTTP Responses Deploying a Django Project to My Raspberry Pi (Video) Thoughts on Code Reviews Django SQLite Benchmark Django, SQLite, and the Database Is Locked Error No Downtime Deployments with Gunicorn SQLite Write-Ahead Logging Writing a Pytest Plugin
How to Safely Update Your Dependencies
Anže Pečar · 2026-04-19 · via Anže's Blog

With all the supply chain attacks happening lately (litellm being the most recent example) keeping dependencies up to date without risk has been on my mind.

Below is everything I do to keep my personal projects secure, what we do at Fencer to keep our own codebase secure, and what we recommend to the startups we work with.

Be hesitant about what you add

The best way to reduce the risk of installing a compromised dependency is to avoid relying on it in the first place. Before adding a new dependency, I first make sure that implementing it ourselves would be too much work (or tokens!).

Besides that, I try to ensure the dependency is reliable and well maintained. There is no good way to determine this, but looking at the git history, issues, etc., can usually give you a rough idea.

Pin to hashes, not just versions

Having a lock file that pins your dependencies to a specific version is good, but not sufficient. Version numbers are not always immutable! A compromised maintainer account can republish a tag or a package version that points to different bytes. Pinning to a hash is what actually protects you.

pip itself ships a pip hash command that prints a hash in the format requirements.txt expects. For a single file:

$ pip hash Django-5.1.4-py3-none-any.whl
Django-5.1.4-py3-none-any.whl:
--hash=sha256:236e023f021f5ce7dee5779de7b286565fdea5f4ab86bae5338e3f7b69896cf0

To generate hashes for a whole requirements file, download everything first with pip download and then hash each file:

pip download -d ./wheels -r requirements.txt
for f in ./wheels/*; do pip hash "$f"; done

Stitch the hashes into requirements.txt so each pin looks like:

django==5.1.4 \
 --hash=sha256:de3f88c... \
 --hash=sha256:a5a5f9e...

And install with hash checking enforced:

pip install --require-hashes -r requirements.txt

The manual stitching is why most people reach for pip-tools (pip-compile --generate-hashes) or uv, but it’s good to know that plain pip has the primitives.

uv writes a hashed lockfile by default.

On the JS side, npm’s package-lock.json already stores integrity hashes for every resolved package. The thing to do is use npm ci rather than npm install in CI and production builds. npm ci fails loudly if the lockfile and package.json disagree and verifies the integrity hashes instead of silently re-resolving.

Pin GitHub Actions too

This one gets overlooked a lot: pin your GitHub Actions to a full commit SHA, not a tag. Tags are mutable. A compromised maintainer (or a compromised Action they depend on, like tj-actions/changed-files earlier this year) can repoint v4 at malicious code, and any workflow using @v4 picks it up on the next run.

So instead of:

- uses: actions/checkout@v4

Do:

- uses: actions/checkout@<full-sha-of-v4.2.2> # v4.2.2

Leaving the human-readable version as a trailing comment lets Dependabot bump the pin while keeping the SHA as the source of truth. If you have a lot of workflows to convert, pinact can rewrite all your tag references to SHAs in one pass.

Update periodically

If all your dependencies are locked and never change, you risk running insecure software. Even if none of your dependencies have open CVEs today, being several versions behind means that when a CVE does drop, you’ll have a mountain of accumulated upgrades to work through before you can patch.

The best solution is to automate your dependency upgrades. Dependabot is the canonical tool, but I find it hard to configure without it being too noisy. Dependabot is great for notifying you about and fixing CVEs. Still, for general updates, I prefer a single PR that bumps all your versions at once, and I usually create this workflow with a custom GitHub Action.

90% of the time, this dependency upgrade PR won’t require much work, but occasionally you’ll have to address a breaking change or a bug in an upstream repository. It’s easier to handle these small changes as they pop up than to deal with a whole pile of them after pinning a dependency on the same version for years.

If an individual package upgrade turns out to be a lot of work, you can always pin that specific package to the older version and handle it separately.

Don’t run updates locally

I also try to avoid updating dependencies on my work or personal laptop and prefer to do it in a CI job with no access to anything critical. This way the blast radius is greatly reduced.

For this we have a GitHub Actions job that runs automatically once a week, and we can also kick it off manually when we need newer dependencies.

Update with cooldowns

When you update dependencies periodically, you are always at risk that the latest version you’re upgrading to has been compromised. These attacks are usually detected and yanked within a few hours, but you can still get unlucky and pull the bad version during that window.

Dependency cooldowns help here. They instruct your tool to install a new version if it was published more than a specified period ago. The longer the cooldown, the lower the risk of installing a compromised package, but the longer you wait for legitimate security patches too.

I use a 5-day cooldown for most packages and a 1-day cooldown for packages I trust more and want to pull security fixes from quickly. The only such package at the moment is django.

uv supports this via exclude-newer and exclude-newer-package in pyproject.toml:

[tool.uv]
exclude-newer = "5 days"
exclude-newer-package = { django = "1 day" }

pip 26.0 (released in January 2026) added --uploaded-prior-to for the same purpose:

pip install --uploaded-prior-to 2026-04-12T00:00:00Z -r requirements.txt

For now, it only accepts absolute timestamps, so if you want a relative cooldown like “5 days ago,” you have to compute the timestamp yourself. The good news is pip#13837 has been merged and will ship in pip 26.1, adding ISO 8601 duration support so you’ll be able to write:

pip install --uploaded-prior-to P5D -r requirements.txt

P3D is ISO 8601 duration syntax: P is the period designator and 5D is 5 days. P1W would be a week, PT1H an hour (the T separates date from time parts).

On the JS side, all major Node package managers now offer some form of cooldown: pnpm added minimumReleaseAge in v10.16, Yarn shipped npmMinimalAgeGate in 4.10.0, Bun added minimumReleaseAge in v1.3, and npm itself followed with min-release-age in CLI 11.x.

A note on skills

Skills and skill marketplaces for your AI agents are another vector to worry about. Be very careful where and how you install skills, because they execute with the same permissions as your agent and can exfiltrate anything the agent can see. Treat an untrusted skill the same way you’d treat an untrusted npm package running a postinstall script.

Fin

None of this is bulletproof, but hashes, cooldowns, and a sensible upgrade cadence gets you most of the way there. At Fencer, we’ve helped various companies put these practices in place, so if you’d like a hand rolling them out, feel free to reach out.