<?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: Haven Messenger</title>
    <description>The latest articles on DEV Community by Haven Messenger (@havenmessenger).</description>
    <link>https://dev.to/havenmessenger</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%2F3891528%2F1736c7dd-a6a7-443a-863c-0abb7d56e358.png</url>
      <title>DEV Community: Haven Messenger</title>
      <link>https://dev.to/havenmessenger</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/havenmessenger"/>
    <language>en</language>
    <item>
      <title>What Plugging Something Into Thunderbolt Actually Exposes</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Sun, 23 Aug 2026 12:18:45 +0000</pubDate>
      <link>https://dev.to/havenmessenger/what-plugging-something-into-thunderbolt-actually-exposes-1iim</link>
      <guid>https://dev.to/havenmessenger/what-plugging-something-into-thunderbolt-actually-exposes-1iim</guid>
      <description>&lt;p&gt;Thunderbolt, and the USB-C ports that carry it, support direct memory access, DMA for short. A DMA-capable device can read and write your computer's memory directly, without asking the operating system's permission each time, which is exactly why external GPUs and high-speed storage over Thunderbolt perform as well as they do. The CPU isn't a bottleneck in the middle of every transfer.&lt;/p&gt;

&lt;p&gt;That's also the entire attack surface. A peripheral that can write to memory directly can, in principle, write to memory it has no business touching. And a peripheral that can read memory directly can read whatever is sitting there, including things like disk encryption keys that are held in RAM while the machine is unlocked or even while it's asleep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thunderclap, 2019
&lt;/h2&gt;

&lt;p&gt;Researchers from the University of Cambridge, Rice University, and SRI International published Thunderclap in 2019, demonstrating that a malicious Thunderbolt peripheral, something as unassuming-looking as a charger or a dock, could read sensitive memory contents from a plugged-in machine within seconds, even on systems that had some DMA protections enabled but not configured strictly enough to stop it. The point wasn't a single unpatched bug. It was that the trust model for Thunderbolt peripherals assumed a level of good faith that a truly malicious device has no reason to honor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thunderspy, 2020
&lt;/h2&gt;

&lt;p&gt;Björn Ruytenberg at Eindhoven University of Technology followed up in 2020 with Thunderspy, which went further: it could bypass Thunderbolt's security levels entirely, including on a locked or sleeping machine, by reprogramming the controller firmware to make an untrusted device appear as a device the user had already approved. Disk encryption didn't save the machine either, because the attack could extract keys held in memory rather than needing to break the encryption itself. The disclosure noted that the attack required physical access for as little as five minutes, with inexpensive hardware, and left no trace in the operating system's logs afterward.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why disk encryption alone doesn't stop this&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Full-disk encryption protects data at rest, when the machine is off and the key hasn't been loaded into memory yet. Once you unlock the machine, the key has to live in RAM to actually decrypt anything you're working on. A DMA attack targets that window, not the encrypted disk itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Changed Since
&lt;/h2&gt;

&lt;p&gt;Both disclosures pushed real fixes. Windows shipped Kernel DMA Protection, which routes Thunderbolt and USB-C devices through an IOMMU that restricts what memory a peripheral can actually reach, on hardware that supports it. Linux has equivalent IOMMU-based protections available, though historically not always enabled by default depending on distribution and kernel version. Apple's Thunderbolt implementation has included VT-d-based protections for years. The Thunderbolt 4 specification itself made DMA protection a mandatory requirement rather than an optional configuration, closing off the class of machines that shipped with the capability present but switched off.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it protects against&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full-disk encryption&lt;/td&gt;
&lt;td&gt;Data at rest, machine powered off, key not yet in memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock screen&lt;/td&gt;
&lt;td&gt;Casual access through the OS's normal UI, not the hardware port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kernel DMA Protection / IOMMU&lt;/td&gt;
&lt;td&gt;What a connected peripheral can actually reach in memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thunderbolt 4 mandatory DMA protection&lt;/td&gt;
&lt;td&gt;Whether the protection above is even optional on a given machine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Not Every USB-C Port Carries This Risk Equally
&lt;/h2&gt;

&lt;p&gt;Plain USB, the protocol most keyboards, mice, and flash drives speak even when the physical connector is USB-C, never granted DMA by design. The CPU mediates every transfer, which is slower but keeps a peripheral from touching memory it wasn't handed explicitly. The DMA exposure described above is specific to Thunderbolt and to USB4, the newer standard that folds Thunderbolt-style tunneling into the USB-C connector. The confusing part for most people is that Thunderbolt, USB4, and plain USB 3.x all now show up on the same identical-looking port, so there's no way to tell by looking at a laptop which protocol a given USB-C port actually speaks, or whether DMA is in play for a given accessory. Checking your machine's specification sheet for which ports are Thunderbolt or USB4-capable is the only reliable way to know where this risk actually applies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Still Depends on You
&lt;/h2&gt;

&lt;p&gt;Firmware and OS mitigations close the general case. They don't change the fact that this class of attack requires physical access, which is the same category of risk covered in our piece on &lt;a href="https://havenmessenger.com/blog/posts/evil-maid-attacks/" rel="noopener noreferrer"&gt;evil maid attacks&lt;/a&gt;: anyone with a few unsupervised minutes with your hardware has more options than the lock screen suggests. If your threat model includes that kind of physical access, whether from a border crossing, a hotel room, or a shared workspace, the practical steps are the same ones that apply to any physical-access risk: fully power off the machine rather than leaving it asleep, since sleep mode keeps encryption keys resident in memory in a way a full shutdown doesn't; disable unused Thunderbolt or USB-C ports in firmware settings when traveling with data you can't afford to lose; and confirm Kernel DMA Protection or your OS's equivalent is actually enabled rather than assuming it is by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  External GPUs and Docks Aren't the Enemy
&lt;/h2&gt;

&lt;p&gt;None of this is an argument against Thunderbolt itself, which is a genuinely useful standard for anyone running an external GPU, a high-bandwidth dock, or fast external storage. The point of walking through Thunderclap and Thunderspy in detail isn't to make the port sound cursed. It's that the security model for a long time rested on an assumption, that a device physically plugged in deserves broad memory access by default, which turned out not to hold once researchers built a device specifically designed to violate it. The fix wasn't to remove DMA, since removing it would undo the performance benefit the whole standard exists for. The fix was to put a narrower gate, the IOMMU, between the peripheral and memory it has no legitimate reason to touch.&lt;/p&gt;

&lt;p&gt;Related reading: our coverage of &lt;a href="https://havenmessenger.com/blog/posts/cold-boot-attacks-ram-forensics/" rel="noopener noreferrer"&gt;cold boot attacks and RAM forensics&lt;/a&gt; covers a different way memory contents can be extracted after the fact, and our rundown of &lt;a href="https://havenmessenger.com/blog/posts/badusb-attacks-explained/" rel="noopener noreferrer"&gt;BadUSB attacks&lt;/a&gt; covers the older, protocol-level version of "the port trusts the device more than it should."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/thunderbolt-usb-c-dma-attacks/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>encryption</category>
      <category>cryptography</category>
    </item>
    <item>
      <title>Why Your Shared Docs Aren't End-to-End Encrypted</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Sat, 22 Aug 2026 12:18:17 +0000</pubDate>
      <link>https://dev.to/havenmessenger/why-your-shared-docs-arent-end-to-end-encrypted-146f</link>
      <guid>https://dev.to/havenmessenger/why-your-shared-docs-arent-end-to-end-encrypted-146f</guid>
      <description>&lt;p&gt;Google Docs, Notion, Microsoft 365, and Confluence all encrypt your documents at rest and in transit. None of them are end-to-end encrypted, meaning the provider can read the contents on their servers, and this isn't an oversight. Real-time collaborative editing and end-to-end encryption are pulling in opposite directions, and most of the industry has, so far, picked collaboration.&lt;/p&gt;

&lt;p&gt;"Encrypted" on a cloud storage or document platform's marketing page almost always means encrypted at rest, on the provider's disks, and in transit, over TLS while it travels between your browser and their servers. Both are real protections against an attacker who steals a hard drive or intercepts network traffic. Neither stops the provider itself from reading the document, because the provider holds the decryption keys. That's a deliberate architectural choice, not a corner cut, and it exists because of how collaborative editing actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical conflict
&lt;/h2&gt;

&lt;p&gt;Real-time collaborative editing, the kind where you can see a coworker's cursor and their keystrokes appear in your document within milliseconds, relies on the server actively participating in resolving what happens when two people type in the same place at the same time. The dominant approaches are operational transformation and CRDTs (conflict-free replicated data types), and both were designed with a server, or at minimum a shared relay, that processes the actual edit operations to merge them correctly.&lt;/p&gt;

&lt;p&gt;If the server can't read the content, in a naive design it also can't help merge conflicting edits, generate a searchable index, render a preview thumbnail, or run the spell-checker that flags a typo as you type. Every one of those features, the ones that make a cloud document tool feel effortless, depends on server-side access to plaintext in the architectures most of these products were built on.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Contrast with encrypted chat&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;End-to-end encrypted messaging works because a message, once sent, is a discrete unit: encrypt it once, deliver it, done. A live-collaborative document is closer to an ongoing negotiation between multiple editors happening continuously, and encrypting that negotiation without a party who can read it to referee the merges is a much harder problem, one the field has only recently started solving well.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What's actually changing
&lt;/h2&gt;

&lt;p&gt;CRDTs, unlike operational transformation, don't strictly require a trusted server to resolve conflicts, because they're designed so that any two versions of the document can be merged deterministically using only the data structure itself, no arbitration needed. That property is what makes end-to-end encrypted real-time collaboration technically possible: encrypt the CRDT operations client-side, let the server relay opaque ciphertext blobs between collaborators, and each client decrypts and merges locally.&lt;/p&gt;

&lt;p&gt;A handful of newer products, mostly encrypted note-taking and password-manager-adjacent tools, ship real-time sync built this way. It remains uncommon among mainstream office suites, partly because retrofitting a CRDT-and-encryption architecture onto a decade of existing product is a bigger undertaking than most vendors have chosen to make, and partly because features like server-side full-text search, which most business users expect, get meaningfully harder or move client-side entirely when the server can't read the content.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each model actually protects against
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Threat&lt;/th&gt;
&lt;th&gt;At-rest + in-transit encryption&lt;/th&gt;
&lt;th&gt;End-to-end encryption&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Network eavesdropper&lt;/td&gt;
&lt;td&gt;Protected (TLS)&lt;/td&gt;
&lt;td&gt;Protected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stolen server hard drive&lt;/td&gt;
&lt;td&gt;Protected&lt;/td&gt;
&lt;td&gt;Protected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider compelled by legal order to hand over content&lt;/td&gt;
&lt;td&gt;Provider can comply&lt;/td&gt;
&lt;td&gt;Provider has nothing readable to hand over&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Malicious or coerced insider at the provider&lt;/td&gt;
&lt;td&gt;Has plaintext access&lt;/td&gt;
&lt;td&gt;No plaintext access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider breach exposing document contents&lt;/td&gt;
&lt;td&gt;Depends on breach scope&lt;/td&gt;
&lt;td&gt;Ciphertext only, unreadable without client keys&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What this means for choosing a tool
&lt;/h2&gt;

&lt;p&gt;For most day-to-day collaborative work, meeting notes, project plans, drafts that will eventually be shared publicly anyway, the provider's ability to read the document rarely matters in practice. The gap matters for a specific category: documents where the threat model includes the platform itself, legal strategy memos, unreleased financial terms, anything where "the vendor got a subpoena or got breached" is a scenario worth planning for, not just an abstract risk.&lt;/p&gt;

&lt;p&gt;For that category, the practical options today are narrower than for messaging: end-to-end encrypted collaborative editing exists but isn't yet the default anywhere close to universal, and picking a tool built that way from the ground up currently means trading away some of the polish of mainstream suites, full-text search across your whole workspace being the most common casualty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Haven fits
&lt;/h2&gt;

&lt;p&gt;Haven doesn't do collaborative documents, we're encrypted email and chat, but the same architectural tension shows up in our own design decisions: features that would require server-side plaintext access get built without it, or don't get built at all, rather than quietly widening what the provider can read.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/cloud-document-collaboration-encryption-gap/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>encryption</category>
      <category>cryptography</category>
    </item>
    <item>
      <title>Content Credentials: Cryptographic Provenance for Images and Video</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Fri, 21 Aug 2026 12:20:00 +0000</pubDate>
      <link>https://dev.to/havenmessenger/content-credentials-cryptographic-provenance-for-images-and-video-2afk</link>
      <guid>https://dev.to/havenmessenger/content-credentials-cryptographic-provenance-for-images-and-video-2afk</guid>
      <description>&lt;p&gt;The Coalition for Content Provenance and Authenticity (C2PA) publishes a standard for attaching a signed history to an image, video, or audio file: what device or tool produced it, what edits were applied, and in what order. It's a useful, narrow tool. It's also frequently described as a solution to a bigger problem than it actually solves.&lt;/p&gt;

&lt;p&gt;C2PA started as a joint effort between Adobe, Microsoft, the BBC, Intel, and Sony, and now includes most major camera and software vendors in some capacity. The specification defines "Content Credentials": a structured, cryptographically signed manifest that travels with a media file and records its provenance, an assertion about where the file came from and what happened to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Manifest Actually Contains
&lt;/h2&gt;

&lt;p&gt;A Content Credentials manifest is built from a chain of assertions, each one signed and each one referencing the state of the file before that assertion was made. A typical chain for a photograph might include: a capture assertion from the camera, recording the device model and timestamp; an edit assertion from an editing tool, recording that a crop and a color adjustment were applied; and a final assertion from a publisher, recording that the image was prepared for distribution. Each assertion in the chain is signed by whoever made it, using a certificate issued under the C2PA trust model.&lt;/p&gt;

&lt;p&gt;The manifest also stores a cryptographic hash of the underlying pixel data at each stage, which is how tampering gets detected. If someone edits the image after the final assertion without going through a C2PA-aware tool, the hash in the last manifest entry no longer matches the file, and a verifier can tell the chain has been broken, even without knowing what the edit was.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this proves, precisely&lt;/strong&gt;&lt;br&gt;
A valid, unbroken chain proves that a specific set of signers made a specific set of claims about this file's history, and that the file hasn't changed since the last signature. It does not prove any of those claims are accurate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Gap Between "Signed" and "True"
&lt;/h2&gt;

&lt;p&gt;This is the distinction that gets lost in most consumer-facing coverage of the standard. A camera's capture assertion says "this device, running this firmware, captured this data at this time." It does not verify that the scene in front of the camera was real, unstaged, or unmanipulated before the shutter opened. A camera pointed at a monitor playing a synthetic video will faithfully sign a capture assertion for footage that is, at the sensor level, exactly what it claims: light captured by that device at that moment. What it doesn't and can't attest to is what was actually in the scene.&lt;/p&gt;

&lt;p&gt;The chain is only as reliable as its weakest signer, and every signer in the chain is trusted to have told the truth about their own step. A generative AI tool that signs its own output as "AI-generated" is providing useful, verifiable disclosure. A tool, or a person with the right hardware and no obligation to be honest, that omits the AI-generation assertion, or strips the manifest entirely, produces a file with no Content Credentials at all, which looks identical, to a casual viewer, to a file that simply predates the standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripping and the Missing-Metadata Problem
&lt;/h2&gt;

&lt;p&gt;C2PA manifests are metadata, and metadata survives re-encoding, cropping, and platform uploads inconsistently. Most social platforms that support Content Credentials preserve the manifest through their own processing pipeline deliberately, as a feature. Many tools that were never built with C2PA in mind simply drop it, the same way EXIF data has been stripped by image processing pipelines for two decades without any adversarial intent behind the stripping.&lt;/p&gt;

&lt;p&gt;This creates an asymmetry that matters a great deal for how the standard functions in practice: the absence of a Content Credentials manifest tells you almost nothing. It could mean the file predates the standard, passed through a tool that doesn't support it, was deliberately stripped to obscure its origin, or was never real media that had provenance to attest to in the first place. A missing manifest and a manifest-never-attached-because-it's-fully-synthetic-and-undisclosed look exactly the same to a viewer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;th&gt;What it doesn't&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Valid, unbroken C2PA chain&lt;/td&gt;
&lt;td&gt;Which signers made which claims, and that the file matches the last signed state&lt;/td&gt;
&lt;td&gt;Whether the claims themselves are accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broken chain (hash mismatch)&lt;/td&gt;
&lt;td&gt;The file was modified after the last signature, by a tool outside the chain&lt;/td&gt;
&lt;td&gt;What the modification was, or whether it was material&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No manifest present&lt;/td&gt;
&lt;td&gt;Nothing reliable on its own&lt;/td&gt;
&lt;td&gt;Whether the media is authentic, synthetic, or simply predates adoption&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Trust Model Underneath
&lt;/h2&gt;

&lt;p&gt;Signature validity in C2PA rests on a certificate authority structure: signing certificates are issued to camera manufacturers, software vendors, and publishers who apply and meet the coalition's conformance requirements. Verifying a manifest means checking the signature against that certificate chain, the same basic mechanism that underlies TLS on the web. That means the strength of any given Content Credentials claim is bounded by how carefully the coalition vets who gets a signing certificate, and by how well individual signers protect their private keys. A leaked signing key for a major camera manufacturer would let an attacker produce manifests that verify as genuinely captured by that manufacturer's hardware, for content the hardware never touched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Standard Is Genuinely Useful
&lt;/h2&gt;

&lt;p&gt;None of this makes Content Credentials pointless. It's a real, auditable improvement over having no provenance mechanism at all, and it's most useful in exactly the case its design targets: a newsroom or platform that wants to verify a specific claim, "did this photo come from this named photographer's camera, unedited," where the signers in the chain are known, accountable parties with a reputation to protect. A wire-service photo with an intact C2PA chain from a known photojournalist through a known agency is meaningfully more verifiable than the same photo with no chain at all.&lt;/p&gt;

&lt;p&gt;The standard is weakest exactly where the public conversation about deepfakes and misinformation is loudest: unattributed content circulating on social platforms, produced by parties with no certificate, no accountability, and no incentive to attribute the content accurately in the first place. For that category of content, the absence of a manifest is the expected state, not an anomaly, and C2PA has nothing to say about it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/content-credentials-c2pa-explained/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>encryption</category>
      <category>cryptography</category>
    </item>
    <item>
      <title>Verifying Webhooks: The Four Checks Handlers Skip</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Thu, 20 Aug 2026 12:16:51 +0000</pubDate>
      <link>https://dev.to/havenmessenger/verifying-webhooks-the-four-checks-handlers-skip-29gp</link>
      <guid>https://dev.to/havenmessenger/verifying-webhooks-the-four-checks-handlers-skip-29gp</guid>
      <description>&lt;p&gt;A webhook endpoint is an unauthenticated POST route on the public internet that writes to your database, sends email, moves money or provisions accounts. There is no session, no bearer token from your own identity provider and no user to consent. The signature header is the entire authentication story.&lt;/p&gt;

&lt;p&gt;Most implementations verify that header, and most of them still leave at least one of four gaps open. The gaps are consistent enough across codebases that they are worth naming individually.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a signature actually asserts
&lt;/h2&gt;

&lt;p&gt;The common construction is an &lt;a href="https://havenmessenger.com/blog/posts/hmac-message-authentication-explained/" rel="noopener noreferrer"&gt;HMAC&lt;/a&gt; over a canonical string, keyed with a secret that both sides hold, delivered as hex or base64 in a header. The canonical string is usually the request body, sometimes prefixed with a timestamp and a version marker.&lt;/p&gt;

&lt;p&gt;What a valid HMAC proves is that someone holding the secret produced these exact bytes at some point. It does not prove when, it does not prove that this is the first time you have seen them, and it does not prove which of the several parties holding the secret produced them. Each of the checks below closes one of those gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check one: sign the raw bytes
&lt;/h2&gt;

&lt;p&gt;The single most common defect is computing the HMAC over a re-serialised body. A handler receives the request, the framework parses JSON into a dictionary, the verification code serialises that dictionary back to a string, and the HMAC is computed over the result.&lt;/p&gt;

&lt;p&gt;Those bytes are not the bytes that were signed. Key order changes. Unicode escaping differs between libraries. Floating point formatting differs. Insignificant whitespace disappears. Sometimes the round trip happens to produce identical output and the code works in testing, then fails on the first payload containing a non-ASCII character or a number that formats differently.&lt;/p&gt;

&lt;p&gt;The fix is to capture the raw body before any parsing and verify against it. In most frameworks that requires explicit configuration, because the default middleware parses first and discards the original buffer. Do that work before writing the comparison, because a verification that passes on re-serialised bytes is not verifying the same message the sender signed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check two: bound the replay window
&lt;/h2&gt;

&lt;p&gt;A signature with no timestamp is valid forever. Anyone who captures one valid request, from a proxy log, an error tracker, a misconfigured mirror or a compromised staging environment, can replay it at any time and it will verify correctly.&lt;/p&gt;

&lt;p&gt;Providers that handle this include a timestamp in the signed string, typically as a header the receiver reads and folds into the canonical form. The receiver rejects anything outside a tolerance window, commonly a few minutes. Two details matter and are frequently skipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The timestamp must be inside the signature.&lt;/strong&gt; A timestamp header that is not part of the signed string can be edited freely by whoever is replaying the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The window still permits replay inside it.&lt;/strong&gt; Keep a short-lived cache of event identifiers already processed and reject repeats. Five minutes is plenty of time to send the same payment-succeeded event forty times.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Check three: compare in constant time
&lt;/h2&gt;

&lt;p&gt;Comparing the computed digest to the received one with an ordinary string equality operator returns as soon as it finds a differing byte. The time taken therefore depends on how many leading bytes matched, which leaks information about the correct value.&lt;/p&gt;

&lt;p&gt;Remote timing attacks across the internet are difficult and noisy, and this is the check people most often argue about skipping. The argument is not worth having, because the fix is a single function call that already exists in every standard library: &lt;code&gt;hmac.compare_digest&lt;/code&gt; in Python, &lt;code&gt;crypto.timingSafeEqual&lt;/code&gt; in Node, &lt;code&gt;subtle.ConstantTimeCompare&lt;/code&gt; in Go. See &lt;a href="https://havenmessenger.com/blog/posts/constant-time-programming-explained/" rel="noopener noreferrer"&gt;constant-time programming&lt;/a&gt; for why the property is harder to preserve than it looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check four: close the fallback paths
&lt;/h2&gt;

&lt;p&gt;The first three checks are code. This one is process, and it is where verified endpoints quietly become unverified.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accept-if-missing.&lt;/strong&gt; Added during a migration so the old sender keeps working, never removed. An attacker simply omits the header.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permanent dual-secret acceptance.&lt;/strong&gt; Rotation implemented as accept-either, with no expiry on the old secret. The compromised secret stays valid indefinitely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets in the URL.&lt;/strong&gt; A per-endpoint token in the query string appears in proxy logs, browser referrers and error reports, and cannot be rotated without redeploying the sender.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The unverified twin.&lt;/strong&gt; A debug or test endpoint that accepts the same payloads without checking, left routable in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Returning 200 on verification failure.&lt;/strong&gt; Done to stop the provider retrying, it also stops anyone noticing that forged requests are arriving.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;If skipped&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Raw body&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Verification is unreliable and breaks on encoding differences&lt;/td&gt;
&lt;td&gt;Capture the buffer before parsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Signed timestamp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Captured requests replay forever&lt;/td&gt;
&lt;td&gt;Include the timestamp in the signed string, reject outside tolerance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event deduplication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Repeats inside the tolerance window are processed again&lt;/td&gt;
&lt;td&gt;Short-lived cache of processed event identifiers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Constant-time compare&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Byte-by-byte comparison timing leaks the expected digest&lt;/td&gt;
&lt;td&gt;Use the standard library comparison function&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;No fallback path&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The strongest verification is bypassed rather than broken&lt;/td&gt;
&lt;td&gt;Remove accept-if-missing, expire old secrets, delete debug twins&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  After the signature verifies
&lt;/h2&gt;

&lt;p&gt;Verification is the start of the handler, not the end of the security work.&lt;/p&gt;

&lt;p&gt;Providers retry on timeout, so the same valid event will arrive more than once as a matter of normal operation. Handlers need idempotency for correctness regardless of any attack. Do the verification and the deduplication before any expensive work, and acknowledge quickly, because a slow handler produces retries that look like an attack.&lt;/p&gt;

&lt;p&gt;Webhook bodies frequently contain personal data, and the signature header is a credential-adjacent value. Neither belongs in an unredacted request log or an error tracker payload. If your service lets a customer configure the URL a webhook is sent to, that outbound request is a &lt;a href="https://havenmessenger.com/blog/posts/server-side-request-forgery-ssrf-explained/" rel="noopener noreferrer"&gt;server-side request forgery&lt;/a&gt; surface, and the destination needs validating against internal address ranges before the first send.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Symmetric versus asymmetric signing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With HMAC, both parties hold the same secret, so a receiver's leaked secret lets an attacker forge messages that appear to come from the sender. Providers that sign with Ed25519 or ECDSA give receivers a public verification key instead, and a leaked verification key forges nothing. If a provider offers asymmetric signing, take it. It removes an entire class of incident from your side of the boundary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Testing that the verification fires
&lt;/h2&gt;

&lt;p&gt;A test suite that only sends correctly signed payloads proves nothing, because a handler that accepts everything passes it. The tests that carry information are the negative ones: a request with the signature header removed, a request whose body was modified by one byte after signing, a request signed with the previous secret after rotation, a replayed request with a stale timestamp, and a replayed request with a fresh timestamp and a repeated event identifier.&lt;/p&gt;

&lt;p&gt;Each of those should be rejected with a distinct log line. If they are not tested, nothing proves the verification still fires, and security code has a way of quietly stopping during a refactor that nobody flagged.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/webhook-signature-verification/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>encryption</category>
      <category>cryptography</category>
    </item>
    <item>
      <title>What The Update Framework Assumes Will Go Wrong</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Wed, 19 Aug 2026 12:18:14 +0000</pubDate>
      <link>https://dev.to/havenmessenger/what-the-update-framework-assumes-will-go-wrong-2p07</link>
      <guid>https://dev.to/havenmessenger/what-the-update-framework-assumes-will-go-wrong-2p07</guid>
      <description>&lt;p&gt;Most software update systems have a signing key and a repository, and the security argument stops there: the package is signed, the signature verifies, the update is safe. That argument covers exactly one attack, forging a package, and leaves several others untouched.&lt;/p&gt;

&lt;p&gt;The Update Framework, usually written TUF, starts from the opposite end. It enumerates what an attacker who already controls the repository or the network can do to a client that verifies signatures correctly, and then designs around each one. It came out of research at NYU and is now a graduated project at the Cloud Native Computing Foundation, which means the design has had a long time in contact with production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The attacks a signature does not stop
&lt;/h2&gt;

&lt;p&gt;Assume the attacker has compromised the mirror your client talks to, or sits on the network path, but has no signing key. Here is what they can still do to a client that checks signatures on packages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rollback.&lt;/strong&gt; Serve an older, correctly signed package with a known vulnerability. Every signature verifies. The client installs a downgrade.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Freeze.&lt;/strong&gt; Keep serving the current metadata forever. The client never learns that a security update exists, and nothing looks wrong.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mix and match.&lt;/strong&gt; Assemble a set of individually valid packages that were never tested together and whose combination is exploitable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wrong file.&lt;/strong&gt; Serve a valid, signed package under a name the client requested for something else.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Endless data.&lt;/strong&gt; Return an infinite stream to a client that allocates before it checks a declared length.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now assume the attacker does get a key. In a single-key system that is the end of the story. Every future update is theirs, and revocation requires an out-of-band channel that most projects have never exercised.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The framing that drives the design.&lt;/strong&gt; TUF treats key compromise as a scheduled event rather than an emergency. The question is not how to prevent it, it is how much an attacker gets, for how long, from compromising any single key. Every structural decision below follows from that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Four roles, deliberately separated
&lt;/h2&gt;

&lt;p&gt;TUF splits signing authority across four roles, each with its own key set, its own threshold, and its own expiry window.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Signs&lt;/th&gt;
&lt;th&gt;Key handling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Root&lt;/td&gt;
&lt;td&gt;The set of keys trusted for every other role&lt;/td&gt;
&lt;td&gt;Offline, high threshold, long expiry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Targets&lt;/td&gt;
&lt;td&gt;Hashes and lengths of the actual files&lt;/td&gt;
&lt;td&gt;Offline where possible, delegable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshot&lt;/td&gt;
&lt;td&gt;The version numbers of all current metadata&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;td&gt;A short-lived statement that this is current&lt;/td&gt;
&lt;td&gt;Online, frequently rotated, short expiry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Map the attacks onto the roles and the structure explains itself. Timestamp metadata expires in hours or days, so a freeze attack fails on its own: a client that sees expired timestamp metadata knows it is being starved, rather than concluding that no updates exist. Snapshot fixes the set of metadata versions that belong together, which is what defeats mix and match. Targets carries hashes and lengths, so wrong-file and endless-data both fail before the payload is trusted. Version numbers in signed metadata defeat rollback.&lt;/p&gt;

&lt;p&gt;The keys the repository infrastructure needs online are timestamp and snapshot. Those are the ones an attacker who owns the server gets, and they are deliberately the two that cannot authorise a new file. Stealing them buys the ability to say "nothing has changed", which the expiry windows then bound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thresholds and delegation
&lt;/h2&gt;

&lt;p&gt;Each role requires &lt;em&gt;m&lt;/em&gt; of &lt;em&gt;n&lt;/em&gt; signatures rather than one. A threshold of three out of five means compromising one maintainer's laptop is not enough, which changes the economics of targeting individuals in a project.&lt;/p&gt;

&lt;p&gt;Delegation is the part that makes this workable at repository scale. The targets role can delegate a namespace to another key: this key is authoritative for packages matching this pattern, and for nothing else. A community package repository does not need one key that can sign anything for anyone. It needs a top-level role that says who is authoritative for what, and per-project keys that are powerless outside their own path. A compromised project key then compromises that project, not the ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it is deployed
&lt;/h2&gt;

&lt;p&gt;Uptane is the automotive adaptation, standardised through IEEE-ISTO, and it is the most instructive deployment because the constraints are hostile. A vehicle may be offline for months. A compromised update reaches braking and steering firmware. The unit doing the verification is a resource-constrained ECU. Uptane splits the problem across a director repository that decides what a specific vehicle should install and an image repository that vouches for the images themselves, requiring both before an ECU accepts anything.&lt;/p&gt;

&lt;p&gt;In the Python ecosystem, PEP 458 specified TUF for package index metadata. Container tooling used TUF through Notary for content trust. Several language ecosystems have adopted or piloted it since.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it relates to Sigstore, SLSA and reproducible builds
&lt;/h2&gt;

&lt;p&gt;These get discussed as competing options and they are not. They answer different questions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://havenmessenger.com/blog/posts/code-signing-sigstore-explained/" rel="noopener noreferrer"&gt;Sigstore&lt;/a&gt; answers "who signed this artifact, and can I verify that without long-lived key custody?"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://havenmessenger.com/blog/posts/slsa-supply-chain-levels-explained/" rel="noopener noreferrer"&gt;SLSA&lt;/a&gt; answers "what process produced this artifact, and how tamper-resistant was that process?"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://havenmessenger.com/blog/posts/reproducible-builds-explained/" rel="noopener noreferrer"&gt;Reproducible builds&lt;/a&gt; answer "does this binary correspond to that source?"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TUF answers "given all of the above, is the thing my client just fetched the current, complete, correct set for me right now?"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A system can have perfect provenance on every artifact and still hand a client a six-month-old vulnerable version. Delivery integrity is its own problem, and it is the one TUF is for. &lt;a href="https://havenmessenger.com/blog/posts/binary-transparency-explained/" rel="noopener noreferrer"&gt;Binary transparency&lt;/a&gt; sits alongside as the public-log approach to the same trust surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from it if you ship software
&lt;/h2&gt;

&lt;p&gt;Adopting the full framework is a real project, and for many teams it is not the next thing to do. The design ideas transfer without it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Sign the &lt;em&gt;list&lt;/em&gt; of current versions, not just each file. Otherwise you have no defence against an attacker who chooses which of your valid files to serve.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Put an expiry on that list, short enough that a stalled client notices. Freeze attacks are invisible without one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep the key that authorises new content off the machine that serves it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Require more than one signature for anything that changes who is trusted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write down what happens when a key is lost, and then rehearse it. An unexercised recovery path is a plan, not a capability.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last one is where most projects are weakest, and it is free. Whether the recovery path works is a fact about your project that you either know or do not.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/tuf-update-framework-explained/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>What 802.1X Actually Proves, and the Checkbox That Decides It</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Tue, 18 Aug 2026 12:18:25 +0000</pubDate>
      <link>https://dev.to/havenmessenger/what-8021x-actually-proves-and-the-checkbox-that-decides-it-4d89</link>
      <guid>https://dev.to/havenmessenger/what-8021x-actually-proves-and-the-checkbox-that-decides-it-4d89</guid>
      <description>&lt;p&gt;A company laptop joins the office network with the same credentials the user types into everything else, and nobody thinks about it again. The mechanism underneath is 802.1X, and it has a specific asymmetry worth knowing: it is very good at proving who the client is, and it proves who the network is only if the client was configured to check. That configuration is one screen most deployments skip.&lt;/p&gt;

&lt;p&gt;The home Wi-Fi model is a shared secret. Everyone who knows the passphrase derives the same key material, and the access point cannot tell one person from another. Enterprise Wi-Fi replaces that with a per-user authentication exchange, standardised as IEEE 802.1X, and it introduces three parties instead of two.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;supplicant&lt;/strong&gt; is the software on the client device. The &lt;strong&gt;authenticator&lt;/strong&gt; is the access point or switch, which starts out passing nothing but authentication traffic and opens the port once the exchange succeeds. The &lt;strong&gt;authentication server&lt;/strong&gt; is a RADIUS server holding the actual user database. The access point is a relay in this design. It never sees the credential and never decides anything.&lt;/p&gt;

&lt;p&gt;What travels between supplicant and authentication server is the Extensible Authentication Protocol (RFC 3748), which is not an authentication method at all. EAP is a container. The method inside it is negotiated, and the choice of method is where deployments diverge sharply in what they actually guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  The methods, and what each one proves
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Client proves identity by&lt;/th&gt;
&lt;th&gt;Server identity checked?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;EAP-TLS&lt;/strong&gt; (RFC 5216, RFC 9190)&lt;/td&gt;
&lt;td&gt;Client certificate and private key&lt;/td&gt;
&lt;td&gt;Mandatory. Both sides present certificates; it is a mutual TLS handshake.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PEAP-MSCHAPv2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Password, inside a TLS tunnel&lt;/td&gt;
&lt;td&gt;Client setting. The tunnel is only as good as the client's willingness to verify who terminated it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EAP-TTLS/PAP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Password sent in the clear inside a TLS tunnel&lt;/td&gt;
&lt;td&gt;Client setting. Same dependency, worse failure mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;TEAP&lt;/strong&gt; (RFC 7170)&lt;/td&gt;
&lt;td&gt;Chained methods, user and machine together&lt;/td&gt;
&lt;td&gt;Client setting, with better tunnel binding than PEAP.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EAP-SIM / EAP-AKA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Carrier SIM key material&lt;/td&gt;
&lt;td&gt;Handled by the carrier's key hierarchy, not the venue's.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two families, then. Certificate-based methods where the client holds a private key, and tunnelled password methods where the client holds something a person can type. The second family carries the deployment problem, and it is by far the more common of the two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the inner password method matters less than it should
&lt;/h2&gt;

&lt;p&gt;MS-CHAPv2 is a challenge-response protocol from the era of dial-up. In 2012 Moxie Marlinspike and David Hulton demonstrated that its security reduces to a single DES operation. The protocol splits the NT hash into three chunks and encrypts the same challenge under each; the third chunk is padded with five zero bytes, which makes it trivially recoverable, and the remaining work is one exhaustive DES keyspace search. Purpose-built hardware turned that into a bounded, predictable job. The correct summary since then is that a captured MS-CHAPv2 exchange yields the NT hash, and the NT hash is the credential.&lt;/p&gt;

&lt;p&gt;This is survivable, because in PEAP the MS-CHAPv2 exchange happens inside a TLS tunnel. An observer on the air sees ciphertext. The entire security of the arrangement therefore rests on one question: did the client verify that the TLS tunnel terminated at the organisation's RADIUS server, and not at a laptop in the car park?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The two settings that carry the weight&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A correctly configured PEAP client pins two things: the certificate authority that signed the RADIUS server certificate, and the expected server name on that certificate. Pinning only the CA is not enough if the CA is a public one, because anyone can obtain a certificate from it. Both fields, together, are what make the tunnel meaningful.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When those fields are blank, the client accepts any certificate, completes a tunnel with whoever answered, and hands over an MS-CHAPv2 exchange for a domain account. Tools that automate this have existed for over a decade. The attack needs no interaction from the user beyond being in range of a stronger signal with a matching network name, which is the same premise as an ordinary &lt;a href="https://havenmessenger.com/blog/posts/evil-twin-attacks/" rel="noopener noreferrer"&gt;evil twin&lt;/a&gt; but with a much better prize at the end.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the ecosystem responded
&lt;/h3&gt;

&lt;p&gt;Client platforms have been closing the gap by removing the option to be wrong. Android 11 dropped the "do not validate" choice for enterprise networks, requiring either a specified CA certificate with a domain, or explicit use of the system trust store with a domain. WPA3-Enterprise in its 192-bit mode requires server certificate validation and constrains the cipher suites. Configuration profiles, whether pushed by MDM or by a provisioning tool such as the one eduroam publishes, exist mainly so that the CA and server name fields are never left to the person joining the network.&lt;/p&gt;

&lt;p&gt;The practical consequence for anyone specifying a deployment: &lt;strong&gt;PEAP is acceptable only alongside a mechanism that guarantees the client configuration&lt;/strong&gt;. If users join by typing a network name and a password, the certificate check is not happening on some fraction of devices, and that fraction is where the credentials leave.&lt;/p&gt;

&lt;h2&gt;
  
  
  RADIUS itself is older than the threat model
&lt;/h2&gt;

&lt;p&gt;Behind the access point, the authenticator talks to the authentication server over RADIUS (RFC 2865), a protocol from 1997 that runs over UDP and authenticates its packets with an MD5-based construction and a shared secret. The User-Password attribute is obscured with an MD5 stream, and everything else in the packet, including usernames and the accounting record of who connected from where and for how long, is plaintext on the wire.&lt;/p&gt;

&lt;p&gt;In July 2024 a research team published Blast-RADIUS (CVE-2024-3596), which turns MD5 chosen-prefix collisions into a forgery of the Access-Accept response. An attacker positioned between the authenticator and the RADIUS server can convert a rejection into an acceptance without knowing the shared secret. The mitigations are the Message-Authenticator attribute enforced on both ends, and, more durably, moving RADIUS inside TLS with RadSec (RFC 6614).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EAP-TLS traffic is not affected by the response forgery in the same way, because the client has already completed a mutual handshake and derived keys with the server it verified. Protocol choices that establish keys between the endpoints tend to age better than protocol choices that trust an intermediate result.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What certificate-based authentication changes
&lt;/h2&gt;

&lt;p&gt;EAP-TLS removes the password from the exchange entirely. The client holds a private key that never leaves the device, ideally in a TPM or secure element, and the network holds a public certificate. There is nothing to phish, nothing to reuse on another service, and nothing an evil twin can capture and crack offline, because the client will not complete a handshake with a server whose certificate does not chain to the expected authority.&lt;/p&gt;

&lt;p&gt;The cost is that somebody has to run a certificate authority, enrol every device, handle renewal before expiry, and maintain revocation. That is real operational work, and it is the reason PEAP persists. It is also work that pays for itself in adjacent places, since the same enrolment can serve &lt;a href="https://havenmessenger.com/blog/posts/mtls-mutual-tls-explained/" rel="noopener noreferrer"&gt;mutual TLS&lt;/a&gt; for internal services and &lt;a href="https://havenmessenger.com/blog/posts/ssh-certificate-authority-explained/" rel="noopener noreferrer"&gt;SSH certificates&lt;/a&gt; for administrative access.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 802.1X does not do
&lt;/h2&gt;

&lt;p&gt;Network authentication decides who may send packets. It does not encrypt anything above the link layer, and it does not conceal traffic from the network operator, who is now in a stronger position than before, because every session is tied to a named account rather than to a device that shares one key with everyone else. On a corporate network, the accounting records generated by RADIUS are a per-user log of connection times and access point locations, which is an &lt;a href="https://havenmessenger.com/blog/posts/server-log-minimization-privacy/" rel="noopener noreferrer"&gt;access log with a floor plan attached&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a guest or a personal device, the useful posture is the same one that applies to any untrusted network: assume the operator sees traffic metadata, and rely on transport encryption end to end rather than on the link. Enterprise Wi-Fi raises the bar for an outsider on the airwaves. It does not change what the network's own operator can observe, and it is not designed to.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prefer EAP-TLS&lt;/strong&gt; where device enrolment is feasible. It removes the credential class that evil twins exist to collect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If PEAP, distribute configuration&lt;/strong&gt;. Set the CA and the server name by profile, not by instruction. A screenshot in a wiki page is not a control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a private CA for the RADIUS certificate&lt;/strong&gt;, or pin the server name precisely. A public CA plus a blank name field is close to no check at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce Message-Authenticator&lt;/strong&gt; on RADIUS, and move to RadSec where the software supports it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set a retention period on accounting logs&lt;/strong&gt; before the first one is written, not after somebody asks for two years of them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern here recurs across security protocols. A design offers mutual authentication, one direction is mandatory and the other is a client-side setting, and the optional direction is the one that gets skipped at scale. Certificate validation in TLS went through the same history before browsers made failure loud. Enterprise Wi-Fi is a few years behind on the same curve, and the platforms removing the "do not validate" checkbox are what moves it forward.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/eap-tls-enterprise-wifi-explained/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>networking</category>
      <category>cryptography</category>
    </item>
    <item>
      <title>Your Access Log Is a Location Database</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Mon, 17 Aug 2026 12:22:54 +0000</pubDate>
      <link>https://dev.to/havenmessenger/your-access-log-is-a-location-database-eo7</link>
      <guid>https://dev.to/havenmessenger/your-access-log-is-a-location-database-eo7</guid>
      <description>&lt;p&gt;Nobody decides to build a movement-tracking dataset. You install a web server, it writes its default log format, and eighteen months later you are holding a per-second record of which network every user connected from, what they asked for, and when. It survives in three places you set up on purpose and two you forgot about.&lt;/p&gt;

&lt;p&gt;This is a practical look at what a default stack records, why the usual anonymisation trick does not work, and how to keep enough to run the service.&lt;/p&gt;

&lt;p&gt;Start with the line itself. The combined log format that ships as the default in common web servers produces something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;198.51.100.42 - - [17/Aug/2026:09:14:22 +0000] "GET /reset?token=8f3a...c1 HTTP/2.0"
200 4213 "https://example.org/settings" "Mozilla/5.0 (iPhone; CPU iPhone &lt;span class="ss"&gt;OS&lt;/span&gt; 18_2 ...)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every field is there for a reason and every field carries something beyond that reason.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it also gives you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client address&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Approximate location, employer or ISP, and a join key that links otherwise separate sessions into one person's activity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Timestamp to the second&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Waking hours, time zone, working pattern. Two users on the same network are separable by rhythm alone.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Request line&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The full query string. Password reset tokens, invitation codes, email addresses passed as parameters and search terms all land here in plaintext.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Referer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The previous page, including any identifiers another site put in its own URLs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OS and browser version, which is a coarse device fingerprint and a precise inventory of who is running unpatched software.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The query string row is the one that produces incidents. A password reset link in an access log is a live credential sitting in a file that ships to a log aggregator, gets indexed, gets backed up, and is readable by everyone with production support access. Several large providers have published exactly this as a postmortem, usually as credentials logged in plaintext by a component nobody thought of as a logger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hashing an IP address does not anonymise it
&lt;/h2&gt;

&lt;p&gt;The standard reflex is to hash the client address before writing it, so that sessions can still be correlated without storing the address itself. This is worth understanding precisely, because it is nearly universal and it does not do what it appears to do.&lt;/p&gt;

&lt;p&gt;IPv4 has 2^32 addresses, about 4.3 billion. A single modern GPU computes SHA-256 at a rate on the order of ten billion hashes per second. Enumerating the entire IPv4 space and building a complete reverse table is therefore under a second of work, and the table is small enough to keep. An unsalted hash of an IP address is a reversible encoding with extra steps.&lt;/p&gt;

&lt;p&gt;Adding a fixed application-wide salt does not fix it either. It stops a precomputed table built before your salt was known, and it costs the attacker one more pass over the same 4.3 billion values once they have the salt, which they will have if they got the logs from your infrastructure.&lt;/p&gt;

&lt;p&gt;What actually changes the result is a &lt;strong&gt;rotating secret salt that is discarded&lt;/strong&gt;: derive the daily key, keep it in memory, destroy it at rollover. Yesterday's identifiers can no longer be linked to today's, and nobody can reverse either set without the key, which no longer exists. Several large providers use this pattern and it is a good default.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Truncation is blunter and often better.&lt;/strong&gt; Zeroing the final octet of an IPv4 address, or keeping only the first 48 bits of an IPv6 address, destroys the identifier rather than obscuring it. You lose per-host resolution and keep network-level resolution, which is what most abuse and capacity work actually needs. Unlike hashing, the loss is real and there is no key that undoes it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The legal position is settled enough to plan around. The Court of Justice of the European Union held in &lt;em&gt;Breyer&lt;/em&gt; that a dynamic IP address can be personal data in the hands of an online service where means reasonably likely to be used exist to identify the person behind it. Pseudonymised data remains personal data under the GDPR by definition. A hashed address is pseudonymised, not anonymous, and the compliance argument that treats it as anonymous is the same argument the mathematics above refutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The copies you did not plan
&lt;/h2&gt;

&lt;p&gt;Retention policy is usually written for the file on the web server. The data is rarely only there.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The log shipper's destination.&lt;/strong&gt; Whatever retention the search backend enforces is the real retention, and it is often longer because storage is cheap and the default is generous.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The error tracker.&lt;/strong&gt; Exception reporters attach request context by default, which can include headers, cookies and body fragments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The CDN and load balancer.&lt;/strong&gt; They log independently, upstream of anything your application does, under a different vendor's policy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database slow-query logs.&lt;/strong&gt; Query text plus bound parameters, which is to say user data, written to a file with its own lifecycle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups of all of the above.&lt;/strong&gt; A 30-day log retention with 12-month backup retention is a 12-month log retention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before designing anything, the useful exercise is to grep your own production logs for the things you believe are never logged. Email addresses, bearer tokens, the string &lt;code&gt;password&lt;/code&gt;, the reset path. The result is usually informative.&lt;/p&gt;

&lt;h2&gt;
  
  
  A design that still lets you operate
&lt;/h2&gt;

&lt;p&gt;Minimisation fails when it is framed as logging less. Framed as deciding which question each field answers, it converges quickly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Split audit from debug.&lt;/strong&gt; Security-relevant events (authentication, permission changes, administrative actions) need long retention, tight access and integrity. Operational debug logs need hours to days. Merging them forces the shorter requirement to inherit the longer retention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redact at the source.&lt;/strong&gt; A redaction step in the query layer protects nobody, because the raw value was already written to disk and shipped. The transform belongs in the logging call, before serialisation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allowlist the fields.&lt;/strong&gt; Structured logging with an explicit set of permitted keys fails safe. A denylist of forbidden keys fails the moment someone adds a field, which is the normal case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strip query strings from access logs.&lt;/strong&gt; Log the path, not the parameters. If a specific parameter is genuinely needed for operations, name it explicitly and add it back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample the boring paths and keep all the errors.&lt;/strong&gt; One percent of successful requests answers capacity questions accurately. Full retention of non-2xx responses answers the debugging questions. This cuts volume by orders of magnitude, which happens to cut cost as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate at write time where you can.&lt;/strong&gt; If the requirement is requests per endpoint per minute, increment a counter. A counter cannot be subpoenaed for an individual's history because that history was never in it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce retention in the storage layer.&lt;/strong&gt; Object lifecycle rules and index-level TTLs delete on their own. A cron job that deletes old files stops silently, and nothing alerts on data that failed to disappear.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What minimisation is worth
&lt;/h2&gt;

&lt;p&gt;The compliance case is direct: the GDPR requires data to be adequate, relevant and limited to what is necessary, and kept no longer than necessary. Logs are usually the least examined part of an estate against those two requirements.&lt;/p&gt;

&lt;p&gt;The operational case is stronger. A field you never wrote cannot appear in a breach, which shrinks what a notification has to cover. It cannot be handed over in response to a subpoena or an emergency data request, because there is nothing in the table. It cannot be misused by an employee with production access. Every other control depends on a process continuing to work correctly. This one depends on a column not existing.&lt;/p&gt;

&lt;p&gt;That is the same reasoning behind end-to-end encryption, applied one layer down. Encrypting message contents means a provider cannot read them. Minimising logs means the provider does not hold the record of who talked to whom and from where, which for a great many adversaries is the more useful half anyway. Both are answers to the same question, which is what your infrastructure would be able to disclose on the worst day it has.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/server-log-minimization-privacy/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Commit You Reverted Is Still There</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Sun, 16 Aug 2026 12:23:17 +0000</pubDate>
      <link>https://dev.to/havenmessenger/the-commit-you-reverted-is-still-there-1ac8</link>
      <guid>https://dev.to/havenmessenger/the-commit-you-reverted-is-still-there-1ac8</guid>
      <description>&lt;p&gt;A credential goes into a config file during debugging. Six minutes later someone notices, commits a fix, and the diff on the pull request is clean. The credential is still in the repository, still served over HTTPS to anyone who can construct the right URL, and on a public repository it was very likely collected by an automated scanner before the fix was written. The reason has nothing to do with carelessness and everything to do with how Git stores things.&lt;/p&gt;

&lt;p&gt;Git does not store diffs. It stores whole objects, addressed by the hash of their content. When you commit a file, Git writes a &lt;strong&gt;blob&lt;/strong&gt; holding the complete file contents, a &lt;strong&gt;tree&lt;/strong&gt; mapping filenames to blobs, and a &lt;strong&gt;commit&lt;/strong&gt; pointing at a tree and at its parent commits. The diff you see in a review interface is computed on demand by comparing two trees. It is a view, not a storage format.&lt;/p&gt;

&lt;p&gt;This is why every intuition borrowed from editing a document fails here. Removing a line in a later commit creates a new blob without that line. It does not touch the old blob, which remains stored, intact, and reachable from the commit that introduced it.&lt;/p&gt;

&lt;p&gt;You can watch it happen in any repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git log -p -- path/to/config&lt;/code&gt; prints every version of that file that was ever committed, including the ones later deleted.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git rev-list --objects --all&lt;/code&gt; enumerates every object reachable from any reference.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git cat-file -p &amp;lt;sha&amp;gt;&lt;/code&gt; prints any of them by hash.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The three moves that feel like removal and are not
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you did&lt;/th&gt;
&lt;th&gt;What happened to the secret&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;git revert&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A new commit was added that applies the inverse change. The original commit and its blob are untouched and still in the branch's history.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;git commit --amend&lt;/strong&gt; then force push&lt;/td&gt;
&lt;td&gt;Your branch reference now points somewhere else. The original commit object still exists on the server as an unreachable object, and on the major hosts it stays fetchable by its hash.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Deleting the file&lt;/strong&gt; in a later commit&lt;/td&gt;
&lt;td&gt;Nothing at all. The file is absent from the current tree and present in every earlier one.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second row is the one that surprises experienced developers. Locally, an unreachable object is eventually removed by garbage collection. On a hosted service it usually is not, at least not on any timetable you control. GitHub documents this directly: after a force push, commits remain accessible by their hash, and removing them requires contacting support rather than pushing again.&lt;/p&gt;

&lt;p&gt;There is a further wrinkle in fork networks. Repositories in a fork relationship share an object store, so an object pushed to one of them can be requested from another by hash. Security research published in 2024 demonstrated that this makes commits from deleted forks and from closed pull requests retrievable from the upstream repository. Deleting your fork does not delete what you pushed to it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The operative fact.&lt;/strong&gt; A hash is not a secret. It appears in pull request timelines, in CI logs, in webhook payloads and in the events firehose that hosts publish publicly. Anyone holding it can request the object directly, and no branch needs to point at it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How fast the collection happens
&lt;/h2&gt;

&lt;p&gt;Public repository activity is published as a continuous event stream, and scraping it for freshly pushed credentials is a mature, automated business. The realistic assumption for a public push is that any credential in it was extracted, tested against the relevant API, and filed within minutes.&lt;/p&gt;

&lt;p&gt;There is a defensive version of the same speed. Major hosts run secret scanning on pushes and participate in partner programmes with credential issuers: a pattern matching a cloud provider key or a payment processor token triggers a notification to that provider, which in some cases revokes or quarantines the credential automatically. If you have ever pushed an access key and received a revocation email before you had finished typing the fix, that is what happened.&lt;/p&gt;

&lt;p&gt;Treat that as a backstop rather than a control. It covers well known credential formats from participating vendors. It does not cover your own service's session tokens, a database connection string, an SSH private key, or the passphrase in a comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The response order that matters
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rotate the credential.&lt;/strong&gt; First, before any history work, before writing the incident note. Everything else on this list is cleanup; this is the only step that changes what an attacker can do with what they already have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check what the credential did.&lt;/strong&gt; Pull the access logs for the window between the push and the rotation. This is the step most often skipped, and it is the one that distinguishes an exposure from an intrusion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then consider rewriting history.&lt;/strong&gt; &lt;code&gt;git filter-repo&lt;/code&gt; is the current tool for this, and BFG Repo-Cleaner remains a reasonable choice for the simple case. Both rewrite every affected commit, which changes every downstream hash, which means every collaborator has to re-clone or reset. Coordinate it, or someone pushes the old history back a day later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask the host to expire unreachable objects.&lt;/strong&gt; On a hosted service this is a support request. Without it, the rewrite changed what the branch points at and not what the server will serve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write it down.&lt;/strong&gt; A short note recording which credential, which window, what the logs showed and what changed afterwards is what turns one incident into one fewer future incident.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps three and four are worth doing and they are not a substitute for step one. Every clone taken before the rewrite still contains the original object, and you have no inventory of those clones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevention, in order of how much it actually buys
&lt;/h2&gt;

&lt;p&gt;These are not equivalent, and the ordering matters more than the list.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Have no long-lived credential to leak.&lt;/strong&gt; Workload identity federation, where CI exchanges a short-lived signed assertion for a token valid for minutes, removes the static secret from the repository and from the CI configuration at the same time. It is more setup than a stored key, and afterwards there is no long-lived credential in the tree for anyone to find.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inject at runtime from a secret manager.&lt;/strong&gt; The application reads from the environment or from an authenticated fetch. Nothing secret is in the tree, so nothing secret can be committed from it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable server-side push protection.&lt;/strong&gt; A hook on the receiving end blocks the push before the object reaches the shared store. This is the control that works, because it does not depend on the developer's local configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a pre-commit scanner as well.&lt;/strong&gt; Tools such as gitleaks and trufflehog catch most of it before it leaves the machine, with the caveat that &lt;code&gt;--no-verify&lt;/code&gt; exists and gets used under deadline pressure. Keep the local hook for the convenience of catching things early, and do not count it as the control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer scanners that verify.&lt;/strong&gt; Entropy heuristics generate noise on base64 test fixtures and miss structured low-entropy secrets. A scanner that attempts a live authentication with a candidate credential produces findings you can triage in order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One item deserves separating out because it is so commonly misunderstood. &lt;strong&gt;Adding a path to &lt;code&gt;.gitignore&lt;/code&gt; does nothing to a file Git is already tracking.&lt;/strong&gt; The ignore list governs untracked files. If the file was committed once, it stays tracked until &lt;code&gt;git rm --cached&lt;/code&gt; removes it from the index, and the earlier blobs remain in history regardless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Private repositories are not a control
&lt;/h2&gt;

&lt;p&gt;The reasoning that a private repository makes committed secrets acceptable fails at several joints at once. Private repositories get forked internally, cloned to contractor laptops, backed up to third-party services, mirrored for CI, opened to a wider team on reorganisation, and occasionally made public by accident. Repositories also get open-sourced years later by people who did not write the history and do not know what is in it.&lt;/p&gt;

&lt;p&gt;CI logs are the parallel leak path and they follow the same rule: a build script running under &lt;code&gt;set -x&lt;/code&gt; echoes its arguments, and a shared build log retains them long after the pipeline has finished. Masking in the CI system helps only for values it knows are secret.&lt;/p&gt;

&lt;p&gt;The formulation to work from: &lt;strong&gt;anything committed to a repository should be assumed to be permanent and eventually public.&lt;/strong&gt; A content-addressed store keeps every object it has ever been given, and a working repository accumulates clones faster than anyone tracks them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/leaked-secrets-in-git-history/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>git</category>
      <category>devops</category>
    </item>
    <item>
      <title>Length Extension: Why SHA-256(secret + message) Is Not a MAC</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Sat, 15 Aug 2026 12:22:23 +0000</pubDate>
      <link>https://dev.to/havenmessenger/length-extension-why-sha-256secret-message-is-not-a-mac-2le0</link>
      <guid>https://dev.to/havenmessenger/length-extension-why-sha-256secret-message-is-not-a-mac-2le0</guid>
      <description>&lt;p&gt;In 2009 two researchers showed they could add parameters to a signed Flickr API request and produce a valid signature for the modified version, without ever recovering the shared secret. Nothing was wrong with MD5's collision resistance that day. The problem was in the construction underneath it, and SHA-256 is built the same way.&lt;/p&gt;

&lt;p&gt;A developer who needs to prove a request came from someone holding a shared key reaches for the tool that is already in the standard library. Concatenate the key and the message, hash the result, send the digest along. The server repeats the computation and compares. It looks like a signature, it is one line of code, and against MD5, SHA-1, SHA-256 and SHA-512 it is forgeable by anyone who has seen a single valid pair.&lt;/p&gt;

&lt;p&gt;The reason has nothing to do with how hard it is to find two inputs with the same digest. It is a consequence of what the digest of these functions actually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The digest is a snapshot the attacker can resume
&lt;/h2&gt;

&lt;p&gt;MD5, SHA-1 and the SHA-2 family are built on the Merkle-Damgard construction. The input is padded to a multiple of the block size, 512 bits for SHA-256. An internal state, eight 32-bit words for SHA-256, starts at a fixed initialisation vector. A compression function mixes each block into that state in turn. When the blocks run out, the function returns the state.&lt;/p&gt;

&lt;p&gt;That last step is where the trouble lives. There is no finalisation, no separate squeeze, no discarded capacity. The 256 bits you receive are the complete working state of the algorithm at the moment it stopped. Hand those bits back to the compression function and it will happily carry on from exactly where it left off, on data it has never seen.&lt;/p&gt;

&lt;p&gt;The padding is public and deterministic. For SHA-256 it is a single 1 bit, written as the byte &lt;code&gt;0x80&lt;/code&gt;, then as many zero bytes as needed, then the length of the message in bits as a 64-bit big-endian integer. An attacker who knows how long the secret is can write out that padding themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the attacker gets to do
&lt;/h2&gt;

&lt;p&gt;Suppose a server computes &lt;strong&gt;tag = SHA-256(secret || message)&lt;/strong&gt; and publishes both &lt;code&gt;message&lt;/code&gt; and &lt;code&gt;tag&lt;/code&gt;. An attacker who wants the tag for a longer message proceeds like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Guess the byte length of the secret. Most are short, so a few dozen guesses covers the realistic range.&lt;/li&gt;
&lt;li&gt;Construct the glue padding that SHA-256 would have appended to &lt;code&gt;secret || message&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Load &lt;code&gt;tag&lt;/code&gt; into a fresh SHA-256 context as its starting state, and set the length counter to the number of bits already consumed.&lt;/li&gt;
&lt;li&gt;Hash the desired extension bytes and read out the new digest.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result is a valid tag for &lt;code&gt;secret || message || glue padding || extension&lt;/code&gt;. The attacker never learns the secret and never needs to. They have forged a tag for a message they chose the tail of.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why the junk bytes rarely help you.&lt;/strong&gt; The forged message carries the glue padding in the middle of it, which looks like an obvious tell. In practice it usually is not. Query-string parsers that take the last occurrence of a repeated key, formats that ignore trailing bytes, and length-prefixed records that skip to an offset all absorb the padding without complaint. The forgery only fails if the parser is stricter than the MAC.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;None of this requires research-grade tooling. Ron Bowes published &lt;em&gt;hash_extender&lt;/em&gt; over a decade ago, which takes the original message, the known tag, the extension and a guessed secret length, and prints the forged pair for every common Merkle-Damgard hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flickr, 2009
&lt;/h2&gt;

&lt;p&gt;The best-documented production case is the Flickr API signature forgery published by Thai Duong and Juliano Rizzo in September 2009. Flickr signed API calls by sorting the request parameters, concatenating them onto a shared secret, and taking the MD5 of the result. Every ingredient of the attack was present: a secret at the front, attacker-visible message content, and a published digest.&lt;/p&gt;

&lt;p&gt;The write-up made a broader point that has aged well. The developers had not chosen MD5 carelessly; the same call would have been just as forgeable with SHA-256. Upgrading the hash function does nothing here, because the property being abused is one that the stronger hash also has.&lt;/p&gt;

&lt;p&gt;This is the same category of mistake as the &lt;a href="https://havenmessenger.com/blog/posts/cryptographic-doom-principle-explained/" rel="noopener noreferrer"&gt;cryptographic doom principle&lt;/a&gt;: a scheme assembled from sound primitives, composed in an order that voids their guarantees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which hash functions are affected
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Extendable?&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MD5, SHA-1&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Merkle-Damgard, full state published. Both are also broken for collisions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256, SHA-512&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Collision-resistant and still fully extendable. Strength does not remove the property.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-224&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;Truncates a 256-bit state to 224 bits, so 32 bits of state are missing. That is a small gap, not a defence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-384, SHA-512/256&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;A large part of the internal state is discarded before output, so it cannot be reconstructed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-3 / Keccak&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;A &lt;a href="https://havenmessenger.com/blog/posts/sha3-keccak-sponge-explained/" rel="noopener noreferrer"&gt;sponge construction&lt;/a&gt;; the capacity portion of the state is never emitted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLAKE2, &lt;a href="https://havenmessenger.com/blog/posts/blake3-hash-function/" rel="noopener noreferrer"&gt;BLAKE3&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;The final block is processed with a finalisation flag set, so a resumed state produces different output.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The distinction that matters is whether the function throws anything away before it hands you a digest. SHA-3's designers chose a sponge partly for this reason: the capacity bits absorb the entropy an attacker would need to continue, and are never revealed. That is why prefixing a key to a SHA-3 hash is actually safe, and why NIST specified KMAC in SP 800-185 as a direct keyed mode rather than wrapping SHA-3 in something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  HMAC is shaped by this exact problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://havenmessenger.com/blog/posts/hmac-message-authentication-explained/" rel="noopener noreferrer"&gt;HMAC&lt;/a&gt;, specified in RFC 2104 in 1997, looks strange at first reading. It hashes twice, with two derived keys:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;HMAC(K, m) = H( (K XOR opad) || H( (K XOR ipad) || m ) )&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The inner hash is extendable, and it does not matter. Its output feeds a second hash whose input is a fixed length: one key block plus one digest. An attacker who appends bytes to &lt;code&gt;m&lt;/code&gt; changes the inner digest to a value they cannot predict without the key, and the outer hash they would need to recompute is keyed too. There is nothing to resume.&lt;/p&gt;

&lt;p&gt;The construction dates from the same period as the attack it defends against, which is worth remembering when reading it. HMAC is not overengineered. Every part of it is answering something specific.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to use instead
&lt;/h2&gt;

&lt;p&gt;For authenticating a message with a shared symmetric key, in rough order of how often each fits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HMAC-SHA-256.&lt;/strong&gt; Available everywhere, well understood, and correct with any Merkle-Damgard hash underneath it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An AEAD instead of a bare MAC.&lt;/strong&gt; If the payload should also be confidential, &lt;a href="https://havenmessenger.com/blog/posts/authenticated-encryption-aead-explained/" rel="noopener noreferrer"&gt;AES-GCM or ChaCha20-Poly1305&lt;/a&gt; authenticate and encrypt in one construction, and neither leaves the composition order to the caller.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KMAC&lt;/strong&gt; when the codebase is already on SHA-3, or &lt;strong&gt;BLAKE3 in keyed mode&lt;/strong&gt; when throughput matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A signature scheme&lt;/strong&gt; such as Ed25519 when the verifier should not be able to produce tags of its own. A shared MAC key means every verifier is also a forger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two operational details travel with the choice. Compare tags in constant time, since a byte-by-byte comparison that returns early leaks the correct value one position at a time to anyone who can measure it; see &lt;a href="https://havenmessenger.com/blog/posts/constant-time-programming-explained/" rel="noopener noreferrer"&gt;constant-time programming&lt;/a&gt; for why the obvious implementation is the wrong one. And include everything that matters inside the authenticated bytes, with unambiguous separators. A MAC over concatenated fields with no delimiter authenticates the concatenation, not the fields, and two different parameter sets that concatenate to the same string will share a tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general lesson
&lt;/h2&gt;

&lt;p&gt;Length extension is a useful case to keep in mind because it breaks an intuition rather than an algorithm. The intuition is that a cryptographic hash behaves like a sealed one-way box, so anything built out of one inherits its strength. Merkle-Damgard hashes are one-way with respect to their input and completely transparent with respect to their state, and a homemade MAC sits precisely on the second property.&lt;/p&gt;

&lt;p&gt;The practical rule is narrow enough to remember. If a design calls for proving that a message came from a key holder, use a primitive whose name contains the word MAC. Hash functions authenticate nothing on their own, whatever you concatenate to the front of them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/length-extension-attacks-explained/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>encryption</category>
      <category>cryptography</category>
    </item>
    <item>
      <title>One Ciphertext, Two Valid Plaintexts: Why AEAD Needs Key Commitment</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Fri, 14 Aug 2026 12:21:14 +0000</pubDate>
      <link>https://dev.to/havenmessenger/one-ciphertext-two-valid-plaintexts-why-aead-needs-key-commitment-l8k</link>
      <guid>https://dev.to/havenmessenger/one-ciphertext-two-valid-plaintexts-why-aead-needs-key-commitment-l8k</guid>
      <description>&lt;p&gt;Modern encryption is almost always AEAD: authenticated encryption with associated data. AES-GCM and ChaCha20-Poly1305 are the two you meet everywhere, in TLS, in disk encryption, in message formats, in cloud key management. They give you confidentiality plus an authentication tag, and decryption either returns the plaintext or returns an error.&lt;/p&gt;

&lt;p&gt;The security definition behind that tag is about forgery. An attacker who does not know the key cannot produce a ciphertext that verifies. That definition holds. What it says nothing about is the situation where the attacker &lt;em&gt;does&lt;/em&gt; know one or more keys and gets to choose the ciphertext.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a key multi-collision looks like
&lt;/h2&gt;

&lt;p&gt;Take AES-GCM. Its authentication tag is computed with GHASH, a polynomial evaluation over a binary field, and the relationship between the ciphertext blocks and the tag is linear in that field. Linearity is convenient for speed, and it is also solvable.&lt;/p&gt;

&lt;p&gt;Given two keys the attacker controls, K1 and K2, that linearity lets them set up a system of equations and solve for a ciphertext whose tag verifies under both. Decrypting it with K1 yields one plaintext. Decrypting the same bytes with K2 yields a completely different plaintext. Neither decryption throws an error, because from each key's point of view the tag is correct.&lt;/p&gt;

&lt;p&gt;Both plaintexts can be attacker-chosen and meaningful. The 2019 paper that named this attack demonstrated a file that was a valid image either way, which is where the memorable label came from: the two decryptions showed different pictures, and the second one had a salamander in it that the reporting system never saw.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The property that was missing.&lt;/strong&gt; An AEAD is &lt;strong&gt;key committing&lt;/strong&gt; if a ciphertext can verify under at most one key. AES-GCM, AES-GCM-SIV, and ChaCha20-Poly1305 are not key committing, and were never claimed to be. The property simply was not part of the design goal, and for a long time no widely deployed system depended on it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The system it broke: message franking
&lt;/h2&gt;

&lt;p&gt;Here is the problem that made this matter in production. In an end-to-end encrypted messenger, the server cannot read messages. So when a user reports an abusive message, how does the platform verify that the reported text was actually sent, rather than typed up by the reporter?&lt;/p&gt;

&lt;p&gt;The answer is &lt;em&gt;message franking&lt;/em&gt;. The sender commits to the plaintext, the server blindly signs that commitment on the way through without learning the content, and the recipient can later open the commitment to prove to the platform what was really sent. Facebook Messenger deployed a scheme of this shape.&lt;/p&gt;

&lt;p&gt;Attachments were handled slightly differently from text: the file was encrypted with AES-GCM under an attachment key, and the franking commitment covered the encrypted blob rather than the decrypted file. A sender who could produce a blob that decrypted two ways could therefore send an abusive image whose reported form was innocuous. The report would arrive, the platform would verify it correctly, and the verification would show the wrong picture.&lt;/p&gt;

&lt;p&gt;The attack was reported and fixed. What survived it is the general lesson: a protocol that treats "this ciphertext decrypted successfully" as evidence about which key or which sender was involved is relying on a property the cipher does not have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partitioning oracles: turning it into a password attack
&lt;/h2&gt;

&lt;p&gt;A 2021 follow-up made the consequence sharper. If a key is derived from a password, an attacker who can send ciphertexts to a server and observe whether decryption succeeded normally learns one bit per guess: this password, yes or no.&lt;/p&gt;

&lt;p&gt;With a non-committing AEAD, they can do much better. Build a ciphertext that verifies under a thousand candidate keys at once, send it, and a success tells you the true password is somewhere in that set of a thousand. A failure eliminates all thousand. That converts a linear search into a binary search over the password space, and the researchers demonstrated it against a widely used proxy protocol that authenticated clients with a shared password, along with password-based encryption in other tools.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The cipher did exactly what it was specified to do in every one of these cases. The failure sat in the assumption each protocol layered on top: that a successful decryption identifies a key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where else the assumption shows up
&lt;/h2&gt;

&lt;p&gt;Once you know the shape, it appears in more places than the messaging case.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;The assumption being made&lt;/th&gt;
&lt;th&gt;Committing AEAD needed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TLS record layer&lt;/td&gt;
&lt;td&gt;None. Both endpoints agreed on one key through the handshake.&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-recipient encryption&lt;/td&gt;
&lt;td&gt;Every recipient who decrypts sees the same message.&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Abuse reporting and franking&lt;/td&gt;
&lt;td&gt;What the reporter reveals is what the sender sent.&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password-based encryption over a network&lt;/td&gt;
&lt;td&gt;A decryption failure rules out exactly one candidate password.&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Envelope encryption with key rotation&lt;/td&gt;
&lt;td&gt;A blob decrypting under a data key identifies which key wrapped it.&lt;/td&gt;
&lt;td&gt;Depends on the format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deduplicated cloud storage&lt;/td&gt;
&lt;td&gt;Ciphertext identity implies plaintext identity.&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The multi-recipient row is the group-chat case, which is where this matters most for messaging. If a single ciphertext goes to many members and each unwraps a different key, non-committing AEAD means a malicious sender can show different members different content while everyone believes they are in one conversation. Group protocols such as MLS derive per-epoch keys shared across the group rather than per-recipient blobs, which changes the exposure, but any format where recipients hold different keys over the same bytes needs to think about this.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the fix works
&lt;/h2&gt;

&lt;p&gt;Three approaches are in circulation, in rough order of how disruptive they are to existing code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The padding fix.&lt;/strong&gt; Prepend a fixed block of zero bytes to the plaintext before encryption, and on decryption reject the message unless that block comes back as zeros. Because the attacker must now satisfy a fixed known prefix under both keys as well as the tag, the multi-collision problem becomes hard. The 2022 analysis that formalised this approach is what most implementations now cite. It costs one extra block and no new primitives, which is why it won.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explicit commitment alongside the ciphertext.&lt;/strong&gt; Derive a commitment value from the key with a hash or a KDF, ship it next to the ciphertext, and check it before decrypting. This is clean, adds bytes on the wire, and is easy to reason about because the commitment is a hash and hashes are collision resistant. Constructions in this family generally derive both a commitment and the encryption subkey from the original key using HKDF.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encrypt then MAC over the key.&lt;/strong&gt; Older and heavier, but a MAC computed over the key itself commits to it directly. Some protocols already do something equivalent without having framed it this way.&lt;/p&gt;

&lt;p&gt;Cloud key management SDKs and file-encryption formats have been adding key commitment since 2020, and the property is now documented in Crypto Forum Research Group drafts on AEAD properties, so a spec author can point at a definition instead of inventing one. If you maintain a message or file format that encrypts to multiple keys, this is a question worth answering explicitly in the spec rather than inheriting from whichever library was convenient.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this says about reading security proofs
&lt;/h2&gt;

&lt;p&gt;Every result here was reachable from the published definitions. AES-GCM was proven secure against the goals it was designed for, and it met them. The gap opened where protocol designers read "authenticated" as a broader word than the definition supports, and then built systems whose safety rested on the broader reading.&lt;/p&gt;

&lt;p&gt;That pattern recurs. Nonce reuse in GCM is a similar case, where a documented precondition gets violated by deployment reality and the consequences are severe.&lt;/p&gt;

&lt;p&gt;The practical habit that comes out of it is narrow and useful. When a design says "if this decrypts, then X," write down what X is and check whether the cipher's own documentation claims it. Confidentiality, integrity against forgery, and identification of the key are three separate properties, and only the first two come standard.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/key-committing-encryption-explained/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>encryption</category>
      <category>cryptography</category>
    </item>
    <item>
      <title>Business Email Compromise: The Fraud That Doesn't Break Any Encryption</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Wed, 12 Aug 2026 12:18:18 +0000</pubDate>
      <link>https://dev.to/havenmessenger/business-email-compromise-the-fraud-that-doesnt-break-any-encryption-4h56</link>
      <guid>https://dev.to/havenmessenger/business-email-compromise-the-fraud-that-doesnt-break-any-encryption-4h56</guid>
      <description>&lt;p&gt;Business email compromise, usually shortened to BEC, has been the largest reported category of cybercrime loss tracked by the FBI's Internet Crime Complaint Center for years running, ahead of ransomware and ahead of credential theft. It's also one of the least technically sophisticated attacks in active use. There's often no malware, no zero-day, no cracked cipher. The entire attack lives in the gap between "this email looks legitimate" and "this email is legitimate," and that gap is wider than most encryption marketing wants to admit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two different attacks that look identical to the victim
&lt;/h2&gt;

&lt;p&gt;BEC comes in two forms, and the distinction matters for defense. The first is &lt;strong&gt;account takeover&lt;/strong&gt;: an attacker phishes credentials or steals a session token, logs into the real mailbox, and sends from it directly. Every authentication signal a recipient could check passes, because it genuinely is that account sending that mail. SPF passes. DKIM passes. DMARC alignment passes. The message is, by every protocol-level measure, authentic.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;domain impersonation&lt;/strong&gt;: the attacker registers a lookalike domain (rn instead of m, a swapped TLD, an extra hyphen) and sends from there, often after quietly monitoring a real email thread for weeks to learn the tone, the vendor names, and the amounts already in flight. No account is compromised. The forgery is in the domain, not the mailbox.&lt;/p&gt;

&lt;p&gt;Both produce the same result in an inbox: a message that reads as coming from someone the recipient already trusts, asking for something time-sensitive and financial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why DMARC enforcement helps with only one of the two
&lt;/h2&gt;

&lt;p&gt;SPF, DKIM, and DMARC exist to answer one question: did this message actually originate from a server the domain owner authorized? When DMARC is enforced at &lt;code&gt;p=reject&lt;/code&gt;, and not left at the far more common &lt;code&gt;p=none&lt;/code&gt; monitoring mode, it closes the domain impersonation vector almost completely. A lookalike domain sending unauthenticated mail gets rejected outright, or at minimum flagged before it lands.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key distinction.&lt;/strong&gt; DKIM and DMARC prove which mail server sent a message and that its headers weren't altered in transit. They say nothing about who was sitting at the keyboard, or whose credentials that keyboard belonged to. Account takeover BEC passes every one of these checks, because the check was never designed to answer that question.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the ceiling on authentication-layer defenses. A message from a genuinely compromised mailbox is, cryptographically, indistinguishable from a message the legitimate owner typed. Some of the same weaknesses that make forwarded and mailing-list mail hard to authenticate cleanly apply here too: the signature travels with the message regardless of who currently controls the account attached to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four patterns that account for most losses
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Typical ask&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Executive fraud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compromised or spoofed executive mailbox, often timed to travel or a known meeting&lt;/td&gt;
&lt;td&gt;Urgent wire transfer, gift cards, secrecy requested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vendor invoice redirect&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compromised vendor mailbox monitoring a real, ongoing invoice thread&lt;/td&gt;
&lt;td&gt;"Our bank changed, use this account for the next payment"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Attorney impersonation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Spoofed or compromised law firm domain, timed around a real closing&lt;/td&gt;
&lt;td&gt;Wire the closing or escrow funds to a new account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payroll diversion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compromised employee or HR mailbox&lt;/td&gt;
&lt;td&gt;Change direct-deposit routing details before the next pay cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice the common thread across all four: none of them require the attacker to break anything cryptographic. They require patience, plausible timing, and a payment process with no independent verification step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually stops it
&lt;/h2&gt;

&lt;p&gt;The controls that work aren't primarily cryptographic. They're process controls that assume any single email, however authentic it looks, can be wrong.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Out-of-band verification for any payment-detail change.&lt;/strong&gt; Call a phone number pulled from an existing record, never one supplied in the email itself, before honoring a new account number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dual control on wire transfers above a threshold.&lt;/strong&gt; No single approver, regardless of seniority or urgency framing, should be able to release funds alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DMARC enforcement at &lt;code&gt;p=reject&lt;/code&gt;, with SPF and DKIM properly aligned.&lt;/strong&gt; This closes the impersonation half of the problem outright and is table stakes even though it doesn't touch account takeover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message signing for finance-sensitive internal communication.&lt;/strong&gt; A PGP or S/MIME signature tied to a specific private key gives a recipient something to check beyond "does this look right": did this message verify against the key I already have for this person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Training staff to recognize the pattern, not the sender.&lt;/strong&gt; Urgency, secrecy, and a payment-detail change are the three-signal cluster that matters, independent of whose name is on the message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the one most training programs get backwards. Teaching people to scrutinize sender names and domains helps with impersonation, but account takeover BEC comes from an address that is, in every visible respect, correct. The signal that actually generalizes across both attack types is the shape of the request itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The session-token wrinkle that makes MFA less of a backstop than it used to be
&lt;/h2&gt;

&lt;p&gt;A common assumption is that multi-factor authentication closes the account-takeover half of this problem outright. It closes the credential-phishing version of it. It does much less against the adversary-in-the-middle phishing kits now common in BEC campaigns, which proxy a real login flow in real time and capture the session token issued after MFA succeeds, rather than trying to capture a reusable password. The victim completes a normal-looking login, MFA prompt included, and the attacker walks away with a live session cookie that bypasses the need to re-authenticate at all. A stolen session token is functionally indistinguishable, to the mail server, from the account owner simply staying logged in.&lt;/p&gt;

&lt;p&gt;This is part of why the process controls matter more than any single authentication upgrade. Phishing-resistant hardware keys (FIDO2/WebAuthn) close the adversary-in-the-middle gap in a way that OTP-based MFA doesn't, and they're worth adopting for exactly that reason. But a determined BEC operation only needs one compromised mailbox to run the executive-fraud or vendor-redirect pattern, and the defense that actually catches it is a human process that treats every payment-detail change as unverified until confirmed through a separate channel, regardless of how the request arrived or how authentic the account looked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a signed, encrypted channel helps and where it doesn't
&lt;/h2&gt;

&lt;p&gt;End-to-end encryption was never built to solve BEC, and it's worth being direct about that rather than implying otherwise. Encryption protects a message's contents from anyone who doesn't hold a key. It does nothing to verify who's sitting at the sender's keyboard, and a compromised account can send an encrypted message just as convincingly as a plaintext one.&lt;/p&gt;

&lt;p&gt;What a PGP-signed message adds is narrower and more specific: cryptographic proof that a message was signed with a particular private key, which is a stronger claim than "this domain's DKIM record checked out." If that private key lives somewhere an attacker who merely phished a web login can't reach, a forged executive request stops verifying, even from a compromised mail account, unless the attacker also has the signing key. That's a real, if partial, improvement over authentication headers alone. It's not a replacement for callback verification and dual control on money movement, and no vendor, us included, should suggest otherwise.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/business-email-compromise-fraud/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>encryption</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Thunderbolt and DMA Attacks: What a Port Can Do to a Locked Laptop</title>
      <dc:creator>Haven Messenger</dc:creator>
      <pubDate>Tue, 11 Aug 2026 12:22:19 +0000</pubDate>
      <link>https://dev.to/havenmessenger/thunderbolt-and-dma-attacks-what-a-port-can-do-to-a-locked-laptop-4an7</link>
      <guid>https://dev.to/havenmessenger/thunderbolt-and-dma-attacks-what-a-port-can-do-to-a-locked-laptop-4an7</guid>
      <description>&lt;p&gt;A locked laptop feels like a closed door. Full-disk encryption and a login screen are usually enough to stop a casual thief. Neither one, on its own, stops a device plugged into the right port from reading memory directly off the running system, because Thunderbolt was built to give external hardware a level of access that ordinary USB peripherals never get.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thunderbolt Is PCIe, Extended Out Through a Port
&lt;/h2&gt;

&lt;p&gt;Most external ports, a standard USB-A jack, for example, connect a peripheral to the system through a controller that mediates what the device can access. Thunderbolt works differently. It tunnels PCI Express, the same high-speed internal bus that graphics cards and NVMe drives use, out through an external connector. That's what makes Thunderbolt fast enough to drive an external GPU or a multi-monitor dock over a single cable. It's also what makes it a much more privileged interface than USB: a PCIe device isn't just handing data to the operating system through a driver, it can be granted direct memory access, DMA, which means reading and writing system RAM without going through the CPU or the OS at all.&lt;/p&gt;

&lt;p&gt;Direct memory access exists because it's useful. It's how a graphics card moves frame data efficiently, and how a fast storage controller avoids CPU overhead on every read and write. The security problem is that a malicious PCIe device, connected through a Thunderbolt port, can ask for the same kind of access and use it to read encryption keys, session tokens, or anything else sitting in memory while the machine is running, whether or not the screen is locked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Thunderspy Actually Demonstrated
&lt;/h2&gt;

&lt;p&gt;In 2020, researcher Bjorn Ruytenberg at Eindhoven University of Technology published Thunderspy, a set of attacks against Thunderbolt's security model. The research showed that with a few minutes of physical access to a Thunderbolt port, an attacker could reprogram the controller's firmware to clone the identity of an already-trusted device, or in some cases disable Thunderbolt security checks entirely, even on systems where the more restrictive security levels were enabled and even when the machine was locked or in sleep. Some of the described attacks required opening the laptop case briefly to attach hardware directly to the Thunderbolt controller; others worked through the port alone.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The finding wasn't that Thunderbolt has a bug. It's that the security model assumed a level of attacker effort that turned out to be lower than expected, and that firmware-level trust decisions made once at pairing time weren't being re-verified strongly enough afterward.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This class of attack sits in the same family as &lt;a href="https://havenmessenger.com/blog/posts/cold-boot-attacks-ram-forensics/" rel="noopener noreferrer"&gt;cold boot attacks&lt;/a&gt; in that both target data sitting in memory rather than data at rest, but the mechanism is different. Cold boot attacks exploit the physical property that RAM contents fade rather than vanish instantly after power loss. DMA attacks over Thunderbolt exploit a live, powered, running system through a legitimate hardware interface designed to grant broad access.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mitigations That Followed
&lt;/h2&gt;

&lt;p&gt;Intel's response was Kernel DMA Protection, which uses the system's IOMMU (the same VT-d or AMD-Vi hardware used for virtualization memory isolation) to restrict what a Thunderbolt-connected PCIe device can access before the operating system has explicitly authorized it. On supporting hardware and operating systems, this closes most of the pre-boot and pre-authorization window that Thunderspy relied on. The protection isn't universal retroactively: it depends on firmware and OS support that older systems, including many machines from before Kernel DMA Protection existed, simply don't have.&lt;/p&gt;

&lt;p&gt;Thunderbolt 4 made this stronger by requiring Kernel DMA Protection as part of the certification specification itself, rather than leaving it as an optional implementation detail the way Thunderbolt 3 did. That's a meaningful difference: a Thunderbolt 3 machine's actual exposure depends on whether its specific vendor implemented the protection, while a certified Thunderbolt 4 machine is required to have it.&lt;/p&gt;

&lt;p&gt;Apple's implementation on Mac hardware has followed a somewhat different path, layered on top of the T2 security chip on Intel-based Macs and, since the shift to Apple Silicon, on the tighter hardware and firmware integration of the M-series systems. The practical effect has been a narrower attack surface than a typical Windows laptop of the same era, though "narrower" isn't the same as "immune," and researchers have continued to probe Thunderbolt and USB4 implementations across vendors as the specification itself evolves. The lesson generalizes past any one platform: DMA protection is a property of the specific firmware and OS combination running on a given machine, not a property of the word "Thunderbolt" printed next to the port.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;DMA exposure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Thunderbolt 3, Kernel DMA Protection not implemented&lt;/td&gt;
&lt;td&gt;Full pre-Thunderspy-mitigation exposure while unlocked or briefly accessible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thunderbolt 3, Kernel DMA Protection implemented&lt;/td&gt;
&lt;td&gt;Significantly reduced, but depends on OS and firmware support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thunderbolt 4 (certification requires the protection)&lt;/td&gt;
&lt;td&gt;Kernel DMA Protection mandatory by specification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thunderbolt disabled entirely in firmware&lt;/td&gt;
&lt;td&gt;No PCIe tunneling exposure through the port at all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Sleep, Not Just Lock, Is the Exposure Window That Matters
&lt;/h2&gt;

&lt;p&gt;A locked screen with the system still fully powered and running is meaningfully different from a system that's been shut down or hibernated with the disk fully unmounted. Full-disk encryption protects data at rest, when the machine is off, but a locked or sleeping machine still has decryption keys and unlocked data sitting in RAM, which is exactly what a DMA attack targets. This is the same underlying exposure that &lt;a href="https://havenmessenger.com/blog/posts/evil-maid-attacks/" rel="noopener noreferrer"&gt;evil maid attacks&lt;/a&gt; exploit more broadly, brief unsupervised physical access to a device that's locked but still running, though DMA over Thunderbolt is a specific technical path into that broader category rather than a synonym for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Reduces the Risk
&lt;/h2&gt;

&lt;p&gt;For most people, the practical exposure is low: this requires physical access and specialized hardware, not something a remote attacker can do. It matters most for anyone who leaves a laptop briefly unattended in a context where a skilled adversary might have physical access, a hotel room, a border crossing, a shared workspace. Where it matters, the concrete steps are disabling Thunderbolt entirely in firmware settings when it's not in active use, keeping firmware and OS updated so Kernel DMA Protection stays current, and fully shutting down rather than sleeping when leaving a device unattended for any length of time, since a powered-off machine with &lt;a href="https://havenmessenger.com/blog/posts/secure-boot-explained/" rel="noopener noreferrer"&gt;secure boot&lt;/a&gt; and full-disk encryption removes the live-memory attack surface entirely.&lt;/p&gt;

&lt;p&gt;None of this argues against Thunderbolt as a technology. The speed it enables is real and useful. It argues for treating an external port that can request direct memory access the way you'd treat any other high-privilege interface: something to be deliberate about, not something to assume is safe by default because it looks like an ordinary cable.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://havenmessenger.com/blog/posts/thunderbolt-dma-attacks-explained/" rel="noopener noreferrer"&gt;havenmessenger.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>encryption</category>
      <category>hardware</category>
    </item>
  </channel>
</rss>
