<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Damiano</title>
    <description>The latest articles on DEV Community by Damiano (@damiano_builds).</description>
    <link>https://dev.to/damiano_builds</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4184263%2F959d570c-975b-41c6-8482-fec09fd0bf8e.png</url>
      <title>DEV Community: Damiano</title>
      <link>https://dev.to/damiano_builds</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/damiano_builds"/>
    <language>en</language>
    <item>
      <title>The Bug Apple Patched 18 Months Ago Is Now on CISA's Exploited List</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Thu, 05 Nov 2026 10:03:51 +0000</pubDate>
      <link>https://dev.to/damiano_builds/the-bug-apple-patched-18-months-ago-is-now-on-cisas-exploited-list-3pgh</link>
      <guid>https://dev.to/damiano_builds/the-bug-apple-patched-18-months-ago-is-now-on-cisas-exploited-list-3pgh</guid>
      <description>&lt;p&gt;On November 4, 2026, CISA added CVE-2024-27834 to its Known Exploited Vulnerabilities catalog. The entry is one line long: "Apple Multiple Products Improper Authentication Vulnerability." Behind that line is a useful story about how real-world exploitation actually works, because this is not a fresh zero-day. Apple shipped the fix on May 13, 2024, almost eighteen months ago, in iOS 17.5, iPadOS 17.5, macOS Sonoma 14.5, tvOS 17.5, watchOS 10.5 and Safari 17.5 (with iOS 16.7.8 for older devices).&lt;/p&gt;

&lt;h2&gt;
  
  
  What the bug is
&lt;/h2&gt;

&lt;p&gt;CVE-2024-27834 is an improper authentication issue in WebKit that lets an attacker who already has arbitrary read and write capability bypass Pointer Authentication, the hardware-backed integrity check that is supposed to stop exactly this kind of memory corruption from becoming code execution. Apple's advisory credits Manfred Paul, working with Trend Micro's Zero Day Initiative; the bug was demonstrated as a zero-day at Pwn2Own Vancouver in March 2024. Its CVSS score is 5.5, which reads as "medium," and on its own it is: you still need another bug to get the read/write primitives first.&lt;/p&gt;

&lt;p&gt;That is precisely the point. Exploit chains are built out of links, and a medium-severity link is still a link.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it is in the KEV now
&lt;/h2&gt;

&lt;p&gt;The KEV addition follows reporting by Sekoia researchers Amaury-Jacques Garcon and Felix Aime on a campaign they call Poisoned Source, discovered in mid-October 2026 and attributed with moderate to high confidence to APT29, the group tied to Russia's Foreign Intelligence Service. The campaign turns websites into silent exploit delivery systems for iPhones and Android devices, using three infection vectors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compromised legitimate websites with injected malicious JavaScript. Reported victims include a major U.S. chemical industry trade association, a far-right European political party and a Bulgarian military-related website.&lt;/li&gt;
&lt;li&gt;Lookalike domains masquerading as high-profile sites in the U.S., Ukraine, the Czech Republic and Bulgaria, including one posing as an official U.S. anniversary celebration site.&lt;/li&gt;
&lt;li&gt;A supply chain compromise of Plerdy SEO Tracker, a Ukrainian web analytics service. Between August 27 and September 3, 2026, attackers injected malicious code into a JavaScript file (a.plerdy[.]com/public/js/click/main.js) that Plerdy's customers load on their own sites, which multiplied the campaign's reach without touching each site individually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The injected script profiles the visitor's device and browser, then the delivery infrastructure picks an exploit chain. Sekoia describes three classes. Class A hits iOS 16.7 devices by combining a WebKit remote code execution flaw (CVE-2025-43529) with CVE-2024-27834, the PAC bypass, to run JavaScript malware that steals browser cookies. Class B goes after iOS 18.5 with a longer DarkSword chain of five CVEs and aims at full device data: SMS and iMessages, mail databases, WhatsApp and Telegram data, iCloud Keychain and Wi-Fi passwords, contacts, call history, photos, Health, Calendar, Notes and iCloud Drive content. Class C targets Android Chrome versions 139 through 145 (skipping 143 for unknown reasons) and drops a payload Sekoia calls AndroSnitch to steal Chrome data and Wi-Fi passwords. Curiously, the Android chain aborts if the device identifies as a Samsung Galaxy S25 Ultra. Stolen data goes to a telemetry endpoint at ad.starx-net[.]com:8881.&lt;/p&gt;

&lt;p&gt;None of this requires the victim to tap anything. These are watering hole attacks: opening the wrong page on an unpatched device is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real lesson: n-days are the workhorse
&lt;/h2&gt;

&lt;p&gt;Every vulnerability in these chains was already patched when the campaign was running. CVE-2024-27834 had a fix for nearly a year and a half. That is the normal economics of exploitation now, and Sekoia says so explicitly: packaged n-day exploit chains, originally built by commercial surveillance vendors and reused by state actors, lower the cost and the skill threshold of advanced mobile exploitation. The attacker does not need a zero-day if enough of the world's phones are running an eighteen-month-old OS.&lt;/p&gt;

&lt;p&gt;There is also a severity lesson. A CVSS 5.5 bug that "only" bypasses a mitigation looks unimpressive in a triage queue. Chained behind an RCE, it is the difference between a crashed browser and stolen cookies. Severity scores describe a bug in isolation; attackers use bugs in combination.&lt;/p&gt;

&lt;p&gt;Two honest caveats. Attribution to APT29 is Sekoia's assessment at moderate to high confidence, not a proven fact, and Sekoia itself says it is unclear whether the campaign meant to hit specific high-value visitors or simply compromise as many unpatched devices as possible and sort the data later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do
&lt;/h2&gt;

&lt;p&gt;The boring advice is the correct advice, and for once it is fully within reach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update your iPhone, iPad, Mac and Android devices to the current OS, and Chrome to the current version. Turn on automatic updates so you are not the softest target in a watering hole.&lt;/li&gt;
&lt;li&gt;Restart your phone occasionally. These chains generally do not establish persistence, and a reboot wipes the implant from memory.&lt;/li&gt;
&lt;li&gt;If you are a genuinely high-risk user (journalist, activist, official), consider Apple's Lockdown Mode, which is built for exactly this class of threat.&lt;/li&gt;
&lt;li&gt;If you run a website, review which third-party analytics and tag scripts you load. The Plerdy compromise shows that your visitors' security now depends on your vendors' security. U.S. federal agencies have until November 18, 2026 to remediate CVE-2024-27834 under CISA's Binding Operational Directive 26-04; everyone else has the same deadline in practice, set by the attackers rather than by CISA.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A patch is only a fix when it is installed. Eighteen months is a long time to leave a door unlocked, and someone eventually walks through it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: CISA, "CISA Adds One Known Exploited Vulnerability to Catalog" (Nov 4, 2026); Apple, "About the security content of iOS 17.5 and iPadOS 17.5" (May 13, 2024); Sekoia's Poisoned Source research as reported by The Hacker News (Nov 4-5, 2026) and BleepingComputer (Nov 4, 2026).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>ios</category>
    </item>
    <item>
      <title>The Update Button Is the Attack Surface: Inside WordPress CVE-2026-119977</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Wed, 04 Nov 2026 10:09:03 +0000</pubDate>
      <link>https://dev.to/damiano_builds/the-update-button-is-the-attack-surface-inside-wordpress-cve-2026-119977-2gak</link>
      <guid>https://dev.to/damiano_builds/the-update-button-is-the-attack-surface-inside-wordpress-cve-2026-119977-2gak</guid>
      <description>&lt;p&gt;Yesterday's article looked at Microsoft's analysis of 2,056 exploited CVEs and what real-world exploitation has in common. Twenty-four hours later, the pattern has a live case study.&lt;/p&gt;

&lt;p&gt;On November 3, CISA added CVE-2026-119977 to its Known Exploited Vulnerabilities (KEV) catalog: a high-severity (CVSS 8.1) time-of-check time-of-use (TOCTOU) race condition in WordPress Core. The uncomfortable detail is where the vulnerability lives. The vulnerable moment is the update itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mechanics: a race against your own updater
&lt;/h2&gt;

&lt;p&gt;WordPress Core versions 4.7.0 through 7.1.3 are affected. When WordPress applies a full-package core upgrade, it unpacks the new files into a predictable directory beneath &lt;code&gt;wp-content/upgrade&lt;/code&gt;. That staged copy contains the WordPress installer, &lt;code&gt;setup-config.php&lt;/code&gt;. On many server configurations the staging directory is reachable over the web. An unauthenticated attacker who requests the staged installer at exactly the right moment can interact with it and progress to PHP code execution on the server.&lt;/p&gt;

&lt;p&gt;"Exactly the right moment" is doing a lot of work in that sentence. In Wordfence's lab testing, the staging directory normally existed for only a few seconds. A one-off request is very unlikely to land. But a script that polls the path continuously, for hours, improves its odds considerably, and that is precisely what attackers built.&lt;/p&gt;

&lt;p&gt;The vulnerability was responsibly reported by Umut Bayram. WordPress 7.1.4, released on October 28, fixes it by randomizing the staging directory, adding an index guard, and preventing unsafe promotion of staged configuration data. The fix was backported to every supported branch, from 7.1.4 down to 4.7.39. WordPress 4.6 and older no longer receive security fixes at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploitation started in minutes
&lt;/h2&gt;

&lt;p&gt;The timeline is the story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;October 28, 2026&lt;/strong&gt; — WordPress 7.1.4 ships as a security release covering 22 vulnerabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20:30 UTC the same day&lt;/strong&gt; — Wordfence deploys a blocking firewall rule, roughly three hours after the release announcement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nine minutes later&lt;/strong&gt; — the first request matching the new rule arrives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First 21 hours after public disclosure&lt;/strong&gt; — Wordfence records more than 18,500 requests targeting staged &lt;code&gt;setup-config.php&lt;/code&gt; installer paths. Two of the largest sources repeatedly polled small groups of sites for about 14.5 hours, behavior consistent with trying to catch the brief upgrade window. Other sources tested staging-directory and version-name variations, showing the attackers understood the race condition quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 30&lt;/strong&gt; — telemetry from Previdian records three exploitation attempts against its honeypot sensors from an IP address geolocated in the U.K.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;November 3&lt;/strong&gt; — CISA adds the CVE to the KEV catalog, citing evidence of active exploitation, and gives U.S. federal civilian agencies until &lt;strong&gt;November 6&lt;/strong&gt; to apply the fixes. Known ransomware campaign use: unknown. As of November 4, Wordfence was still blocking over 12,600 exploit attempts per day.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The chicken-and-egg problem, stated honestly
&lt;/h2&gt;

&lt;p&gt;There is an awkward truth in this vulnerability: patching is exactly when the window opens. If an attacker is polling your site for the staging path, the moment you click "Update Now" is the moment they have been waiting for.&lt;/p&gt;

&lt;p&gt;A reader raised this in the comments on the Wordfence disclosure, and the exchange is worth reading because both sides are right. The technically precise move, if you manage your own server or CDN layer, is to temporarily deny web access to &lt;code&gt;/wp-content/upgrade/&lt;/code&gt; for the duration of the update, at the server, firewall, or DNS level, and then update. But the Wordfence response is the operationally honest one: most of the millions of WordPress site owners cannot do that quickly, and delaying the update while they figure out how leaves them exposed to all 22 vulnerabilities for longer. The race window is seconds; the exploitation attempts observed so far were concentrated, not universal. For the overwhelming majority of sites, updating immediately is still the right call.&lt;/p&gt;

&lt;p&gt;That nuance matters more than the usual "patch now" headline. The correct advice is not "update" or "wait." It is: update now, and if you have the access and the skill to shield the staging path during those seconds, do that first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check today
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify the version, do not assume it.&lt;/strong&gt; Automatic background updates cover most sites, but they fail silently more often than people think. Dashboard → Updates should show 7.1.4 or the patched point release for your branch. If the site still runs 4.6 or older, it is outside security support entirely and needs a real upgrade, not a patch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the site stayed unpatched after October 28, look backwards.&lt;/strong&gt; Suspicious signs: requests to PHP files beneath &lt;code&gt;wp-content/upgrade&lt;/code&gt; in access logs, unfamiliar administrator accounts, plugins nobody installed, PHP files nobody placed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A firewall rule is not the patch.&lt;/strong&gt; This exploit requests a staged PHP file directly, without loading WordPress first, so plugin-level defenses only work if they run before the request reaches the file (Wordfence calls this Extended Protection). Whatever WAF you use, the rule buys time. Only the update removes the vulnerable behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat the KEV deadline as a floor, not a target.&lt;/strong&gt; November 6 binds U.S. federal agencies. If the site matters to you, the fact that probing began nine minutes after a defensive rule deployed tells you the timeline you are actually on.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The pattern, again
&lt;/h2&gt;

&lt;p&gt;An update mechanism is a privileged process that everyone trusts by design, running with write access to the application itself. The WordPress installer had to be staged somewhere for the update flow to work, and "temporary" plus "predictable" plus "web-accessible" turned out to be the whole vulnerability. It extends yesterday's theme uncomfortably well: the processes we trust most blindly deserve the most scrutiny, and few things are trusted more blindly than an updater.&lt;/p&gt;

&lt;p&gt;The update button is part of the attack surface. This week, so are the few seconds after you press it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: &lt;a href="https://wordpress.org/news/2026/10/wordpress-7-1-4/" rel="noopener noreferrer"&gt;WordPress 7.1.4 release note&lt;/a&gt;, &lt;a href="https://www.wordfence.com/blog/2026/10/wordpress-7-1-4-security-release-rce/" rel="noopener noreferrer"&gt;Wordfence disclosure and telemetry&lt;/a&gt;, &lt;a href="https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-core/wordpress-core-470-713-unauthenticated-upgrade-window-staged-installer-remote-code-execution?asset_slug=wordpress" rel="noopener noreferrer"&gt;Wordfence threat intel entry for CVE-2026-119977&lt;/a&gt;, &lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog" rel="noopener noreferrer"&gt;CISA Known Exploited Vulnerabilities catalog&lt;/a&gt;, &lt;a href="https://thehackernews.com/2026/11/cisa-confirms-hackers-exploiting.html" rel="noopener noreferrer"&gt;The Hacker News&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>wordpress</category>
      <category>vulnerabilities</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What Exploited Vulnerabilities Have in Common: Microsoft's 2,056-CVE Analysis</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Tue, 03 Nov 2026 10:12:52 +0000</pubDate>
      <link>https://dev.to/damiano_builds/what-exploited-vulnerabilities-have-in-common-microsofts-2056-cve-analysis-7pd</link>
      <guid>https://dev.to/damiano_builds/what-exploited-vulnerabilities-have-in-common-microsofts-2056-cve-analysis-7pd</guid>
      <description>&lt;p&gt;Microsoft Security Research published a short, numbers-heavy analysis on 2 November 2026 of which vulnerabilities attackers actually use. The headline figure: from 1999 through September 2026 roughly 383,000 vulnerabilities were disclosed, and only about 2,000 CVEs (around 0.5%) have been linked to real-world exploitation. The exploited set is 2,056 CVEs in their data. Fewer than 1 in 200 published CVEs ever gets used in an attack.&lt;/p&gt;

&lt;p&gt;This post goes through what the report says, what I think follows from it, and where the numbers need care. Everything quoted below comes from the &lt;a href="https://www.microsoft.com/en-us/security/blog/2026/11/02/exploited-vulnerabilities-common-data-driven-analysis-real-world-attacks/" rel="noopener noreferrer"&gt;Microsoft Security blog post&lt;/a&gt;, which I read in full.&lt;/p&gt;

&lt;h2&gt;
  
  
  The exploited set is not a sample of the whole
&lt;/h2&gt;

&lt;p&gt;The authors classified each CVE by impact class and platform, then compared the full population against the exploited subset. The shift is large:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Impact class&lt;/th&gt;
&lt;th&gt;Share of all published CVEs&lt;/th&gt;
&lt;th&gt;Share of exploited CVEs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Server-side RCE&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;td&gt;37%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local privilege escalation&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client-side RCE&lt;/td&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;td&gt;13%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-site scripting&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Denial of service&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;td&gt;7%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Operating-system code goes from 23% of all CVEs to 41% of exploited ones. Windows is the most affected platform in the exploited set.&lt;/p&gt;

&lt;p&gt;The report groups remote code execution, authentication bypass and local privilege escalation as "takeover" classes and says they account for 78% of exploited vulnerabilities. Its reasoning is plain: these give an attacker a foothold and control, while a crash or a limited script execution offers less to build on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patching runs slower than exploitation
&lt;/h2&gt;

&lt;p&gt;Two clocks are compared. On the defender side, Microsoft's exposure data from 2026 shows the median vulnerability patched in about two weeks on Windows workstations, more than a month on Windows Servers, 10+ weeks on Linux servers and macOS applications, and beyond a year on network appliances.&lt;/p&gt;

&lt;p&gt;On the attacker side, the report cites VulnCheck (29% of exploits in 2025 were launched within one day of CVE publication) and CISA data on the time from CVE publication to the KEV catalog: a mean of 89 days in 2024, 57 days in 2025, and 20 days for 2026 through September, with a median of 3 days.&lt;/p&gt;

&lt;p&gt;The two sets of numbers come from different sources and measure different things. Patch times describe Microsoft's customer telemetry. KEV timing describes when CISA added a CVE to its list, which can lag the first exploitation. So the comparison shows a direction, not a precise gap. The report also says AI-assisted exploit development is "a significant factor" in shortening timelines, attributing this to industry research without giving a measurement. I would treat that as a claim, not a finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers do and do not say
&lt;/h2&gt;

&lt;p&gt;A few cautions before using this to set priorities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Exploited" means known exploited.&lt;/strong&gt; The 2,056 CVEs are the ones with evidence of exploitation in Microsoft's catalog. A vulnerability not on the list may simply not have been observed. The 0.5% is a floor on what is used, not a ceiling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A label is not a capability.&lt;/strong&gt; The authors note that some vulnerabilities described as RCE end up as denial of service in practice. Impact class comes from the advisory text. Whether a flaw is reliably exploitable is a separate question the CVE entry often does not answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Base rates are not your environment.&lt;/strong&gt; The 78% takeover figure describes attackers in aggregate. Your own exposure depends on which of your systems are reachable and which identities they hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical part: impact times exposure
&lt;/h2&gt;

&lt;p&gt;The report's prioritization argument is that two things set a vulnerability's value to an attacker: what exploitation gives them, and how exposed and connected the asset is. A server with a service account holding broad permissions has a larger blast radius than a workstation with the same bug.&lt;/p&gt;

&lt;p&gt;For a small team without a vulnerability-management product, a workable reading is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start from what is reachable from the internet.&lt;/strong&gt; An inventory of exposed services comes before any CVE list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Within that, rank by class.&lt;/strong&gt; Server-side RCE, authentication bypass and privilege escalation first. XSS and crash bugs later, unless the asset is one where downtime is the real damage. The report explicitly names availability-critical systems and credential disclosure as the "next in line" slice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check KEV before CVSS.&lt;/strong&gt; A CVE on the CISA catalog has evidence of exploitation. A CVSS 9.8 without that evidence is a different kind of risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shrink the slow tail.&lt;/strong&gt; If network appliances take a year to patch, the realistic control is reducing their exposure, not waiting for the patch cycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look at the identity behind the system.&lt;/strong&gt; Trim over-broad service accounts. That limits what a successful exploit can reach, and it works even when patching cannot happen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The report ends with product recommendations for Microsoft Defender tools. That section is vendor material and I have not evaluated it; the data section stands on its own and is the useful part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Most vulnerabilities are never exploited, and the ones that are cluster in a few classes that give an attacker control. The patching windows in the data are longer than the exploitation windows. Spend effort where impact and exposure overlap, and use known-exploitation evidence to order the work.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: Microsoft Security Research (Yair Tsarfaty, Or Begam), "What exploited vulnerabilities have in common: A data-driven analysis of real-world attacks", 2 November 2026. This is a summary and commentary, not security or legal advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>infosec</category>
    </item>
    <item>
      <title>A Stealer With a Triage Analyst: VOLTA Outsources the Reading to an LLM</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Mon, 02 Nov 2026 10:14:07 +0000</pubDate>
      <link>https://dev.to/damiano_builds/a-stealer-with-a-triage-analyst-volta-outsources-the-reading-to-an-llm-1jnf</link>
      <guid>https://dev.to/damiano_builds/a-stealer-with-a-triage-analyst-volta-outsources-the-reading-to-an-llm-1jnf</guid>
      <description>&lt;p&gt;Stealers have been harvesting browser passwords, cookies and wallet seeds for years, and the bottleneck was never the stealing. It was the reading. A single infected machine coughs up thousands of lines of credentials, sessions and files, and a human operator had to decide which victims were worth a second look. This week Cisco Talos published research on VOLTA-AUTODOXER, a Windows infostealer that closes that gap the way any overworked team would: it hired an analyst. The analyst is a large language model.&lt;/p&gt;

&lt;p&gt;Ryan Fetterman's Talos writeup is the primary source here, and it is worth reading, because the interesting part of VOLTA is not that it uses AI. It is what it uses AI for.&lt;/p&gt;

&lt;h2&gt;
  
  
  A mature stealer, plus one new stage
&lt;/h2&gt;

&lt;p&gt;Strip the AI away and VOLTA is a competent, ordinary stealer. It harvests passwords and cookies from Chromium browsers, Outlook profiles, Slack workspaces, payment cards, cryptocurrency seed phrases, Discord, Telegram, Steam and Roblox sessions, Microsoft OAuth tokens, SSH and cloud provider keys, Wi-Fi profiles, and screenshots. Exfiltration runs over Telegram, in three stages.&lt;/p&gt;

&lt;p&gt;What VOLTA adds is the step after collection. The malware assembles what Talos calls a structured evidence bundle about the person behind the machine: geolocation and home address, employer and employer domain, inferred birth year, card last-4 digits, Slack workspaces, document authorship metadata, iPhone backup indicators, and a live valuation of the victim's Roblox account. To firm up the home address, it uses stolen session access to search the victim's own Gmail for receipts from Amazon, DoorDash, Uber Eats, Grubhub, Walmart and PayPal.&lt;/p&gt;

&lt;p&gt;Then it serializes the bundle and sends it to an LLM for appraisal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The analyst prompt
&lt;/h2&gt;

&lt;p&gt;The requests go through OpenRouter, a unified API gateway, with a chain of models: Anthropic's Claude Opus 4.6 by default, falling back to Moonshot's Kimi K2 and then Google's Gemini 2.5 Flash. The fallback logic is worth pausing on. VOLTA checks each response for refusal phrases like "I can't" or "I'm not able," and simply tries the next model. Talos describes a chain of up to three models, resilient against any one model's refusal.&lt;/p&gt;

&lt;p&gt;The system prompt, about 240 words, casts the model as a "forensic intelligence analyst" and includes the instruction that the bundle is the only permissible source of facts. The required output has a section titled "WHAT THEY'RE WORTH," ranking the victim's assets by monetization value into low, medium and high tiers, and a section on "THEIR OPSEC," assessing the victim's security posture and password hygiene.&lt;/p&gt;

&lt;p&gt;Read that twice. The model is not writing phishing lures or generating exploit code, the two use cases that dominate the AI-abuse conversation. It is doing triage: turning a raw credential log into a prioritized, citation-backed dossier. Talos names the shift precisely: "The axis of AI improvement here is not speed or scale but effort displacement."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the builder kit matters
&lt;/h2&gt;

&lt;p&gt;VOLTA's configuration is a template with six slots filled in at build time: the Telegram channel, API keys, and other settings. That structure suggests the author either sells a builder kit or compiles a bespoke binary per customer. If so, the LLM dossier is not one operator's clever trick. It is a product feature, and it changes who gets to run targeted operations:&lt;/p&gt;

&lt;p&gt;A year ago, ranking victims by monetization value took an experienced human reading logs. Now it takes a customer of any sophistication level pointing a binary at victims and reading ranked assessments in a Telegram inbox. The scarce skill that used to gate targeted crime was not the malware. It was the analysis. That skill just got productized.&lt;/p&gt;

&lt;h2&gt;
  
  
  The refusal problem, stated plainly
&lt;/h2&gt;

&lt;p&gt;There is a lesson here for the people building the models, and it is uncomfortable. The malware does not jailbreak anything. It asks a well-formed analytical question with a professional persona, and if one provider's model declines, it asks the next provider's. Safety training that lives per-model and per-provider fails open in a multi-model world, because the attacker routes around a refusal the way a web developer routes around a 503.&lt;/p&gt;

&lt;p&gt;"The bundle is the ONLY permissible source of facts" is, incidentally, better prompt hygiene than most production AI features ship with.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLMjacking: your key, their inference
&lt;/h2&gt;

&lt;p&gt;The same Talos cycle brought a second data point. NIK C2, a Go-based Windows RAT with over 52 commands, integrates an OpenAI client to run AI analysis inside the compromised session. Its key resolution is the part to remember: if the operator supplies no API key, the malware scans the victim's environment variables for OPENAI_API_KEY and uses that.&lt;/p&gt;

&lt;p&gt;That is LLMjacking, and it is doubly rude. The victim's own account funds the inference used to analyze the victim's own stolen data, and the requests are attribution-laundered through the victim's identity. Every developer who exports an API key into their shell profile for a weekend experiment has left that door open. Environment variables are readable by any process running as you. That was already true. Now it is priced.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes for defense
&lt;/h2&gt;

&lt;p&gt;None of the old advice dies, but two pieces of it get sharper.&lt;/p&gt;

&lt;p&gt;First, secrets in the browser and in the environment are the payload. VOLTA's whole pipeline starts with what Chromium stores and what your shell exports. Password managers beat browser storage, hardware-backed keys beat exported variables, and short-lived tokens beat long-lived API keys, precisely because they shrink the evidence bundle.&lt;/p&gt;

&lt;p&gt;Second, detection has a new surface. An endpoint making outbound calls to an LLM gateway with a serialized dossier is a strange beast, and egress rules that treat api.openrouter.ai or provider endpoints as ordinary HTTPS traffic will miss it. If your developers legitimately use these APIs, you already have the inventory to notice when a machine that shouldn't be calling them starts.&lt;/p&gt;

&lt;p&gt;VOLTA is not a glimpse of the future. The collection side is last decade's malware, and the analysis side is this quarter's API. The only new thing is the invoice: someone looked at the most boring, labor-intensive part of running a stealer operation and realized it costs a few cents of inference to delete it.&lt;/p&gt;

&lt;p&gt;The models are not the malware. They are the junior analyst, and they work for whoever asks.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>The handshake only proves the leaf: LXD's CVSS 10.0 certificate mix-up</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Sun, 01 Nov 2026 10:07:52 +0000</pubDate>
      <link>https://dev.to/damiano_builds/the-handshake-only-proves-the-leaf-lxds-cvss-100-certificate-mix-up-2a0p</link>
      <guid>https://dev.to/damiano_builds/the-handshake-only-proves-the-leaf-lxds-cvss-100-certificate-mix-up-2a0p</guid>
      <description>&lt;p&gt;Mutual TLS is supposed to settle the question of identity in both directions. The server proves itself to the client, the client proves itself to the server, and both sides walk away knowing who they are talking to. This week Canonical disclosed a bug that shows how thin the line is between "we use mTLS" and "we actually verified the client": CVE-2026-112816, a maximum-severity flaw in LXD and MicroCloud that let an attacker authenticate as any trusted client, without ever holding that client's private key.&lt;/p&gt;

&lt;p&gt;The short version: LXD checked whether the client's certificate was in its trust store. It did not check whether the client actually owned the key for that certificate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What LXD and MicroCloud are
&lt;/h2&gt;

&lt;p&gt;LXD is Canonical's system container and virtual machine manager, the thing that sits on a host and runs your containers and VMs behind a clean REST API. MicroCloud ties several machines running LXD into a single cluster, so a small fleet behaves like one private cloud. Both let remote clients talk to that API over HTTPS, and both use mutual TLS to decide who is allowed in.&lt;/p&gt;

&lt;p&gt;In mTLS, the client presents a certificate, and the server only accepts clients whose certificate it already trusts. The list of accepted identities is the trust store. If your certificate is in it, you are in. That is the design, and it is a sound one, as long as "presenting the certificate" actually proves you are its owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the check went wrong
&lt;/h2&gt;

&lt;p&gt;TLS does prove ownership, but only for one certificate: the leaf, the first one in the chain the client sends. The cryptographic handshake demonstrates possession of the private key for that leaf certificate. Everything after the leaf in the chain is just data the client attached. The client chose it. The client could have attached anything.&lt;/p&gt;

&lt;p&gt;LXD's HTTPS listener compared every certificate in the presented chain against the trust store, not just the leaf. So the attack is almost embarrassingly simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the public certificate of a client the server trusts. Public certificates are not secret; they are exchanged in handshakes, pinned in configs, checked into repos.&lt;/li&gt;
&lt;li&gt;Generate your own key pair and certificate, which you fully control.&lt;/li&gt;
&lt;li&gt;Connect, present your own certificate as the leaf, and attach the trusted client's certificate behind it in the chain.&lt;/li&gt;
&lt;li&gt;The server finds a trusted certificate somewhere in the chain and authenticates you as that client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You have now logged in as a privileged client while proving possession of exactly one private key: your own. If the impersonated client has administrative rights, Canonical says this can mean full access to the cluster and its virtual machines, and from there, root on the host machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  How exposed is a real install
&lt;/h2&gt;

&lt;p&gt;Two preconditions gate the attack, and they matter.&lt;/p&gt;

&lt;p&gt;First, the attacker needs network access to the HTTPS API. A standalone LXD server does not listen on the network by default: it answers on a local Unix socket until an administrator enables the remote API with the &lt;code&gt;core.https_address&lt;/code&gt; setting. Plenty of production LXD deployments enable it, because remote management is the point, but a default single-host setup is not reachable this way.&lt;/p&gt;

&lt;p&gt;Second, the attacker needs a trusted client's public certificate. Not secret, but not printed on the homepage either.&lt;/p&gt;

&lt;p&gt;MicroCloud has the sharper edge. When a new member joins the cluster, its certificate moves across the network. An attacker positioned to observe or interfere with that join can capture the member's public certificate and then authenticate as that member. The cluster's own plumbing becomes the leak.&lt;/p&gt;

&lt;p&gt;This is also why Ubuntu's own tracker rates the issue Medium while the CVSS score is 10.0. The 10.0 describes what happens when the preconditions line up: unauthenticated, remote, total compromise. The Medium reflects that the preconditions are real friction, not formalities. Both numbers are honest. They measure different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is affected and what fixes it
&lt;/h2&gt;

&lt;p&gt;Per Canonical's CVE record, the flaw is improper certificate validation in the HTTPS listener, and these LXD versions are affected, each before the fixed release that follows it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LXD 4.0.14, fixed in 4.0.15&lt;/li&gt;
&lt;li&gt;LXD 5.0.10, fixed in 5.0.11&lt;/li&gt;
&lt;li&gt;LXD 5.21.8, fixed in 5.21.9&lt;/li&gt;
&lt;li&gt;LXD 6.9, fixed in 6.10.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ubuntu's archive packages of LXD are listed as not affected or not shipped, so the fixed releases that matter are the snaps. On a snap install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap refresh lxd
&lt;span class="nb"&gt;sudo &lt;/span&gt;snap refresh microcloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One caveat from Canonical's own advisory: at the time of disclosure, the fixed point releases were still rolling out from the candidate channel to stable. Refreshing is not the same as being patched. Check the version you actually have after the refresh, and if the fix has not landed on your channel yet, the interim move is to restrict who can reach the API at all, because the attack needs to touch that listener.&lt;/p&gt;

&lt;p&gt;The bug did not originate at Canonical. The Incus project, the community fork of LXD, reported it first, and Canonical confirmed LXD shared it. Incus fixed the same flaw in version 7.6.0, along with 17 other security issues, and its advisory is blunt: there is no configuration fix, only restricting network access to the listener until you upgrade.&lt;/p&gt;

&lt;p&gt;As of the disclosure there is no evidence of exploitation in the wild. The CVE record carries a CISA assessment dated October 30 that the flaw is not known to be exploited, it is not on the Known Exploited Vulnerabilities list, and no public exploit exists. That is a state of the world, not a safety property. Now that the mechanism is public, the preconditions are all that stand between a lab curiosity and a working attack, and the mechanism is one paragraph long.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern worth keeping
&lt;/h2&gt;

&lt;p&gt;File this bug next to the ones that keep recurring across decades of TLS implementations: the library did the cryptography correctly, and the application trusted the wrong thing. The handshake proved possession of the leaf key, exactly as designed. The application then extended that proof, silently, to every certificate the client happened to attach. Nothing in TLS promised that. The chain is input, not evidence.&lt;/p&gt;

&lt;p&gt;If you write code that consumes a peer's certificate chain, the rule is short: authenticate on the leaf, and treat everything else as untrusted data until your own validation says otherwise. If you operate LXD or MicroCloud, the rule is shorter: refresh, verify the version, and know who can reach your API.&lt;/p&gt;

&lt;p&gt;The handshake only ever proves the leaf. The moment a system forgets that, anyone holding a public certificate and five minutes becomes someone you trust.&lt;/p&gt;

</description>
      <category>security</category>
      <category>linux</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>The Sandbox Held. The Mail Slot Didn't: One ChatGPT Share Link to Account Takeover</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Sat, 31 Oct 2026 09:59:37 +0000</pubDate>
      <link>https://dev.to/damiano_builds/the-sandbox-held-the-mail-slot-didnt-one-chatgpt-share-link-to-account-takeover-8n9</link>
      <guid>https://dev.to/damiano_builds/the-sandbox-held-the-mail-slot-didnt-one-chatgpt-share-link-to-account-takeover-8n9</guid>
      <description>&lt;p&gt;On October 28, the security firm Parameter published a writeup with a title that does not undersell it: "Do the Math: How We Took Over ChatGPT Accounts." The short version: an ordinary public ChatGPT share link, opened once by a logged-in victim, could run attacker-controlled JavaScript inside that victim's signed-in session - with full access to ChatGPT's authenticated APIs, the conversation list, and private chats never shared with anyone.&lt;/p&gt;

&lt;p&gt;OpenAI rated the report P1, its most serious category, paid a $5,000 bounty, and shipped a fix on October 26, eight days after the October 18 report. There is no evidence of real-world exploitation; Parameter ran its proof between its own accounts. Users have nothing to install or reset. So this is not a panic story. It is something better: a clean, fully documented lesson in how modern web defenses fail - not one at a time, but in composition.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sandbox that worked
&lt;/h2&gt;

&lt;p&gt;ChatGPT can turn an HTML code block into a small working webpage through its Preview feature. Because that page can contain arbitrary code, ChatGPT runs it inside a restricted frame on a separate web origin. Parameter's first finding was that this sandbox held: code in the Preview could not read the main page. Isolation, doing its job.&lt;/p&gt;

&lt;p&gt;The way in came from a second, unrelated feature in the same conversation: an interactive graph. Rendering the graph loaded MathLive, a math typesetting library, into the main ChatGPT page - and loading MathLive installed a listener for &lt;code&gt;postMessage&lt;/code&gt; events about its on-screen virtual keyboard.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;postMessage&lt;/code&gt; is the browser's mail slot between frames that are not allowed to read each other. It is safe only if the receiver checks who sent the message. MathLive's top-level controller used an origin policy of &lt;code&gt;none&lt;/code&gt;: it accepted keyboard-setting messages from any sender, and the keyboard layout it accepted was allowed to contain raw HTML. The graph had installed the receiver; the Preview supplied an untrusted sender. Two features, each defensible alone, together formed a bridge across the sandbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  From HTML injection to code execution
&lt;/h2&gt;

&lt;p&gt;Getting HTML into the main page was not yet code execution. The remaining steps show how many small gaps had to line up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The layout became &lt;code&gt;innerHTML&lt;/code&gt;.&lt;/strong&gt; When the virtual keyboard opened, MathLive joined the attacker-supplied layout into an HTML string and assigned it in the real ChatGPT page. A helper named &lt;code&gt;createHTML&lt;/code&gt; looked like a sanitizer; it was not. It marked the string as trusted and passed the HTML through unchanged. Script tags injected this way do not run - but an &lt;code&gt;iframe&lt;/code&gt; with a &lt;code&gt;srcdoc&lt;/code&gt; attribute does something better: it creates a fresh document, inline, that can load external scripts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The new frame inherited &lt;code&gt;chatgpt.com&lt;/code&gt;.&lt;/strong&gt; MathLive inserted the frame into the main document without a &lt;code&gt;sandbox&lt;/code&gt; attribute, and because the main page created it, the browser gave it the &lt;code&gt;https://chatgpt.com&lt;/code&gt; identity. Code inside it could reach the parent page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSP was bypassed with YouTube's help.&lt;/strong&gt; ChatGPT's content security policy correctly blocked inline scripts. It also allowed scripts from YouTube. YouTube's oEmbed endpoint accepted a JSONP-style callback parameter, and - even while rejecting an invalid callback - it echoed the callback text into the JavaScript response before the error message. A callback ending in &lt;code&gt;;void&lt;/code&gt; made the trailing JSON harmless, and the browser executed the injected code, since YouTube was on the allowlist.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At that point Parameter had arbitrary JavaScript running as chatgpt.com. Their proof enumerated the victim's conversations, opened a private chat, and exfiltrated a portion of it - not by scraping the screen, but by calling ChatGPT's own signed-in APIs. "Essentially, we could perform any action on behalf of the victim's ChatGPT account," they wrote.&lt;/p&gt;

&lt;p&gt;The final engineering step was delivery. Early versions needed the victim to connect a researcher-controlled app first. The convincing version put both pieces into one ordinary public conversation: a genuine graph to load MathLive into the main page, and an assistant-authored HTML Preview to send the two messages. Export that conversation as a share link, and opening the link was the only user action required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this one is worth reading twice
&lt;/h2&gt;

&lt;p&gt;Three design choices had to align: a message listener with no origin check, a settings message allowed to carry raw HTML, and a frame created without a sandbox attribute. The YouTube JSONP trick was only the last workaround; blocking it alone would have left the bridge intact. Every layer that failed had been built by someone as a defense or a feature. The vulnerability was not in any of them. It was in the assumption that they composed.&lt;/p&gt;

&lt;p&gt;That is the transferable lesson, and it applies far beyond chatbots. Any page that renders untrusted framed content and also hosts third-party widgets is one unchecked &lt;code&gt;postMessage&lt;/code&gt; listener away from the same shape of failure. The audit question is never "is the sandbox strong?" - Parameter confirmed ChatGPT's was. It is "what listeners, parsers, and frame factories live in the trusted page, and which of them accept input from outside?"&lt;/p&gt;

&lt;p&gt;The honest ending, as with last week's best incident reports: the researchers disclosed privately, the vendor reproduced, escalated, fixed, and paid, and the public writeup arrived after the fix. Boring process, excellent outcome. The exciting part is entirely in the math library.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: Parameter, "Do the Math: How We Took Over ChatGPT Accounts" (Oct 28, 2026), primary writeup; The Hacker News (Oct 31, 2026).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>chatgpt</category>
      <category>webdev</category>
    </item>
    <item>
      <title>GitLab Was Patched, the Server Was Still Vulnerable: CVE-2026-97229 Lives in Ruby</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Fri, 30 Oct 2026 10:17:41 +0000</pubDate>
      <link>https://dev.to/damiano_builds/gitlab-was-patched-the-server-was-still-vulnerable-cve-2026-97229-lives-in-ruby-22ie</link>
      <guid>https://dev.to/damiano_builds/gitlab-was-patched-the-server-was-still-vulnerable-cve-2026-97229-lives-in-ruby-22ie</guid>
      <description>&lt;p&gt;On October 20, 2026, someone registered an account on Alpine Linux's public GitLab instance and started sending it crafted CI configurations. The instance crashed eight times over two days. Four of those crashes were the attacker getting partway through an exploit and failing at the next step. Alpine's maintainers later wrote, with unusual honesty, "The attack came close to working."&lt;/p&gt;

&lt;p&gt;The vulnerability is CVE-2026-97229, a CVSS 10.0 unauthenticated remote code execution flaw in GitLab CE and EE. On October 29, CISA added it to the Known Exploited Vulnerabilities catalog, giving US federal agencies until November 1 to patch. This post is about the part of the story that should worry anyone running a self-managed GitLab: Alpine upgraded GitLab, and the vulnerability did not go away.&lt;/p&gt;

&lt;h2&gt;
  
  
  What CVE-2026-97229 actually is
&lt;/h2&gt;

&lt;p&gt;Per the CVE record and GitLab's October 12 critical patch release (19.4.2, 19.3.5, 19.2.8), the flaw is an integer overflow or wraparound in how GitLab evaluates regular expressions inside CI/CD configuration. An unauthenticated user can send a specially crafted regex in a CI config and execute arbitrary code on the server, because the regex validation is insufficient.&lt;/p&gt;

&lt;p&gt;The crucial detail: the underlying bug is not in GitLab's code. It is in Onigmo, the regular expression engine built into Ruby. GitLab's fix is a set of patches to the Ruby interpreter that GitLab bundles in its own packages and container images. Upstream Ruby shipped the same fix on October 23, in Ruby 3.3.13, 3.4.12 and 4.0.9.&lt;/p&gt;

&lt;p&gt;That single fact drives everything that followed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Alpine Linux timeline, and the upgrade that wasn't
&lt;/h2&gt;

&lt;p&gt;Alpine's post-mortem is one of the most transparent incident write-ups a distribution has published, and the timeline is the lesson:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;October 12&lt;/strong&gt; - GitLab releases the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 13&lt;/strong&gt; - CVE-2026-97229 is published. Alpine upgrades its instance, but to 19.3.4 instead of 19.3.5, the version carrying the fix. A human error, the kind that happens in every operations team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 20-21&lt;/strong&gt; - The attacker probes and then launches the main attack: eight worker crashes, four partial exploit progressions. They also download Alpine's GitLab container image, which the maintainers read as the attacker tuning the exploit to their exact build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 22&lt;/strong&gt; - Alpine upgrades to GitLab 19.3.6. The instance is still vulnerable, because that image uses standard, unpatched Ruby. The app was current; the interpreter was not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 23&lt;/strong&gt; - Ruby releases the upstream fix. The attacker's account is blocked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 25-26&lt;/strong&gt; - The attacker returns and tries to get back in. The attempts fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 26&lt;/strong&gt; - Alpine discovers its instance is still vulnerable despite the upgrade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 27-28&lt;/strong&gt; - The instance is closed and rebuilt from scratch. All passwords are reset, GitLab's secret keys replaced. Alpine verifies that the aports repository, all packages built October 20-24, and the October 21 stable releases were not tampered with, by checking commits against records the attacker could not reach: a developer's clone and all 45 builders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 29&lt;/strong&gt; - GitLab reopens on 19.3.7 with a patched Ruby.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alpine found no evidence that the attacker's code ever ran: no unexpected files, keys, hooks or processes, and timing probes showed no delays. But a successful exploit of this class would leave no crash and no web-log trace, some logs from the attack day were gone, and they had no outbound connection logging. So they treated the instance as compromised and rebuilt it. That is the correct call, and it is worth saying plainly: "we can't prove a negative" is a reason to rebuild, not a reason to relax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this bug class is bigger than GitLab
&lt;/h2&gt;

&lt;p&gt;The bug is in Ruby's regex engine. Any program that compiles regular expressions from untrusted input is exposed, not only GitLab. Alpine's own ruby package shipped the fix in 3.4.12 (releases 3.22, 3.23, 3.24 and edge) and 3.3.13 (release 3.21); &lt;code&gt;apk upgrade&lt;/code&gt; gets it. Other distributions shipped their own Ruby updates around October 23.&lt;/p&gt;

&lt;p&gt;This is the uncomfortable pattern of 2026's dependency incidents: the CVE gets a product name, so operators patch the product, while the vulnerable code sits one layer below in a runtime the product merely embeds. Your inventory says "GitLab 19.3.6: patched." Your interpreter says otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check on your own instance
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;If you installed GitLab from the official packages or official container images&lt;/strong&gt;, upgrading to 19.4.2 / 19.3.5 / 19.2.8 or later is enough: those bundles carry the patched Ruby. Verify you are actually on the fixed version, not the one below it. Alpine's 19.3.4-instead-of-19.3.5 slip is the most relatable mistake in the whole report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you run GitLab with your own Ruby&lt;/strong&gt; - built from source, or in a custom container image on a distribution Ruby - upgrading GitLab is not enough. You need Ruby 3.3.13, 3.4.12, 4.0.9 or later. Check the interpreter, not the app version string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you use Alpine's &lt;code&gt;alpinelinux/gitlab&lt;/code&gt; image&lt;/strong&gt;, pull &lt;code&gt;alpinelinux/gitlab:19.3.6&lt;/code&gt; again: the image published on October 27 contains Ruby 3.3.13. Any 19.3.6 image pulled before that date, and all older tags, are vulnerable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for probing.&lt;/strong&gt; Alpine's tell was repeated worker crashes under crafted CI configs and a newly registered account pulling the container image. Crashes of regex-heavy workers are not noise this month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log outbound connections.&lt;/strong&gt; Alpine's single biggest evidence gap was having no record of what the GitLab host connected to. Without it, "no evidence of compromise" collapses into "no evidence at all."&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest ending
&lt;/h2&gt;

&lt;p&gt;There is a detail in Alpine's report that deserves more attention than the CVE itself: they rebuilt a production service serving one of the most widely used Linux distributions on Earth not because they found a breach, but because they could not rule one out. No stolen data, no defaced releases, no smoking gun. Just eight crashes, a missing log window, and the discipline to act as if the worst case were true.&lt;/p&gt;

&lt;p&gt;Patching CVE-2026-97229 is the easy part. Knowing whether you patched the thing that was actually vulnerable - and being able to prove what happened before you did - is the real work.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: Alpine Linux, "GitLab security incident: attempted exploitation of CVE-2026-97229" (Oct 28, 2026); GitLab Critical Patch Release 19.4.2 / 19.3.5 / 19.2.8 (Oct 12, 2026); CISA Known Exploited Vulnerabilities catalog, entries CVE-2026-97229 and CVE-2026-76418 (Oct 29, 2026); The Hacker News (Oct 30, 2026).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>gitlab</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Your PDF says AES-256. The key had 9 bits of entropy</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Thu, 29 Oct 2026 10:00:17 +0000</pubDate>
      <link>https://dev.to/damiano_builds/your-pdf-says-aes-256-the-key-had-9-bits-of-entropy-585d</link>
      <guid>https://dev.to/damiano_builds/your-pdf-says-aes-256-the-key-had-9-bits-of-entropy-585d</guid>
      <description>&lt;p&gt;Open a password-protected PDF and the properties dialog will proudly tell you the document is secured with AES-256. That number describes the cipher. It says nothing about the key. This week, Rick de Jager at V12 published a disclosure showing what several PDF implementations actually do when they generate that key: MuPDF produced AES-256 file keys with only 9 bits of effective entropy, and Foxit derived its keys from the current timestamp. Both are recoverable without the password, in milliseconds, on a laptop.&lt;/p&gt;

&lt;p&gt;The label was honest. The math was fine. The randomness was not.&lt;/p&gt;

&lt;h2&gt;
  
  
  How PDF encryption is supposed to work
&lt;/h2&gt;

&lt;p&gt;When you encrypt a PDF with a password, the password does not encrypt the document directly. The software generates a random 32-byte file key, and that key encrypts the content with AES-256. Your password, salted and hashed, derives a second key whose only job is to encrypt the file key. Revision 6 of the standard even makes the password hashing deliberately slow to resist brute force.&lt;/p&gt;

&lt;p&gt;This is a reasonable design. It means that everything - every page, every invoice, every scanned contract - reduces to one question: how random is that 32-byte file key? If an attacker can guess the file key, the password and the slow hashing are decoration. That is exactly where these implementations failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  MuPDF: 512 candidate keys, 11 milliseconds
&lt;/h2&gt;

&lt;p&gt;MuPDF versions before 1.28.5 generate randomness with a generator from the drand48 family: a 48-bit linear congruential generator, seeded once with &lt;code&gt;time(NULL)&lt;/code&gt; - the current epoch time in seconds, truncated to 32 bits.&lt;/p&gt;

&lt;p&gt;Two implementation details do the damage. First, when MuPDF needs a random byte it steps the generator and takes bits 17 to 24 of the state - the shift discards everything below bit 17, the byte cast discards everything above bit 24. Second, in a linear congruential generator, carries only propagate upward: multiplication and addition can push bits toward higher positions, never toward lower ones. So the state bits above 24 can never influence any output byte, and can be ignored entirely.&lt;/p&gt;

&lt;p&gt;What remains is a 25-bit effective state. And here is the fatal detail: when the generator is seeded, the low 16 bits are always the constant &lt;code&gt;0x330e&lt;/code&gt;, and the timestamp occupies the rest. Of the 25 bits that matter, 16 are a known constant. Nine bits of the timestamp decide everything. Two PDFs created 512 seconds apart get byte-identical key streams.&lt;/p&gt;

&lt;p&gt;So the attack is not an attack. You generate all 512 possible 32-byte key streams and trial-decrypt. MuPDF even helps you: it draws the document &lt;code&gt;/ID&lt;/code&gt; from the same generator immediately before the file key and stores it in the trailer in plaintext, giving you a free validity check. V12's deliberately unoptimized Python does the whole search in about 11 milliseconds. The recovered key opens the document without either password - because a PDF's owner and user passwords both wrap the same file key, and the file key is all that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Foxit: SHA-256 of the time, some zeros, and the word "there"
&lt;/h2&gt;

&lt;p&gt;Foxit PDF Reader 2026.1.3.36551 uses a different construction with the same outcome. The file key is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHA-256( uint32_le(time(NULL)) || &amp;lt;32 uninitialized bytes&amp;gt; || "there" )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice the uninitialized bytes are zero-filled and add nothing. The literal string "there" is really in the code. The only unknown in the entire key derivation is a 32-bit timestamp.&lt;/p&gt;

&lt;p&gt;Foxit's format even provides a fast oracle: the &lt;code&gt;/Perms&lt;/code&gt; permissions block contains known plaintext, so verifying a candidate key costs one SHA-256 hash and one AES block decryption - no password hashing, no full document decryption. V12's multithreaded Rust implementation on a Ryzen 9 7950X tests 370 million timestamps per second. Every possible timestamp from the Unix epoch to now - the whole space, not a slice of it - falls in 4.85 seconds.&lt;/p&gt;

&lt;p&gt;Where did this come from? The same pattern appears in PDFium's initial public commit from May 2014, and the file header credits Foxit for the original code. The timestamp-plus-constant derivation has been quietly living in the PDF ecosystem's shared ancestor for over a decade, and editors that built on that lineage inherited it.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenPDF and iText: time plus free memory, through ARC4
&lt;/h2&gt;

&lt;p&gt;OpenPDF 3.0.5 and iText 9.7.1 route their "cryptographic" randomness through an &lt;code&gt;IVGenerator&lt;/code&gt; class: an ARC4 stream keyed with the current time in milliseconds concatenated with the JVM's free memory. The same stream produces the file key and 32 bytes of salts that the format stores in plaintext, so an attacker can test timestamp-and-memory guesses against the salts and read the key from the adjacent bytes. There is more variance here than in the MuPDF and Foxit cases, but the principle is identical: the advertised 256 bits of security rest on a state space that is far smaller and partially observable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this keeps happening
&lt;/h2&gt;

&lt;p&gt;None of these teams used a weak cipher. They used AES-256 correctly and then fed it keys from &lt;code&gt;time(NULL)&lt;/code&gt;, from uninitialized buffers, from the JVM's free memory counter. The pattern behind all four cases is the same three confusions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;random()&lt;/code&gt; is not a secret.&lt;/strong&gt; Functions like drand48 exist for simulations and games. They are deterministic by design, and their seed is the entire security budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time is not entropy.&lt;/strong&gt; A timestamp is a guessable, often roughly known value. Documents carry their creation date in metadata; the attacker frequently starts with a tight window before testing a single candidate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uninitialized memory is not entropy.&lt;/strong&gt; It is usually zeros, and even when it is not, it is not yours to rely on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The correct call has existed on every platform for decades: getrandom, /dev/urandom, CryptGenRandom, SecureRandom. Reaching for anything else in a security context is the bug, whatever the algorithm label says.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means in practice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Do not treat PDF passwords as real confidentiality.&lt;/strong&gt; They are a permissions hint for honest readers. For anything sensitive - contracts, medical records, official letters - encrypt the file itself with something designed for it (age, GPG, or an encrypted archive), then send the PDF inside that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you ship a PDF pipeline, check your generator's fix.&lt;/strong&gt; MuPDF resolved its side in 1.28.5 after disclosure. But the researchers' warning stands: documents already published with weak keys cannot be retroactively protected. Anything encrypted by an affected version stays open to anyone who bothers to run a script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you build software, grep for the pattern.&lt;/strong&gt; Any key, token, IV or salt derived from time, from process IDs, from memory statistics, or from a non-cryptographic PRNG is this bug waiting for its own disclosure post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The uncomfortable part of this story is how ordinary the code looks. Nothing here is an exotic cryptographic failure; it is plumbing that nobody audited because the dialog box said AES-256. The strongest cipher in the world is a label on the door when the key is under the mat - all 512 copies of it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Sources (all read in primary):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rick de Jager, V12: "The case of the 9 bit AES key" (27.10.2026) - &lt;a href="https://v12.sh/blog/the-case-of-the-9bit-aes-key" rel="noopener noreferrer"&gt;https://v12.sh/blog/the-case-of-the-9bit-aes-key&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>cryptography</category>
      <category>pdf</category>
      <category>infosec</category>
    </item>
    <item>
      <title>CVE-2026-97631: A 16-Year-Old Buffer Overflow That Runs Before Your Defenses Do</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Wed, 28 Oct 2026 10:13:06 +0000</pubDate>
      <link>https://dev.to/damiano_builds/cve-2026-97631-a-16-year-old-buffer-overflow-that-runs-before-your-defenses-do-5gmd</link>
      <guid>https://dev.to/damiano_builds/cve-2026-97631-a-16-year-old-buffer-overflow-that-runs-before-your-defenses-do-5gmd</guid>
      <description>&lt;p&gt;Your server's security model has a load-bearing assumption buried in it: the code that runs first is trustworthy. Secure Boot, the TPM, disk encryption, remote attestation — all of it sits on top of firmware that executes before any of those defenses wake up. Yesterday's big story is about what happens when that assumption fails. CVE-2026-97631 is a 9.5-severity memory-corruption flaw in TianoCore EDK II, the open-source reference implementation of UEFI that virtually every firmware vendor — AMI, Insyde, Phoenix, and Microsoft's hardened Project Mu fork — builds on. It is a buffer overflow that has been sitting in the PXE boot driver for sixteen years. And it came to public attention the worst possible way: by accident, months before the coordinated disclosure date.&lt;/p&gt;

&lt;p&gt;Here is what the bug actually is, why the disclosure timeline matters as much as the code, and what to do about machines you are responsible for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug, precisely
&lt;/h2&gt;

&lt;p&gt;The vulnerable component is the PXE Base Code driver — NetworkPkg/UefiPxeBcDxe — the part of EDK2 that lets servers, storage gear, and other devices boot from code downloaded over a local network. During a PXE boot, the driver keeps a receive request open for ICMP error messages and copies each received message into a fixed 504-byte field (Mode.IcmpError) in its private context structure. The copy loop's bounds handling is wrong in both the IPv4 and IPv6 handlers: for a message longer than 504 bytes it keeps copying, and it advances the destination pointer by the cumulative length rather than the per-fragment length. Nothing earlier on the receive path restricts the message length at all.&lt;/p&gt;

&lt;p&gt;The result, per the official advisory (GHSA-99cj-7hg9-mp4m): an unauthenticated network attacker who can deliver ICMP error messages to a machine while it is PXE-booting — over IPv4 or IPv6, by simple unicast, from any network distance, as long as it arrives during the boot window — can corrupt DXE-phase memory. Best case for the attacker, worst case for you: a "counterfeit event" that executes attacker-controlled code on the CPU, in the UEFI firmware environment, at ring 0. Before the operating system loads. Before Secure Boot measures anything. Before the TPM attests to anything.&lt;/p&gt;

&lt;p&gt;The flaw carries a CVSS v4 score of 9.5: network attack vector, low complexity, no privileges, no user interaction. It was discovered by Alex Matrosov of Anthropic and Dan Holmes of Google, and the CVE record was published on October 22.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "before your defenses" is the whole story
&lt;/h2&gt;

&lt;p&gt;Pre-boot code execution is a different category of bad than the application-layer flaws I usually write about here. The payload itself has no persistence — it runs once. But code running at ring 0 in the firmware environment runs before the chain of trust exists, which means it can poison the chain: install something that does persist, interfere with the measurements the TPM will later make, or get at secrets the boot process still holds. HD Moore put the concrete scenario plainly to Ars Technica: an attacker with short-term network access could use this to break disk encryption by hijacking program flow while the endpoint's TPM is still reachable. Easier still in a theft scenario.&lt;/p&gt;

&lt;p&gt;It also bypasses attestation — the cryptographic proof a machine gives to a remote server that its hardware and boot state are clean. If you can execute before the measurement, the measurement can be made to say whatever you want.&lt;/p&gt;

&lt;p&gt;This is the same architectural lesson as LogoFail (image-parsing bugs in firmware logos), PixieFail (the previous PXE boot flaws in EDK2), PKFail (non-production test keys shipped in hundreds of device models for over a decade), BootHole, and BlackLotus. The industry keeps finding that the code running before its defenses is its least-reviewed code. After PixieFail, researchers ran multiple fuzzing campaigns against EDK2 specifically to flush out the rest of this class. Those campaigns missed this one. Sixteen years, in a fixed-size copy loop, in the preeminent UEFI implementation on Earth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The disclosure was botched, and that changes your timeline
&lt;/h2&gt;

&lt;p&gt;The discoverers had arranged for the vulnerability to stay under wraps until a coordinated disclosure scheduled for January. Then, last month, AMI — one of the independent BIOS vendors that builds on EDK2 — published a public pull request that described the vulnerability in detail and included the fix. Once that was noticed, TianoCore members published the public advisory last week. The cat, as Dan Goodin wrote in Ars Technica, has left the bag.&lt;/p&gt;

&lt;p&gt;This is now the second botched security disclosure in a week, after the PHP2Shell mess. The mechanics of why it matters are identical and worth repeating: with public patch code, working out the trigger is not research, it is reading comprehension. Every advanced actor who cares now has both the vulnerability description and a reference fix to diff against. The window between "exploit trivially derivable" and "firmware patched across the fleet" is the exposure, and for UEFI that window is measured in months on a good day — firmware updates move slowly, through vendors, then through OEMs, then through whatever your org's maintenance process looks like. Moore's prediction is blunt: a "forever bug" that will live on in end-of-support systems permanently.&lt;/p&gt;

&lt;p&gt;One more uncomfortable detail: the upstream EDK2 advisory currently lists no patched version. Vendors are integrating the fix themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you run machines
&lt;/h2&gt;

&lt;p&gt;The honest caveats first. This is not a drive-by internet worm. Exploitation requires the ability to deliver ICMP messages to the target during its PXE boot window — meaning some position on the local network, and a target that actually network-boots. That realistically scopes the threat to insiders, ransomware operators who already have low-level internal access and want to elevate, and nation-state actors doing supply-chain or data-center work. For a laptop that boots from its own disk with PXE disabled, the practical risk is far lower.&lt;/p&gt;

&lt;p&gt;But if you run servers, storage, or anything that network-boots — and data centers do, at scale, by design — the checklist is short and unglamorous:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inventory what actually PXE-boots.&lt;/strong&gt; The attack window only exists while a machine network-boots. If a machine does not need PXE, disabling it removes the exposure entirely — this is the single cheapest mitigation available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat boot-time network segments as trust boundaries.&lt;/strong&gt; PXE traffic should live on isolated, controlled VLANs. If an attacker who lands in your flat office network can also reach machines during PXE boot, your segmentation is the vulnerability multiplier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track vendor firmware updates by name, not by OS patch cycle.&lt;/strong&gt; AMI, Insyde, Phoenix, and Microsoft Project Mu downstreams will ship fixes on their own schedules. OS-level package managers will not save you here; this lives below them. Ask your hardware vendors which EDK2 base they ship and when the ICMP fix lands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch EOL hardware.&lt;/strong&gt; Anything out of support will not be patched. For those machines, mitigation is network-side: disable PXE, isolate the segment, or accept and document the residual risk.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The pattern worth keeping
&lt;/h2&gt;

&lt;p&gt;Three stories this month rhyme, and the rhyme is the point. PHP2Shell: fix public before disclosure, scanners blind to the real exposure. The .NET out-of-band Brotli patch: the vulnerable decoder shipped everywhere, including in apps that bundle their own runtime. Now CVE-2026-97631: a 16-year-old copy loop in the firmware that runs before anything you call "security" starts. Different layers, same lesson — the trust chain is only as strong as its least-examined link, and the least-examined links are the ones everyone assumes someone else reviewed. Nobody reviewed the PXE driver's ICMP handler for sixteen years. Not because it was hidden, but because it was foundational, and foundational code has a way of becoming invisible.&lt;/p&gt;

&lt;p&gt;Check your boot path. It is the part of your security model you are currently taking on faith.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: TianoCore EDK II security advisory GHSA-99cj-7hg9-mp4m — &lt;a href="https://github.com/tianocore/edk2/security/advisories/GHSA-99cj-7hg9-mp4m" rel="noopener noreferrer"&gt;https://github.com/tianocore/edk2/security/advisories/GHSA-99cj-7hg9-mp4m&lt;/a&gt;; CVE record CVE-2026-97631 (published 2026-10-22) — &lt;a href="https://www.cve.org/CVERecord?id=CVE-2026-97631" rel="noopener noreferrer"&gt;https://www.cve.org/CVERecord?id=CVE-2026-97631&lt;/a&gt;; Ars Technica, "Code-execution flaw in UEFI firmware affects most servers on planet Earth" (October 27, 2026) — &lt;a href="https://arstechnica.com/security/2026/10/code-execution-flaw-in-uefi-firmware-threatens-most-se" rel="noopener noreferrer"&gt;https://arstechnica.com/security/2026/10/code-execution-flaw-in-uefi-firmware-threatens-most-se&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>infosec</category>
      <category>devops</category>
      <category>firmware</category>
    </item>
    <item>
      <title>One Token Leaked 155 AI Responses: Inside Cloudflare's GLM-5.3 KV Cache Exposure</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Tue, 27 Oct 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/damiano_builds/one-token-leaked-155-ai-responses-inside-cloudflares-glm-53-kv-cache-exposure-2o3m</link>
      <guid>https://dev.to/damiano_builds/one-token-leaked-155-ai-responses-inside-cloudflares-glm-53-kv-cache-exposure-2o3m</guid>
      <description>&lt;p&gt;On September 8, 2026, a security researcher told Cloudflare that very long prompts sent to one of its hosted models sometimes came back with text from other customers' sessions. Cloudflare reproduced the leak within a day, took the model offline forty minutes after declaring an incident, and restored it the same evening with a fix. Yesterday, October 26, they published the full postmortem alongside SGLang, the open-source inference engine involved. It is one of the most instructive infrastructure write-ups of the year, and the bug at its center is almost insultingly small: a one-token placeholder.&lt;/p&gt;

&lt;p&gt;Here is what actually happened, why it happened, and what it should change about how you think about hosted inference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The facts
&lt;/h2&gt;

&lt;p&gt;Between August 27 and September 9, 2026, the GLM-5.3 model on Cloudflare Workers AI (&lt;a class="mentioned-user" href="https://dev.to/cf"&gt;@cf&lt;/a&gt;/zai-org/glm-5.3, a text model from Z.ai aimed at agentic coding) had a tenant isolation flaw. Prompts longer than 681,280 tokens could receive responses containing data from other customers' recent requests to the same model. In total, 155 responses were affected. Cloudflare says the timing and volume of the affected requests are consistent with ordinary long-context use, not with someone repeatedly exploiting the bug, and that they found no evidence of deliberate exploitation. No other Workers AI model was affected.&lt;/p&gt;

&lt;p&gt;The report came in through HackerOne from Gadi Evron of Knostic. The timeline is worth reading in full, but the shape of it: report at 23:11 UTC on September 8, verification starting 08:00 the next morning, incident declared 13:22 after reproducing the leak with two independent accounts in 25 minutes, model set to private at 14:00, root cause identified at 14:35, corrected configuration deployed by 19:04, model public again at 19:40. Same-day root cause and remediation. Coordinated public disclosure six weeks later, after upstream fixes landed and shipped in SGLang v0.5.21 and v0.5.22.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two bugs, one leak
&lt;/h2&gt;

&lt;p&gt;The exposure needed both of these to exist at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug one: a configuration mismatch that everyone knew about.&lt;/strong&gt; Workers AI runs GLM-5.3 in a disaggregated setup: prefill GPUs process the prompt and build the KV cache, then hand it to decode GPUs over RDMA (via Mooncake) that generate the response. The KV cache is a shared pool of fixed-size pages in GPU memory. Critically, the pool size is not a configuration value: SGLang sizes it at startup from whatever GPU memory remains after loading the model weights. The model's advertised context length is a separate value. Nothing forces the two to agree.&lt;/p&gt;

&lt;p&gt;On the GLM-5.3 prefill nodes, the pool came out to 681,280 tokens while the configured context length was 1,048,576. Cloudflare was aware of the mismatch before launch. Their long-context tests returned successful HTTP status codes with streaming bodies and no error frames, so they assumed the engine handled the gap gracefully. It did not. The postmortem names this precisely: they validated response codes and performance at long contexts, but never validated the model's output and coherence. That is the testing gap that let a launch-day misconfiguration sit in production for two weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug two: the engine's rejection path was not actually a rejection.&lt;/strong&gt; In SGLang before v0.5.21, when a prompt arrived that was longer than the KV pool could hold, the scheduler rejected it. But the rejection path (Req.set_finish_with_abort) did not stop the request. It recorded the error for the client and swapped the prompt for a one-token placeholder, so the request could drain through the normal batch machinery without allocating real memory. In a single-node design that is mostly harmless. In disaggregated mode it was fatal: prefill generated the KV cache for the placeholder and transferred it to the decode node. The decode node received a "transfer complete" message and assumed prefill had overwritten the cache from previous requests with the full prompt. It had not. Pages are not zeroed between requests — zeroing would cost performance, so the next request simply overwrites what it is given — which meant most of the previous state was still sitting there. The decode node then generated a response while attending over stale cache pages holding other users' data.&lt;/p&gt;

&lt;p&gt;Read that again: the request was already rejected. The error existed. The machinery just had no way to stop a rejected request from flowing downstream, and the downstream had no way to verify what it received.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix, in two layers
&lt;/h2&gt;

&lt;p&gt;Cloudflare fixed the configuration and the engine separately, and the reasoning is the part worth stealing.&lt;/p&gt;

&lt;p&gt;Layer one: make the pool hold the full context. They adjusted the GLM-5.3 configuration to match their GLM-5.2 setup, freeing enough GPU memory that the prefill KV pool now holds about 2.04 million tokens, roughly twice the context length. A prompt that fits the context window now always fits the pool, so the rejection path is never reached for a valid request.&lt;/p&gt;

&lt;p&gt;Layer two: make the rejection path safe anyway. Their own words, paraphrased: relying on pool size alone is fragile, because quantization, tensor parallelism, CUDA graph sizes, or a future model version can silently change the pool size again. So they patched the engine so a request carrying a pending rejection is retired at the disaggregation admission queue, with its original error, before any bootstrap, allocation, or KV transfer happens — and had the decode node verify that the number of tokens transferred matches what it reserved. They proposed this upstream (PR #38704), found the fix already on SGLang main for a related path (#36988), and the maintainers merged a further early-rejection change (#38935). Both changes were validated against the researcher's reproduction script before the model went back online.&lt;/p&gt;

&lt;p&gt;They also now check KV pool size against context length directly, from the running engine, before any disaggregated deployment change ships — instead of inferring safety from HTTP 200s.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is a class of bug, not a bug
&lt;/h2&gt;

&lt;p&gt;The postmortem is honest about something most vendors would bury: this belongs to a whole class of KV cache memory-safety issues Cloudflare has been chasing for months. A page gets freed, reused, or read while something else still holds a claim on it. They describe two common causes: page lifetime within a single node (token generation, offload, the KV transfer receiver, and abort paths can all hold the same page — their earlier fixes for decode offload and double release on grammar errors were this kind), and races across nodes (a request aborted while prefill is still writing its KV cache to decode over RDMA). Upstream work since mid-September includes the admission fix, per-transfer checksums, abort-acknowledgement preservation, and deferred KV release turned on by default.&lt;/p&gt;

&lt;p&gt;If you run SGLang yourself — and plenty of self-hosted deployments do — the actionable line is simple: be on v0.5.21 or later, and treat any mismatch between your served context length and the engine's actual KV pool as a live vulnerability, not a performance footnote.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you are a customer, not an operator
&lt;/h2&gt;

&lt;p&gt;Hosted inference is multi-tenant by design. Your prompt shares GPU memory pages with strangers' prompts, and isolation is a software property, not a physical one. Cloudflare did everything right after the report came in, and the write-up is a model of transparency — but the exposure existed from launch day, in a mismatch that was visible in startup logs and metrics the whole time. Two weeks, 155 responses, no zeroing between tenants.&lt;/p&gt;

&lt;p&gt;Three takeaways I am keeping:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A passing status code is not evidence of correctness.&lt;/strong&gt; Cloudflare's own pre-launch tests green-lit a broken configuration because nobody checked whether the output made sense at long contexts. Whatever you are testing, validate the content, not the envelope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rejected work must never reach downstream stages.&lt;/strong&gt; An error path that lets the request keep flowing — even as a placeholder, even for good batching reasons — is an incident waiting for a second bug. The fix was not "make rejection impossible"; it was "make rejection actually stop the request."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration invariants need runtime checks.&lt;/strong&gt; "Nothing forces the two to agree, and it is our job to check that they do" should be printed on a poster in every platform team. If two values must match, check them from the running system, on every deploy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And if your threat model genuinely cannot tolerate cross-tenant exposure of any kind, the honest answer remains the one this series keeps coming back to: run the model on your own hardware, where there is no other tenant in the page pool. For everything else, this postmortem is a good reminder that "the provider handles isolation" is a claim to verify, not a default to assume.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: Cloudflare blog, "Cross-request KV cache exposure in the GLM-5.3 Workers AI model, and our remediation with SGLang" (October 26, 2026) — &lt;a href="https://blog.cloudflare.com/kv-cache-exposure-remediation/" rel="noopener noreferrer"&gt;https://blog.cloudflare.com/kv-cache-exposure-remediation/&lt;/a&gt; — and the upstream SGLang pull requests referenced there (#38704, #36988, #38935).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>llm</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>The Brotli flaw is in .NET now: Microsoft's out-of-band patch, and the self-contained trap</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Mon, 26 Oct 2026 10:06:52 +0000</pubDate>
      <link>https://dev.to/damiano_builds/the-brotli-flaw-is-in-net-now-microsofts-out-of-band-patch-and-the-self-contained-trap-4ke8</link>
      <guid>https://dev.to/damiano_builds/the-brotli-flaw-is-in-net-now-microsofts-out-of-band-patch-and-the-self-contained-trap-4ke8</guid>
      <description>&lt;p&gt;Yesterday I wrote about HTTP2Shell: CVE-2026-111497, a buffer overflow in the Brotli decoder that ended up in Node.js's HTTP stack and a long tail of Python packages. Today the same CVE forced Microsoft to do something it rarely does mid-cycle: ship out-of-band security updates for .NET 8, .NET 9, .NET 10, and .NET 11 Release Candidate 3.&lt;/p&gt;

&lt;p&gt;If you run .NET services, this one needs action, not a newsletter skim. And the way you update depends entirely on how you deploy - which is where teams usually get this wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Microsoft shipped
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://devblogs.microsoft.com/dotnet/dotnet-october-2026-oob-security-updates/" rel="noopener noreferrer"&gt;official .NET blog announcement&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET 8 → &lt;strong&gt;8.0.33&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;.NET 9 → &lt;strong&gt;9.0.22&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;.NET 10 → &lt;strong&gt;10.0.14&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;.NET 11 → &lt;strong&gt;Release Candidate 3&lt;/strong&gt; (RC2 users must update to RC3 to get the fixes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The release addresses two CVEs and re-releases two advisories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-111497&lt;/strong&gt; - the Brotli buffer overflow. Per the &lt;a href="https://www.cve.org/CVERecord?id=CVE-2026-111497" rel="noopener noreferrer"&gt;MITRE record&lt;/a&gt;, it lives in &lt;code&gt;c/dec/decode.c&lt;/code&gt; in Brotli 1.0.3 through 1.2.0 and lets a remote, unauthenticated attacker corrupt decoder memory, disclose memory contents, crash the process, or potentially execute code with a crafted compressed stream. In .NET terms, your app is exposed if it processes Brotli-compressed data through &lt;strong&gt;ASP.NET Core request decompression&lt;/strong&gt;, &lt;strong&gt;HttpClient automatic response decompression&lt;/strong&gt;, or the &lt;strong&gt;System.IO.Compression Brotli APIs&lt;/strong&gt;. Two important qualifiers from Microsoft: request decompression and automatic response decompression are &lt;em&gt;not&lt;/em&gt; enabled by default, and .NET Framework is not affected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-114148&lt;/strong&gt; - unsafe ownership metadata in Linux .NET tarballs, letting a local attacker modify files and escalate privileges. For .NET 10 and 11 this affects source-build tarballs only; for .NET 8 and 9 it also affects ASP.NET Core tarballs, including people who never build from source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-70197&lt;/strong&gt; (SocketAsyncEventArgs memory corruption) and &lt;strong&gt;CVE-2026-102870&lt;/strong&gt; (Blazor WebAssembly debugger) - re-released because the October 13 patches did not fully address them. If you patched on Patch Tuesday and moved on, you are not actually patched.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The self-contained trap
&lt;/h2&gt;

&lt;p&gt;This is the part that matters operationally, and it's the same trap I described for Node.js yesterday: installing the fixed runtime on the host does not reach every application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework-dependent apps&lt;/strong&gt;: install the updated runtime, restart the app. Done. You can verify with &lt;code&gt;dotnet --list-runtimes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-contained apps&lt;/strong&gt;: the runtime is bundled inside your deployment artifact. Installing 8.0.33 or 10.0.14 on the host changes &lt;em&gt;nothing&lt;/em&gt; for these. You must rebuild and republish with the updated runtime, and &lt;code&gt;dotnet --list-runtimes&lt;/code&gt; on the server will not tell you a self-contained app exists, let alone what it bundles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containers&lt;/strong&gt;: same story. Rebuild and redeploy from the updated .NET container images. Patching the host VM that runs your containers does not touch the runtime inside them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux tarball installs on .NET 8/9&lt;/strong&gt;: update from the refreshed ASP.NET Core Runtime tarball even if you don't build from source, because of the ownership fix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the audit question isn't "did we patch the servers?" It's "for every deployed artifact, which runtime is inside it, and when was it built?"&lt;/p&gt;

&lt;h2&gt;
  
  
  A five-minute audit
&lt;/h2&gt;

&lt;p&gt;An example check you can adapt - first, find Brotli exposure in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Are we decompressing Brotli anywhere?&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'*.cs'&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'BrotliStream|BrotliDecoder|DecompressionMethods\.Brotli|RequestDecompression'&lt;/span&gt; src/

&lt;span class="c"&gt;# Are we enabling it in the HTTP pipeline?&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'*.cs'&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'UseRequestDecompression|AutomaticDecompression'&lt;/span&gt; src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, for each deployment, answer the runtime question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Framework-dependent check (on the host):&lt;/span&gt;
dotnet &lt;span class="nt"&gt;--list-runtimes&lt;/span&gt;

&lt;span class="c"&gt;# Self-contained / container check: you need the artifact, not the host.&lt;/span&gt;
&lt;span class="c"&gt;# For containers, inspect the base image tag:&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'FROM mcr\.microsoft\.com/dotnet'&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Dockerfile*'&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any &lt;code&gt;BrotliStream&lt;/code&gt;, &lt;code&gt;DecompressionMethods.Brotli&lt;/code&gt;, &lt;code&gt;UseRequestDecompression()&lt;/code&gt;, or &lt;code&gt;HttpClient&lt;/code&gt; handler with &lt;code&gt;AutomaticDecompression&lt;/code&gt; set means the code path Microsoft lists as exposed. Treat those deployments as priority one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger picture
&lt;/h2&gt;

&lt;p&gt;In three days we watched one Brotli CVE travel through Node.js, Python package ecosystems, and now the entire supported .NET line - plus PHP's own two RCEs from the same week, which &lt;a href="https://www.bleepingcomputer.com/news/security/php2shell-security-flaws-now-sucessfully-exploited-in-the-wild/" rel="noopener noreferrer"&gt;BleepingComputer reports are now exploited in the wild&lt;/a&gt;. The pattern isn't that runtimes are careless. It's that a single compression library, vendored quietly into everything, turns one decoder bug into a global patch day. The scanners won't save you here: the vulnerable code is inside your runtime, inside your self-contained binary, inside your container image - places dependency scanners routinely miss.&lt;/p&gt;

&lt;p&gt;The fix list is short: update, rebuild, redeploy, verify the artifact - not the host. The teams that get bitten are the ones that stop at &lt;code&gt;dotnet --list-runtimes&lt;/code&gt; looking healthy.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: &lt;a href="https://devblogs.microsoft.com/dotnet/dotnet-october-2026-oob-security-updates/" rel="noopener noreferrer"&gt;Microsoft .NET Blog - October 2026 Out-of-Band Security Updates&lt;/a&gt;, &lt;a href="https://www.cve.org/CVERecord?id=CVE-2026-111497" rel="noopener noreferrer"&gt;CVE-2026-111497 (MITRE)&lt;/a&gt;, &lt;a href="https://www.bleepingcomputer.com/news/security/php2shell-security-flaws-now-sucessfully-exploited-in-the-wild/" rel="noopener noreferrer"&gt;BleepingComputer on PHP2Shell exploitation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>security</category>
      <category>csharp</category>
      <category>webdev</category>
    </item>
    <item>
      <title>HTTP2Shell: the Brotli decoder in your HTTP stack is critical, and your scanner won't flag it</title>
      <dc:creator>Damiano</dc:creator>
      <pubDate>Sun, 25 Oct 2026 13:34:19 +0000</pubDate>
      <link>https://dev.to/damiano_builds/http2shell-the-brotli-decoder-in-your-http-stack-is-critical-and-your-scanner-wont-flag-it-47fn</link>
      <guid>https://dev.to/damiano_builds/http2shell-the-brotli-decoder-in-your-http-stack-is-critical-and-your-scanner-wont-flag-it-47fn</guid>
      <description>&lt;p&gt;On Wednesday, October 21, Node.js disclosed CVE-2026-111497: a Node process that decompresses attacker-controlled Brotli data can leak memory or run arbitrary code. People called it Node2Shell and the Node release lines got their patches.&lt;/p&gt;

&lt;p&gt;By Friday, the same CVE had a new name and a much bigger blast radius. Aikido's follow-up disclosure calls it HTTP2Shell: the flaw lives in Google's Brotli decoder, and Brotli is bundled or auto-loaded by far more than Node. Python's most common HTTP clients, curl builds, Bun, Envoy, Istio. If your stack speaks HTTPS, some part of it probably decodes Brotli.&lt;/p&gt;

&lt;p&gt;This is the weekend patch you didn't plan. Here is what is actually known, from the primary sources, and the exact checks I would run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What CVE-2026-111497 is
&lt;/h2&gt;

&lt;p&gt;It is an out-of-bounds write in the Google Brotli decoder, affecting versions 1.0.3 through 1.2.0 on all platforms, with a CVSS v4.0 score of 9.2 (Critical). The decoder keeps accepting bytes when it should reject them; those bytes overflow a small working buffer and corrupt memory. A remote, unauthenticated attacker can crash the process, leak sensitive data, or potentially execute code.&lt;/p&gt;

&lt;p&gt;The detail that should change your weekend planning: it works in both directions. OpenAI, which discovered the vulnerability, describes it as unauthenticated remote code execution where a malicious client can take over an unpatched server, and a malicious server can take over an unpatched client that accepts compressed responses. Your public API is one attack surface. Every script, worker, and cron job in your infrastructure that calls out to someone else's API is the other one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The February part of the story
&lt;/h2&gt;

&lt;p&gt;OpenAI found the issue in February and reported it. The root cause was fixed in the Brotli source code at the time, but nobody recognized it as a security vulnerability, so no release shipped with the fix. The fix sat in the source tree for months while everything that bundles Brotli kept shipping the vulnerable decoder.&lt;/p&gt;

&lt;p&gt;That is why this patch is unusually annoying: there is no single "upgrade Brotli" button. Every project that bundles the decoder has to ship its own update, and you have to chase each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your dependency scanner is quiet
&lt;/h2&gt;

&lt;p&gt;This is the part most likely to bite Python teams. The PyPI packages Brotli and brotlicffi compile that same decoder, and requests, httpx, and aiohttp load them automatically whenever they are installed. Aikido counts roughly 82.8 million monthly downloads for the Brotli package.&lt;/p&gt;

&lt;p&gt;You are exposed if any environment has Brotli 1.2.0 or earlier, or brotlicffi 1.2.0.x, installed. And here is the catch: as of October 23, the advisory databases do not map the CVE to either PyPI package. pip-audit, Dependabot, and Safety will not flag the vulnerable versions. A green scan this week means nothing for this CVE. You have to check the installed versions yourself.&lt;/p&gt;

&lt;p&gt;The check is boring, which is the point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip show Brotli brotlicffi 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'Name|Version'&lt;/span&gt;
&lt;span class="c"&gt;# Vulnerable: Brotli &amp;lt;= 1.2.0, brotlicffi 1.2.0.x&lt;/span&gt;
&lt;span class="c"&gt;# Fixed:      Brotli 1.2.1,   brotlicffi 1.2.1.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check every environment, not just production: CI images, lambdas, the container your side project runs in. Then rebuild any image that bundles them. A patched requirements.txt with an unrebuilt image is still the old decoder.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix list, per stack
&lt;/h2&gt;

&lt;p&gt;From Aikido's reviewed list of fixed releases (as of October 23):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js: upgrade to 22.23.4, 24.22.1, or 26.12.1, or a newer release in your line. Node bundles Brotli, so the runtime version is what matters.&lt;/li&gt;
&lt;li&gt;Google Brotli upstream: 1.0.10, 1.1.1, or 1.2.1, depending on your branch.&lt;/li&gt;
&lt;li&gt;Python packages: Brotli 1.2.1 and brotlicffi 1.2.1.0.&lt;/li&gt;
&lt;li&gt;Bun: 1.4.4, and rebuild compiled executables that bundle it.&lt;/li&gt;
&lt;li&gt;Envoy: 1.40.0, 1.39.3, 1.38.6, or 1.37.8 (1.36.12 has the fix but that branch is unsupported).&lt;/li&gt;
&lt;li&gt;Istio: 1.29.9, 1.30.6, or 1.31.2.&lt;/li&gt;
&lt;li&gt;curl/libcurl: update its Brotli dependency or install your vendor's security-fixed package; statically linked copies need an application rebuild.&lt;/li&gt;
&lt;li&gt;OS packages: install the vendor security update. Vendors may backport the fix without changing the upstream version number, so read the changelog, not just the version string.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two useful exceptions: Apache's mod_brotli does not decode Brotli, and Nginx does not decode Brotli by default. Your classic reverse proxy is probably not the problem. The application runtimes behind it are.&lt;/p&gt;

&lt;p&gt;Then the unglamorous part: update locks, rebuild, redeploy, restart, and verify that the running workload actually uses the patched component. "Merged the fix" is not "running the fix."&lt;/p&gt;

&lt;h2&gt;
  
  
  If you can't patch everything this weekend
&lt;/h2&gt;

&lt;p&gt;Aikido's workarounds, in order of preference:&lt;/p&gt;

&lt;p&gt;For Python apps where Brotli is optional, uninstall both Brotli and brotlicffi, remove extras like httpx[brotli] from your requirements, regenerate locks, rebuild, restart. HTTPX, Requests, and urllib3 keep gzip and deflate without them. Two caveats: a server that insists on sending Brotli may now produce an unreadable response (fallback is not guaranteed), and this does nothing about Brotli decoders bundled elsewhere in the stack.&lt;/p&gt;

&lt;p&gt;For inbound traffic, reject requests whose Content-Encoding includes br at the proxy, load balancer, or middleware, before any vulnerable decoder touches the body. Match case-insensitively, including inside encoding lists.&lt;/p&gt;

&lt;p&gt;For outbound traffic, remove br from Accept-Encoding and reject responses that arrive with Content-Encoding: br before decoding. The gotcha is real: changing Accept-Encoding alone is insufficient, because a malicious server can send a Brotli response no matter what you advertise, and urllib3 picks its decoder from the response header. The check has to happen before automatic body processing; after the client has decoded the response is too late.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern worth keeping
&lt;/h2&gt;

&lt;p&gt;Yesterday it was PHP2Shell, today HTTP2Shell. Different ecosystems, same shape: a memory bug in a low-level library that everything else quietly bundles, discovered months before it was understood as a vulnerability, and a patch that has to be shipped one runtime at a time.&lt;/p&gt;

&lt;p&gt;The practical habit that survives both: know what decompresses on your behalf. Compression, parsing, and deserialization are where "just a library" turns into remote code execution, and the version that matters is the one in the running container, not the one in your lockfile's intent.&lt;/p&gt;

&lt;p&gt;Sources: Aikido's HTTP2Shell disclosure and fixed-release review (aikido.dev, Oct 23-24), the Node.js security releases of October 21, and OpenAI's advisories referenced by Aikido. Version numbers and CVE details above are from those documents; where scanner coverage is described as a snapshot ("as of October 23"), treat it as dated and re-check.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>node</category>
      <category>python</category>
      <category>security</category>
    </item>
  </channel>
</rss>
