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

推荐订阅源

F
Full Disclosure
WordPress大学
WordPress大学
小众软件
小众软件
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
腾讯CDC
量子位
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
Jina AI
Jina AI
Attack and Defense Labs
Attack and Defense Labs
S
SegmentFault 最新的问题
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Google Online Security Blog
Google Online Security Blog
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
罗磊的独立博客
L
LINUX DO - 最新话题
博客园 - Franky
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
AI
AI
C
Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
I
Intezer
S
Securelist

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 Fedidevs Dev Update #1 Django-TUI: A Text User Interface for Django Commands Automate Hatch Publish with GitHub Actions Words TUI: App for Daily Writing Textual App Auto Reload RDS Blue/Green Deployments Fly.io Certificate Renewal Using Testing Library with Selenium in Python The Fastest Way to Build a Read-only JSON API import __hello__ Enum with `str` or `int` Mixin Breaking Change in Python 3.11 Your Code Doesn't Have to Be Perfect Fixing _SixMetaPathImporter.find_spec() Not Found Warnings in Python 3.10 Upgrading Django App to Python 3.10 Integer Overflow Error in a Python Application Python Dependency Management MySQL Performance Degradation in Django 3.1 New Features in Python 3.8 and 3.9 The Code Review Batch Size The Code Review Bottleneck
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.