• Agents
  • Docs
  • Pricing
  • Blog
Log in
Get started

Security for apps built with AI. Paste a URL, get a report, fix what matters.

Product

  • How it works
  • What we find
  • Pricing
  • Agents
  • MCP Server
  • CLI
  • GitHub Action

Resources

  • Blog
  • Docs
  • FAQ
  • Glossary

Security

  • Supabase Security
  • Next.js Security
  • Lovable Security
  • Cursor Security
  • Bolt Security

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • Imprint
© 2026 Flowpatrol. All rights reserved.
Back to Blog
Case Study

XZ Utils Backdoor: The 3-Year Long Con

Someone spent three years building trust in open source to plant a backdoor in a compression library used by every Linux server on the planet. Here's what that means for your app.

Flowpatrol TeamApr 2, 202611 min read
XZ Utils Backdoor: The 3-Year Long Con

The heist that almost worked

In March 2024, a Microsoft engineer named Andres Freund was annoyed that SSH on his laptop was slow.

Not alarmingly slow. Not obviously broken. Just 500ms slower than it should have been. Most people would have shrugged. Freund dug in. What he found, after tracing CPU usage back through a system library, was one of the most sophisticated supply chain attacks ever documented — a backdoor that had been three years in the making, inserted into a compression library running on virtually every Linux server on the planet.

It missed going fully live by about two weeks.


The dependency you never think about

XZ Utils is a compression tool. It has been part of Linux distributions for years. It handles .xz and .lzma files — common formats for distributing software packages and backups. On most systems, it's simply there, like a battery pre-installed in a remote control. You never open the settings. You never check the version. It just works.

That invisibility is precisely what made it valuable.

Because XZ Utils integrates with systemd, and systemd integrates with OpenSSH, a backdoor in XZ could reach into SSH authentication. SSH is the protocol that lets you log into servers, deploy code, and run commands remotely. Compromising that authentication layer — on every server running a modern Linux distribution — meant having a key to tens of millions of machines.

The attacker who planted this backdoor understood the target intimately. They chose XZ Utils not despite its obscurity, but because of it.


Three years of patience

The identity behind this attack operated under the name Jia Tan. The GitHub account JiaT75 appeared in 2021 and began contributing to various open-source projects — small fixes, careful work, nothing that triggered suspicion. In November 2021, the first contribution landed in XZ Utils.

Over the next two years, Jia Tan built a reputation. The contributions were technically sound and genuinely useful. They came consistently, without drama, without demanding attention. Open-source maintainers are chronically overworked and typically grateful for reliable contributors who just show up and do good work.

Meanwhile, other accounts appeared. "Jigar Kumar" and "Dennis Ens" — almost certainly sock puppets operated by the same actor — began pressuring XZ Utils maintainer Lasse Collin. The messages demanded faster development, complained about slow progress, and suggested, helpfully, that Jia Tan seemed like a capable person who could help with maintenance.

Collin was dealing with real burnout. The pressure from these accounts, combined with Jia Tan's track record of solid contributions, led him to grant Jia Tan commit access in January 2023. By March 2023, Jia Tan was a co-maintainer.

Here's the complete timeline:

DateEvent
2021Jia Tan GitHub account created, begins contributing to open source
Nov 2021First XZ Utils contribution
2022Sock puppet accounts begin pressuring maintainer Lasse Collin
2022–2023Contributions increase in scope and significance
Jan 2023Jia Tan gains commit access to XZ Utils
Mar 2023Jia Tan becomes co-maintainer
Feb 2024Backdoor shipped in versions 5.6.0 and 5.6.1
Mar 29, 2024Andres Freund discovers the backdoor

Three years. That's not opportunism — that's a long-term operation. The level of patience, the multiple personas, the technical sophistication of the eventual backdoor: this has the profile of a nation-state actor, though attribution has never been confirmed.

Diagram showing social engineering timeline and backdoor insertion chain


How the backdoor worked

The backdoor in XZ Utils versions 5.6.0 and 5.6.1 (CVE-2024-3094, CVSS 10.0) was not a simple patch to a source file. It was multi-stage, conditional, and deliberately built to avoid automated detection.

The malicious payload was hidden inside what looked like test files: tests/files/bad-3-corrupt_lzma2.xz and tests/files/good-large_compressed.lzma. These are the kinds of files that exist in every project's test suite. Nobody reads them. Static analysis tools don't flag binary test files as suspicious.

During the build process, a script called build-to-host.m4 extracted the payload from those test files. Critically, this extraction only triggered under specific conditions: building a .deb or .rpm package, on x86-64 architecture, using the GCC compiler. Miss any of those conditions and the build looks completely clean.

The extracted payload injected code into liblzma.so — the compiled library. From there, it used a legitimate ELF feature called IFUNC (indirect function resolution) to hook into process initialization. This is a technique that is hard to spot in binary analysis because IFUNC is a standard part of how shared libraries work.

Once loaded by a systemd-enabled SSH daemon, the backdoor intercepted the RSA_public_decrypt() function — part of SSH's authentication process. When an SSH connection arrived carrying the attacker's Ed448 cryptographic signature, the backdoor ran arbitrary code as root. Before authentication even completed.

The attacker needed only their own private key. No username. No password. Just knock on port 22 with the right signature and you're in.


How it was found

Andres Freund was not doing a security audit. He was not running a supply chain monitoring tool. He was benchmarking SSH performance on a personal system running Debian Sid — the bleeding-edge, unstable branch that early adopters use for testing.

He noticed two things: SSH logins were consuming about 500ms more CPU than expected, and Valgrind (a memory analysis tool) was throwing errors related to liblzma. That combination was odd. SSH doesn't normally use liblzma for anything performance-critical.

Freund followed the thread. He traced the CPU spike to the library, examined recent changes in XZ Utils, and found obfuscated code in the build scripts. He then reverse-engineered the backdoor — a significant technical undertaking — to understand what it actually did.

On March 29, 2024, he posted his findings to the oss-security mailing list. Within hours, major Linux distributions were rolling back to safe versions of XZ Utils.

The distributions that had already shipped the backdoored version — Fedora 40 and 41 beta, Debian Sid and Testing, openSUSE Tumbleweed, Kali Linux, Arch Linux, and briefly Ubuntu 24.04 — were all rolling releases or pre-release builds. Stable, production-oriented releases like Debian Stable, Ubuntu LTS, RHEL, and Amazon Linux never shipped the backdoored packages.

The operation failed because of a performance anomaly on one engineer's laptop. That's how close it came.


Why this matters for your vibe-coded app

Here is the uncomfortable part: the XZ Utils story is not just history. It is a template.

When you build with Cursor, Lovable, v0, or Replit, your app runs on Linux infrastructure. The servers those platforms provision, the containers they spin up, the CI/CD pipelines that deploy your code — all Linux. The AI tools you use to build are themselves running on Linux. You are many layers removed from the operating system, but you are not independent of it.

More directly: every application you ship depends on packages. Dozens of them, usually hundreds if you count the full dependency tree. You have read almost none of that code. You probably could not list all of it from memory.

AI coding tools add a specific wrinkle here. When Cursor or Claude generates a requirements.txt or package.json, it draws on training data. That training data reflects what was trusted and common at training time. The AI has no way to flag packages that were later compromised, or contributors who spent years building trust before inserting a backdoor. The tool recommends what worked before. It cannot know what changed.

The XZ Utils attacker understood this. They built trust first. The contribution history was real, the code quality was real, the reputation was real. All of that happened before the malicious code appeared. Any automated tool auditing commit history would have seen a reliable contributor doing good work.


What to check right now

If you deployed anything on Linux infrastructure around February–March 2024, these are the checks worth running.

CheckCommandWhat you're looking for
Installed versionxz --versionVersions 5.6.0 or 5.6.1 are backdoored
Library pathfind /usr -name "liblzma.so*"Confirms which version is loaded
Debian/Ubuntu downgradeapt install xz-utils=5.4.6-1Rolls back to the last clean version
Fedora downgradednf downgrade xz-5.4.6Same for RPM-based systems
SSH anomaliesjournalctl -u sshd | grep -i error

Most production systems running stable Linux releases were never affected. The backdoor only reached rolling-release and pre-release builds. But if you were running Debian Sid, openSUSE Tumbleweed, Kali, or Arch in early 2024 — and especially if those systems had public SSH access — it is worth confirming your current state.


The bigger lesson about dependency trust

Every dependency you add is a trust relationship. Not just with the package itself, but with everyone who has ever committed to it, everyone who maintains it today, and everyone who might contribute to it tomorrow.

The XZ Utils attack did not exploit a bug. It exploited trust. The backdoor was introduced by someone who had legitimately earned the right to commit code. There was no unauthorized access, no stolen credentials, no technical exploit at the point of insertion. The social engineering was the attack.

This changes what "checking dependencies" means. Auditing for known CVEs is table stakes. The harder question — the one the XZ Utils incident forces you to ask — is what happens to a project if its maintainer is compromised, burned out, or replaced by someone with different intentions. Who is actually writing the code that runs inside your app?

For most packages you pull in, the honest answer is: you don't know, and you have no practical way to find out. That is not a failure on your part. It is a structural reality of modern software. But it means the right response is defense in depth, not false confidence.

Specific habits that reduce your exposure:

  1. Pin dependencies in production. Floating version ranges (^1.2.3, >=2.0) mean your next install pulls whatever the current latest is. Pin to exact versions and update deliberately, not automatically.

  2. Use lockfiles. package-lock.json, yarn.lock, poetry.lock — these capture the exact resolved versions of your entire dependency tree. Commit them. Use npm ci or pip install --require-hashes in CI to enforce them.

  3. Audit before you upgrade. When a package release comes in, look at the diff. Not always feasible for every transitive dependency, but for anything that touches auth, crypto, or network I/O, a quick review of the changelog is worth the time.

  4. Follow stable releases in production. The XZ Utils backdoor hit rolling releases and pre-release builds. Stable, LTS releases have slower update cycles — which also means slower uptake of malicious versions. For production workloads, that tradeoff is worth it.

  5. Andres Freund found this because SSH was slow. Performance regressions, unexpected error logs, elevated resource usage — these are not always bugs. Sometimes they are the thread that unravels a much bigger problem.


What to do before your next deploy

  1. Confirm your XZ version on any Linux system that was running rolling releases in early 2024. xz --version takes three seconds.

  2. Switch to pinned dependencies in any app you are actively developing. Remove floating version ranges from direct dependencies.

  3. Commit your lockfile if you have not already. If it is in .gitignore, take it out.

  4. Set up automated dependency monitoring. GitHub's Dependabot, Snyk, or Socket.dev will alert you when a package in your tree gets flagged. You want to hear about it in minutes, not weeks.

  5. Scan your app with Flowpatrol. Dependency issues are one layer of the stack. Flowpatrol tests the parts you can actually control — your API endpoints, your authentication, your exposed configuration — and tells you what is broken before someone else finds it. Paste your URL and see where you stand.

The XZ Utils attack got caught because one engineer was curious about a 500ms slowdown. Your app deserves more than a lucky accident. Build it, then check it.


The XZ Utils backdoor (CVE-2024-3094) was discovered and publicly disclosed by Andres Freund on March 29, 2024. Technical analysis from JFrog Security Research, Datadog Security Labs, and Akamai. CISA advisory published March 29, 2024.

Back to all posts

More in Case Study

The 39-Minute Window: North Korea Compromised axios and It Landed in Your node_modules
Apr 2, 2026

The 39-Minute Window: North Korea Compromised axios and It Landed in Your node_modules

Read more
The Base44 Auth Bypass: Wix Paid $80M, Then Researchers Bypassed Every Login With Two API Calls
Apr 2, 2026

The Base44 Auth Bypass: Wix Paid $80M, Then Researchers Bypassed Every Login With Two API Calls

Read more
A 4-Digit PIN Was Guarding 3.2 Million Health Records
Apr 2, 2026

A 4-Digit PIN Was Guarding 3.2 Million Health Records

Read more
Unusual auth errors may indicate exploitation
Unexpected SSH loginslast -a | head -30Check for unfamiliar IP addresses
Watch for anomalies.