DEV Community

Cover image for Verified, then swapped: two supply-chain attacks on the code you didn't write
Sentinelleai
Sentinelleai

Posted on Originally published at Medium

Verified, then swapped: two supply-chain attacks on the code you didn't write

On 4 November 2026, two unrelated reports landed on the same day.

In the morning, Debian, Ubuntu and the developers of APT, the program that installs software on both systems, published fixes for a flaw that let an attacker on the network swap a package after it had passed its signature check. The attacker's code then ran as root.

A few hours later, the French security firm Sekoia described a campaign it links to the Russian group APT29. For one week, a small web analytics script loaded by thousands of websites quietly added a second script that profiled every visitor's phone and served it an exploit chain to match.

One story is about servers, the other about websites. They teach the same lesson: most of the code that runs on your machines and in your visitors' browsers was written by someone else, and the checks we rely on cover less than we think.

A package, checked and then replaced

Debian and Ubuntu do not trust the mirror a package comes from. Every package list is signed with the distribution's key, and every package must match the checksum in that signed list. That is why APT has long been happy to download over plain HTTP: tampering in transit should be caught.

The flaw, tracked as CVE-2026-109518 and reported by researcher Max Justicz, breaks that promise in a narrow but serious way. In the words of the APT developers' advisory, an attacker can "bypass verification of downloaded packages using redirects and standard transient HTTP server errors that cause APT to retry downloads, leading APT to override existing hardlinks with a chosen payload." Debian's advisory puts it more simply: improper file handling when resuming a download "allows a man-in-the-middle attacker to replace a package after verification."

The check happened. The file that was checked is not the file that got installed. And because a .deb package runs its installation scripts as root, the attacker gets the whole machine.

Three details matter for anyone deciding how worried to be:

  • Who can do it. Anyone who can alter the HTTP traffic between the machine and the package server: a hostile network, a compromised proxy, or a malicious mirror, according to Ubuntu. It triggers when the machine installs or upgrades a package.
  • Which systems. The attack needs APT to retry failed downloads. That has been the default since APT 2.3.2, which means Debian 12 and later, and Ubuntu 22.04 LTS and later. Older releases are exposed only if someone set Acquire::Retries, and the APT developers note that "many user scripts" did. Ubuntu rates it High (CVSS 4.0: 7.7) and says derivative distributions are affected too.
  • What protects you. Ubuntu states that HTTPS between the machine and the archive blocks this attack, because the attacker must manipulate plain HTTP traffic. Setting Acquire::Retries to 0 also prevents it.

Fixes are out for every supported branch (Debian 13 "trixie" gets APT 3.0.4; Ubuntu published USN-8996-1, and by 5 November listed every release from 14.04 to 26.10 as fixed). No advisory reports attacks in the wild. Max Justicz has not published a technical write-up yet, so the diagram above is drawn from the advisories' own wording, not from exploit code.

A tracker that changed hands for a week

Sekoia's report, published the same day, starts with websites the attackers broke into directly: a Ukrainian gas producer, a US chemical industry association, a Bulgarian military-related site, and lookalike copies of sites in the US, Ukraine, the Czech Republic and Bulgaria. Sekoia links the campaign to APT29 with moderate to high confidence.

Then the investigators noticed requests from sites that had no reason to be targets. The common denominator was the Plerdy SEO Tracker, a third-party analytics script. Between 27 August and 3 September 2026, the attackers injected code into Plerdy's own file, a.plerdy.com/public/js/click/main.js. Every site that loaded it served its visitors two things: the real tracker, and a few lines that created a new <script> element pointing at a domain the attackers controlled. Sekoia writes that this exposed hundreds of legitimate websites and their visitors, and widened the campaign's reach across thousands of mostly Ukrainian sites.

What came next was selective. The second script, which the attackers named UltimateFingerprinter, collected 394 fields about the visitor's device and browser, hidden at the end of a 2.3 MB copy of the AngularJS library. Visitors with the right versions got an exploit chain:

  • iOS 16.7: two flaws, including CVE-2024-27834, to steal browser cookies;
  • iOS 18.5: the five-flaw DarkSword chain, which takes messages, WhatsApp and Telegram databases, the iCloud Keychain, Wi-Fi passwords and photos;
  • Android, Chrome 139 to 145 (143 excluded): two flaws that end in a payload Sekoia calls AndroSnitch, which collects Chrome data and saved Wi-Fi passwords.

Every one of those flaws had been patched before the campaign began. As Sekoia puts it, "a user running an up-to-date operating system is safe from these attacks." On the same 4 November, CISA added CVE-2024-27834, fixed by Apple in May 2024, to its catalogue of vulnerabilities known to be exploited, with a deadline of 18 November for US federal agencies.

The site owners did nothing wrong in the usual sense. They added a popular tool with one line of HTML. That line gave a third party the right to run any code it liked on their pages, for every visitor, and for one week that third party was not who they thought.

It is not only state groups. In October, Report URI and iVerify described DeadTag: more than 660 expired domains behind forgotten analytics and advertising tags, re-registered by someone else and still embedded on thousands of websites, an estimated 135 million visits a month. Some of that traffic was sold on to the same family of iPhone exploit kits (Report URI, 22 Oct).

Two months, one week of exposure

The gap between the compromise and the public report is typical. Plerdy's file was clean again on 3 September; Sekoia found the campaign in mid-October and published on 4 November. A site owner who waited for the news to check their scripts would have checked two months late. The control has to be in place before anyone knows there is something to look for.

Which control would have stopped what

Web browsers already have two tools built for this, and most small sites use neither.

Subresource Integrity (SRI) pins a script to its exact content. You add a hash to the tag, and the browser refuses to run the file if a single byte changes:

<script src="https://cdn.example.com/lib-4.2.1.min.js"
        integrity="sha384-…"
        crossorigin="anonymous"></script>
Enter fullscreen mode Exit fullscreen mode

You can compute the hash yourself: curl -s <url> | openssl dgst -sha384 -binary | openssl base64 -A. SRI would have refused Plerdy's altered file. The catch is that analytics vendors update their file in place at the same address, so a pinned hash breaks the day they ship a change. For those scripts, the honest options are to host a copy you control and update on purpose, or to accept that you are trusting the vendor completely.

Content-Security-Policy (CSP) tells the browser which domains a page may load scripts from. The injected Plerdy code needed to load a second file from a domain the attackers controlled. A policy that only lists the domains you actually use would have blocked that second file (as long as it is a plain list of domains: the 'strict-dynamic' keyword deliberately lets an allowed script load others), and with a report-to or report-uri directive it would have told you the attempt happened. CSP does not help if the domain on your list is the one that turns hostile, which is exactly the DeadTag case: there, the fix is to remove tags you no longer use.

Neither control replaces the oldest one: know what your pages load. Most sites have never listed their third-party scripts, so nobody notices when one of them changes or its domain expires.

What you can check tonight

If you run Debian or Ubuntu servers

  1. Check your APT version against the fixed versions in Ubuntu's table or the Debian tracker: apt list --installed '?source-package(^apt$)'.
  2. Upgrade with retries switched off, as Ubuntu recommends, so the update itself cannot be tampered with: sudo apt update && sudo apt upgrade -o Acquire::Retries=0. Unattended upgrades, on by default on Ubuntu, will apply it within a day if you leave them on.
  3. Move your package sources to HTTPS. Replace http:// with https:// in /etc/apt/sources.list and /etc/apt/sources.list.d/, then run sudo apt update and check for errors: some community mirrors do not support HTTPS.
  4. Do not forget the machines you never log into. Container images, CI runners and build servers run APT too. An image built before 4 November carries the old APT; rebuild it from an updated base.

If you run websites, yours or your clients'

  1. List every third-party script each site loads: open the browser's developer tools, Network tab, filter on JS, and reload. Or from a terminal: curl -s https://your-site | grep -oE '<script[^>]+src="[^"]+"' (this misses scripts added later by other scripts, so the browser view is the one to trust).
  2. For each one, ask three questions. Is it still used? Is its domain still registered by the vendor you think? Could you host a fixed copy yourself?
  3. Add SRI to every script with a fixed version, from a CDN or anywhere else. Tools such as srihash.org generate the tag.
  4. Write a Content-Security-Policy that lists only the script domains you use, start it in report-only mode to see what breaks, and send reports somewhere a person reads them. Google's CSP Evaluator and Mozilla's HTTP Observatory are free ways to grade the result.
  5. Update phones and browsers. All three exploit chains in this campaign used flaws that were already fixed.

The lesson

Signatures, checksums and well-known vendors are real protections, and they all share one assumption: that the thing you checked is the thing that runs. This week showed two ways that assumption fails, one on a server and one in a browser. Neither fix is exotic. Turn on HTTPS, update the package manager, list your scripts, pin what can be pinned, and tell the browser where code is allowed to come from.


I build Sentinelleai, which reads the repositories you deliver for vulnerable and malicious dependencies and checks the CDN scripts your pages load for subresource integrity.

Sources: Debian Security Advisory DSA-6607-1 and APT-2026-11-01 security update (4 Nov 2026); Ubuntu, "CVE-2026-109518 APT vulnerability fixes available" and USN-8996-1; Sekoia TDR, "Poisoned Source: APT29 watering holes target mobile devices with n-day exploits" (4 Nov 2026); CISA Known Exploited Vulnerabilities catalogue (4 Nov 2026); Report URI and iVerify, "DeadTag" (22 Oct 2026). Details may change as researchers publish more.

Top comments (0)