DEV Community

Cover image for Software Updates Are a Security Trap — Here's the Framework Designed to Fix That
Kamal
Kamal

Posted on

Software Updates Are a Security Trap — Here's the Framework Designed to Fix That

You probably updated your phone or laptop this week. You clicked install without thinking twice. And if you're a developer, your tools updated themselves too — pip quietly pulled in a new library, npm resolved a dependency tree, a Docker image refreshed in the background. None of it required your attention. That's the point.

But here's something nobody tells you — any of those updates could have been tampered with, and you'd never know. Not because of a weak password or bad wifi. Because the system delivering that update has a fundamental security problem that HTTPS and code signing (where software is cryptographically stamped by its author to prove it hasn't been modified) don't actually fix.

Modern software barely touches code you wrote directly. The average application pulls in hundreds of third-party packages, which pull in hundreds more. Every pip install, every npm install, every docker pull is a download from a repository that someone maintains — and that an attacker might be targeting. Compromise any link in that chain, and you don't hit one machine — you hit every machine that trusts that repository.

This article introduces The Update Framework (TUF) — an open specification built specifically to address this problem. By the end, you'll understand why the obvious defenses leave significant gaps, what TUF's core design insight is, and how it actually protects you. No prior security background required.

The Attacks That Actually Happened

Before looking at any solution, it's worth establishing that this threat isn't theoretical. Software supply chain attacks have produced some of the most significant security incidents of the past decade.

Transmission (2016): The popular BitTorrent client Transmission was compromised twice in a single year. Both times, an attacker replaced the official distribution on Transmission's own website with a version containing a remote access tool. Users who downloaded from the official URL received malware. There was no visible indication that anything was wrong.

Fedora's build server: Projects use signing keys — cryptographic credentials — to stamp software as authentic. If you have the key, you can stamp anything. Fedora's build server was broken into and the project's signing key was stolen. After the incident, Fedora added a Hardware Security Module (HSM) — a physical device specifically designed so the key stored inside can never be read out, even by someone with physical access to the machine — to prevent future key theft. Six to eight months later, a second attacker broke in again. The HSM worked — the key couldn't be extracted. So the attacker uploaded malicious versions of OpenSSH and other critical packages directly to the build server and signed them there, on the compromised machine where the HSM was present. The signature was genuine — produced by the real, untampered key. The problem is that a valid signature only means "someone with the key signed this" — and the attacker now controlled what got signed. The HSM stopped key theft. It did not stop the attack.

SolarWinds (2020): Attackers compromised the automated build pipeline that assembled and signed SolarWinds' Orion software. They injected malicious code before the signing step — meaning the resulting update was signed with SolarWinds' legitimate certificate, served over HTTPS from SolarWinds' official servers, and passed every integrity check customers had in place. Approximately 18,000 organizations installed the update, including US government agencies and major enterprises. Every defense that was present said the software was legitimate — because from those defenses' perspective, it was.

XZ Utils (2024): A threat actor spent roughly two years posing as a trustworthy open-source contributor to XZ Utils, a compression library present in most Linux systems. After gradually earning commit access, they introduced a carefully hidden backdoor in versions 5.6.0 and 5.6.1 — one that would have compromised OpenSSH on affected systems. The malicious versions were days away from being included in stable Debian and Fedora releases when a developer noticed unexpected CPU usage and traced it back to the library. No server was broken into. No key was stolen. The contributor appeared legitimate because, for two years, they were. The trust was earned and then abused.

These incidents share a common thread: the delivery mechanism was weaponized. In every case, software arrived through a trusted channel, appearing correctly signed, from an apparently legitimate source. And in every case, existing defenses — HTTPS, digital certificates, code signing — said everything was fine.


Why the Obvious Defenses Fall Short

When people ask "isn't this already solved by HTTPS / code signing / GPG?", they're pointing at real protections that address real threats. But none of them address the same threat model that TUF was designed for. Understanding the gap is important before understanding what TUF adds.

HTTPS authenticates the connection, not the content

HTTPS encrypts traffic and verifies that you're talking to the right server. That's genuinely useful — it prevents a network-level attacker from reading or modifying traffic in transit.

But consider what it doesn't protect against: a compromised server. If an attacker has broken into the repository and is serving content from it, they serve it over a perfectly valid HTTPS connection. The TLS certificate — the credential that actually makes HTTPS work — is legitimate./ The handshake succeeds. The padlock stays green. HTTPS tells you that you're talking to the right server — it says nothing about whether that server has been compromised or whether what it's sending you is safe.

The SolarWinds update was delivered over HTTPS. The certificate was valid. The connection was encrypted. None of that mattered, because the problem wasn't the connection — it was the content.

GPG signing is better — but has critical failure modes

Code signing with GPG (a widely used open-source cryptographic tool) moves the trust from "you're connected to the right server" to "this content was signed by a recognized key." That's a real improvement. The problem is what happens in practice.

The one-key model. Many projects use a single central signing key on a build server. When that server is compromised, the attacker has the master key. Everything signed from that point forward is trusted. Adding an HSM protects key extraction, but not key use by an attacker who can submit packages directly to the signing environment — which is exactly what happened to Fedora the second time.

Today this same structure plays out at much larger scale in CI/CD pipelines. Signing keys are often stored as environment variables or secrets in automated build systems — accessible to any code that runs in that pipeline. When Codecov's build script was compromised in 2021, it silently exfiltrated environment variables from thousands of CI pipelines that were running it. Any signing key stored as a CI secret was potentially in that exfiltrated data — meaning an attacker with those credentials could sign and publish packages that would look legitimate to every client checking for a recognized signature.

The developer keyring model. An alternative is maintaining a pool of authorized developer keys and trusting content signed by any of them. This sounds reasonable until you notice the implication: every key in the pool can sign every package. The person trusted to write documentation for a minor utility can sign the Linux kernel. A single compromised key anywhere in the pool becomes a vector for malicious packages that every client will trust.

Revocation as an afterthought. When a GPG key is compromised, the usual response is an email announcement, a race against the attacker, and hope that users act before they get hit. Revocation was never a first-class design goal.

Cryptographic signatures alone leave a structural gap

Even with a well-managed signing setup, signatures alone leave a problem that only becomes visible when you think carefully about time.

A signature only proves something was signed — not that it's the latest version.

Package metadata is a file that describes what software exists in a repository — names, versions, file sizes, and checksums. It's separate from the software itself, and it's what clients read to decide what to download and whether to trust it. Think of it like a shipping manifest: the packages are the cargo, and the metadata is the document that says exactly what should be in each box, how heavy it should be, and who authorized the shipment.

If package metadata version 2 supersedes version 1, version 1 still carries a valid cryptographic signature from the correct key. An attacker serving version 1 is presenting legitimately-signed content. There's nothing in the signature itself indicating that version 2 exists and version 1 should no longer be trusted.

There's another problem — how do you even know which key to trust in the first place? If an attacker signs fake metadata with their own key, that's technically a valid signature — just from the wrong source. You still need a secure channel to communicate which keys are authoritative. Signatures solve one part of the problem and expose another.


TUF's Foundational Insight

The Update Framework starts from a different premise than the approaches above.

Rather than asking "how do we prevent our repository from being compromised?", TUF asks something different: "What can we still guarantee once an attacker is already in?"

TUF doesn't try to keep attackers out. It assumes they're already in — and designs around limiting what they can actually do once they're there. Every design decision in TUF traces back to this idea. The goal isn't a perfect perimeter. It's making a breach as harmless as possible.

This philosophy matters more today than when TUF was first designed. Software supply chains have grown dramatically more complex — code passes through package managers, CI/CD pipelines, container registries, artifact stores, and third-party build systems before it reaches a user. Each step is a potential point of compromise. Designing around the assumption of breach isn't pessimism — it's the only realistic posture for a system this interconnected.

Secure Software Distribution


The Four Roles

TUF puts this idea into practice through a hierarchy of four roles. Each role is responsible for one specific type of metadata and uses its own dedicated keys. The security requirements for each role depend on two things: how often it needs to sign, and how bad it would be if its key were stolen.

Before walking through each role, one distinction matters: online vs. offline keys. Online keys live on internet-connected servers and sign automatically. Offline keys are stored on hardware with no network connection — harder to reach, and therefore harder to compromise. TUF assigns key type to each role based on risk.

TUF Key Management

The further down the tree you go, the higher the signing frequency and the lower the impact of a key compromise. The separation is deliberate — it lets you match key storage security to the actual risk.


Root

The root role is the trust anchor for the entire system. Think of it like a founding charter: it was established carefully, it defines who is authorized to do what, and changing it requires the involvement of multiple keyholders — not just one person acting alone.

Each role has two key types: a private key (kept secret, used to sign) and a public key (shareable, used by anyone to verify that signature). The root file contains the public keys for all four roles (including itself), and it is signed by the root private keys — TUF supports requiring multiple keyholders to sign together — in practice, that might mean three out of five designated maintainers, each holding a separate key, all must co-sign before any change to the root takes effect. No single person can unilaterally change the trust configuration.

Before a client can verify anything, it needs an initial copy of the root file. This happens when you first install the software — either the root file ships embedded in the client, or it's downloaded and saved the first time you connect. After that initial copy is established, every subsequent change to the root file must be signed by the previously-trusted root keys. An attacker who controls your repository cannot silently replace your trust hierarchy — they don't have the root keys.

Root keys are kept offline and used infrequently — only when rotating keys or changing trust configuration. Their compromise would give an attacker control over the entire system's trust, so they receive the most aggressive protection.

Targets

The targets role provides metadata about the packages themselves: the cryptographic hashes and expected file sizes that allow clients to verify what they download. A cryptographic hash is like a fingerprint for a file — a short string that changes completely if even one byte in the file changes. If the hash you compute after downloading matches the one recorded in the metadata, the file wasn't tampered with.

Targets also delegates signing authority to other parties for specific subsets of packages. Think of it like a building access badge programmed to open only certain doors — David's badge unlocks signing authority for notary/* packages, and no amount of trickery makes it work on anything else, because the restriction is enforced by the system, not by trust in David. A top-level targets role might say: "David is authorized to sign packages matching notary/*, and only those." If David's key is compromised — or if David turns out to be the XZ Utils scenario playing out in a different repo — how much damage gets done is limited to Notary packages, not the entire repository. The delegation can be revoked by the parent role without any action required on client systems.

This is the fundamental difference from the developer keyring model, where any developer key can sign anything. In TUF, scope is explicit, written into the metadata, and enforced at verification time.

Delegations can nest to any depth, with each level inheriting constraints from its parent. A sub-delegation cannot claim broader scope than what was delegated to it. This maps naturally onto how real projects work: different teams, different packages, different key requirements per team.

Snapshot

The snapshot role provides repository consistency. It publishes a file listing the current version numbers of every other metadata file in the repository.

Think of it like a photograph of the entire repository taken at a specific moment. Every element is captured simultaneously — which means you can't swap in an older version of any individual piece without breaking consistency with everything else in the frame..

Here's the problem snapshot solves: without it, an attacker could mix old versions of some metadata files with newer versions of others, creating a subtly outdated but internally self-consistent view of the repository. Snapshot prevents this by pinning all metadata version numbers simultaneously. If a client has verified the snapshot, it knows exactly which version of every metadata file it should be seeing — and will reject any combination that doesn't match.

The snapshot key is typically online, since the snapshot file needs to be updated whenever any package is published.

Timestamp

The timestamp role answers one question as cheaply as possible: "Has anything in the repository changed?"

The timestamp file is intentionally minimal — just the current time and a hash of the snapshot file. Clients poll it frequently without significant overhead. If the timestamp indicates the snapshot hasn't changed, the client doesn't need to download anything else. If the timestamp is stale — past its expiry — the client knows that something is preventing fresh metadata from arriving.

Think of it like a security guard who is required to radio in an "all clear" on a fixed schedule. If the radio goes quiet for too long, you don't need to know exactly what happened — you know something is wrong.

The timestamp key is online and signs automatically at regular intervals. If compromised, the attacker can lie about update freshness. That's the full extent of what this key can be used to do. Because the damage is bounded and the behavior is detectable via expiry, this is an acceptable trade-off for an online key.


Role What It Signs Key Storage Impact if Compromised
Root Trust configuration for all roles Offline (mandatory) Catastrophic — controls entire system
Targets Package hashes + signing delegations Offline (strongly recommended) High — can authorize arbitrary packages
Snapshot Current version of all metadata files Online (typical) Limited — can create inconsistent views
Timestamp Freshness indicator Online (mandatory) Low — can falsely claim freshness

Attacks and the Defenses TUF Provides

With the four roles in mind, we can map TUF's design decisions to specific, documented attack patterns. This is where the design philosophy becomes concrete.

Rollback attack. The attacker serves an old, validly-signed version of a package — one with known, exploitable vulnerabilities. This is more dangerous than it sounds: old packages have documented CVEs, and many package managers silently accept a downgrade if given an older version. Serving an old version of a package is, in practice, roughly equivalent to being able to compromise the target machine outright.

In concrete terms — imagine your bank's app being secretly downgraded to a version with a known login bypass vulnerability. The app looks identical. The icon is the same. But it's no longer safe.

TUF's defense: the snapshot file records the current version numbers of all metadata. Clients store the last-seen snapshot and refuse to accept any snapshot with a lower version number. Old metadata cannot be served without this version check failing. Since the snapshot locks down all package versions at once, a client cannot be served outdated packages without detection.

Freeze attack. The attacker serves the same timestamp file indefinitely, preventing clients from learning about new security updates. This is simpler to execute than most attacks in this space — no server compromise required, just the ability to block or delay traffic. The technique has been used in the wild specifically to prevent security updates from reaching devices.

TUF's defense: all metadata carries expiry timestamps. A frozen timestamp eventually expires. When it does, the client knows something is blocking normal operation and can alert the user or administrator. The attack is no longer silent.

Without that defense, you'd keep running vulnerable software forever — never knowing updates even existed.

Endless data attack. The server sends a continuous stream of data in response to a metadata request, filling the client's memory or disk until the system crashes. Both apt and yum — the package managers used to install software on Debian and Red Hat Linux systems respectively — had real versions of this vulnerability — apt would exhaust system memory, and yum would fill the disk silently and crash without logging anything, leaving the system in a broken state on the next reboot.

TUF's defense: targets metadata records the expected file size in bytes for every artifact. A client that has verified the metadata knows exactly how much data to expect and will refuse to accept more, aborting the connection if the server exceeds that limit.

Replay attack. The name sounds similar to rollback, and the end result looks the same from the client's perspective — stale metadata. The difference is where the attack originates. Where a rollback attack comes from a compromised server actively serving old content, a replay attack comes from someone who captured old legitimate traffic on the network and is feeding it back. Different source, same goal: get the client to trust an outdated view of the repository.

TUF's defense: version numbers must always go up — they're never allowed to go backwards. A client that has seen version 5 of a file will reject version 4, even with a valid signature from a currently-trusted key. Expiry timestamps provide a second layer of protection.

Mirror compromise. Mirrors are copies of a repository hosted on separate servers to speed up downloads — common in large open-source projects. When one is compromised, an attacker can use it to serve altered packages or outdated content.

TUF's defense: mirrors have no trust in TUF's security model. They're content delivery infrastructure, not trusted parties. All content is verified against metadata from the authoritative repository. A fully compromised mirror can at most serve garbage bytes (caught by hash verification), serve old content (caught by version and expiry checking), or serve nothing at all (a visible failure). Every outcome is either safe or detectable.

Verification Sequence


TUF Is Already Running Around You

One of TUF's stated design goals — described in the project's own documentation as "invisible usability" — is that end users should never need to understand any of this. Updates just work, verified automatically in the background. The security is transparent unless something fails.

You've likely interacted with TUF or a TUF-based system without knowing:

  • PyPI (the Python Package Index, the repository behind pip install) has adopted TUF as its security framework.
  • The GitHub CLI uses TUF to manage the trust roots behind gh attestation verify — when you verify a GitHub-issued build attestation, TUF is what makes that root of trust trustworthy.
  • Sigstore — the open-source signing infrastructure used across the cloud-native ecosystem to sign containers, packages, and artifacts — uses TUF to secure its own root of trust. The keys that make Sigstore's transparency logs trustworthy are themselves managed by TUF.
  • Honda and Toyota vehicles run Automotive Grade Linux, which uses TUF for software updates. Uptane — a TUF variant built for vehicles, which deal with constrained hardware, spotty connectivity, and update requirements where getting it wrong has physical consequences — has been adopted by major automakers as the standard for securing over-the-air updates. Buy a new car in the next few years and there's a good chance TUF is quietly managing its update security.

The next time your phone asks you to install an update — or the next time pip install or npm install runs in your CI pipeline — something like TUF may be quietly working in the background. Making sure what arrives is exactly what the developer signed off on. Not because the connection is encrypted, but because every link in the verification chain — from the root keys all the way down to the hash of the file you're about to run — has been checked, step by step, before anything lands on your machine.


The Update Framework is a graduated CNCF (Cloud Native Computing Foundation) project. Learn more at theupdateframework.io.

Top comments (0)