<?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: Daan Acohen</title>
    <description>The latest articles on DEV Community by Daan Acohen (@daan_acohen).</description>
    <link>https://dev.to/daan_acohen</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3759765%2F3430b840-7a5d-49ca-a1a4-ff761faa9259.png</url>
      <title>DEV Community: Daan Acohen</title>
      <link>https://dev.to/daan_acohen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/daan_acohen"/>
    <language>en</language>
    <item>
      <title>Is your REST API actually Quantum-Safe? How to test it in 2026.</title>
      <dc:creator>Daan Acohen</dc:creator>
      <pubDate>Sat, 28 Mar 2026 18:16:42 +0000</pubDate>
      <link>https://dev.to/daan_acohen/is-your-rest-api-actually-quantum-safe-how-to-test-it-in-2026-4pl6</link>
      <guid>https://dev.to/daan_acohen/is-your-rest-api-actually-quantum-safe-how-to-test-it-in-2026-4pl6</guid>
      <description>&lt;h2&gt;
  
  
  The 2026 Reality: Why PQC Matters Now
&lt;/h2&gt;

&lt;p&gt;If you’ve been seeing the term &lt;strong&gt;Post-Quantum Cryptography (PQC)&lt;/strong&gt; pop up in your security audits lately, there’s a reason for it. We’ve officially entered the era where traditional encryption (like RSA and ECC) is no longer considered "future-proof."&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Harvest Now, Decrypt Later" (HNDL) Threat
&lt;/h3&gt;

&lt;p&gt;You might think, &lt;em&gt;"I don't need to worry about quantum computers yet; they aren't powerful enough to break my API today."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is a dangerous assumption.&lt;/strong&gt; Threat actors are currently practicing &lt;strong&gt;HNDL&lt;/strong&gt;: they are intercepting and storing encrypted traffic &lt;em&gt;today&lt;/em&gt;, waiting for the day a quantum computer is powerful enough to crack it. If your API is sending sensitive user data or long-lived secrets using classical encryption right now, that data is effectively a "time bomb" waiting to be decrypted in the future.&lt;/p&gt;

&lt;p&gt;This is why NIST standardized &lt;strong&gt;ML-KEM (FIPS 203)&lt;/strong&gt;. It’s a quantum-resistant algorithm designed to protect data against these future threats. But as developers, how do we actually know our infrastructure is using it?&lt;/p&gt;

&lt;h2&gt;
  
  
  The "False Positive" Trap
&lt;/h2&gt;

&lt;p&gt;Suppose your organization has spent months upgrading API gateways and load balancer certs to support these new standards. You run your standard verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://api.production.internal/v1/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output looks perfect: &lt;code&gt;HTTP/2 200 OK&lt;/code&gt;. You’ve got a green lock in the browser. You're done, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maybe not.&lt;/strong&gt; A standard &lt;code&gt;200 OK&lt;/code&gt; doesn't tell you if your connection was actually negotiated using a quantum-resistant algorithm or if it quietly fell back to classical X25519 because of a single mismatched cipher suite or an outdated library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;curl&lt;/code&gt; isn't enough for PQC testing
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; is the GOAT for general API work, but for PQC diagnostics, it has a few "blind spots":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OS Dependencies:&lt;/strong&gt; &lt;code&gt;curl&lt;/code&gt; uses your system’s TLS library (OpenSSL, Schannel, etc.). If your local machine's library hasn't been updated to the absolute latest NIST standards, &lt;code&gt;curl&lt;/code&gt; can't test them properly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handshake Noise:&lt;/strong&gt; Digging through &lt;code&gt;curl -v&lt;/code&gt; to find the negotiated key exchange group is a chore. It’s too much noise for a quick sanity check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent Fallbacks:&lt;/strong&gt; Most clients are designed to connect at any cost. They won't warn you if you’ve downgraded to a "vulnerable" classical connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Workflow: From Guessing to Knowing
&lt;/h2&gt;

&lt;p&gt;Instead of squinting at verbose logs, I now use Kemforge for manual "sanity checks" during development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kemforge &lt;span class="nt"&gt;-v&lt;/span&gt; https://api.dev.local/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kemforge pipes security diagnostics directly to &lt;code&gt;stderr&lt;/code&gt;. Before the JSON response even hits your terminal, you get a clear, explicit confirmation of the negotiated key exchange. If you see &lt;strong&gt;ML-KEM&lt;/strong&gt; or &lt;strong&gt;X25519MLKEM768&lt;/strong&gt;, you know the configuration is actually working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Doing the "Quantum-Safe" Check
&lt;/h3&gt;

&lt;p&gt;Because it provides explicit feedback, it’s incredibly easy to see directly if it HTTPS call is quantum safe, typically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* TLS DATA:
* TlsVersion: 1.3
* Cipher:   TLS_AES_256_GCM_SHA384
* KeyExchangeGroup: X25519
* This server is not protected against quantum attacks as the key exchange group does not contain MLKEM.
* 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* TLS DATA:
* TlsVersion: 1.3
* Cipher:   TLS_AES_128_GCM_SHA256
* KeyExchangeGroup: X25519MLKEM768
* This server supports post quantum cryptography so the server has protection against quantum attacks.
* 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxeq25dz55i74bgqo0kae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxeq25dz55i74bgqo0kae.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In 2026, we can't just assume our TLS configurations are correct. We need client-side verification that isn't tied to the whims of our operating system's libraries.&lt;/p&gt;

&lt;p&gt;Being able to grab a static Go binary and get an immediate, cross-platform diagnostic on any API—regardless of the backend language—is a massive time-saver for anyone responsible for API security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is your team verifying PQC on your endpoints? Are you relying on server-side logs, or have you integrated client-side diagnostics into your dev loop?&lt;/strong&gt; Let me know in your comment.&lt;/p&gt;




&lt;p&gt;Check out &lt;a href="https://github.com/ConnectingApps/kemforge" rel="noopener noreferrer"&gt;Kemforge on GitHub&lt;/a&gt; or install it directly. &lt;/p&gt;

&lt;p&gt;On Windows:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;winget install ConnectingApps.Kemforge&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;on macOS:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew tap ConnectingApps/kemforge&lt;/code&gt;&lt;br&gt;
&lt;code&gt;brew install kemforge&lt;/code&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>cybersecurity</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Windows Servers Are a Cryptographic Liability: Entire Countries Can Be Left Exposed to Quantum Attacks</title>
      <dc:creator>Daan Acohen</dc:creator>
      <pubDate>Fri, 20 Mar 2026 19:39:03 +0000</pubDate>
      <link>https://dev.to/daan_acohen/windows-servers-are-a-cryptographic-liability-entire-countries-can-be-left-exposed-to-quantum-32md</link>
      <guid>https://dev.to/daan_acohen/windows-servers-are-a-cryptographic-liability-entire-countries-can-be-left-exposed-to-quantum-32md</guid>
      <description>&lt;p&gt;In backend development, choosing between Windows and Linux often feels like a matter of preference.&lt;/p&gt;

&lt;p&gt;But under the hood, that choice determines something much deeper:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do you control your cryptography — or does your OS vendor?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a world moving toward post-quantum cryptography (PQC), that question is no longer theoretical.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Post-Quantum Cryptography (PQC)?
&lt;/h2&gt;

&lt;p&gt;Today’s internet security relies heavily on cryptographic algorithms like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RSA&lt;/li&gt;
&lt;li&gt;ECC (Elliptic Curve Cryptography)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are used in TLS to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exchange keys&lt;/li&gt;
&lt;li&gt;secure HTTPS traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are considered secure &lt;strong&gt;against classical computers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, a sufficiently powerful quantum computer could break these algorithms using techniques like Shor's algorithm.&lt;/p&gt;

&lt;p&gt;That leads to a well-known threat model:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Store now, decrypt later&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An attacker can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Capture encrypted traffic today&lt;/li&gt;
&lt;li&gt;Store it&lt;/li&gt;
&lt;li&gt;Decrypt it in the future once quantum capabilities exist&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Post-Quantum Cryptography (PQC) refers to new algorithms designed to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;remain secure even against quantum computers&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These are currently being standardized and gradually introduced into TLS (often in hybrid form).&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hidden Detail Most Developers Miss
&lt;/h2&gt;

&lt;p&gt;On Windows, TLS is handled by &lt;strong&gt;Schannel&lt;/strong&gt;, a built-in OS component.&lt;/p&gt;

&lt;p&gt;On Linux, TLS is handled in user space:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenSSL&lt;/li&gt;
&lt;li&gt;BoringSSL&lt;/li&gt;
&lt;li&gt;wolfSSL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This difference is invisible in code — but critical in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Same Code, Different Reality
&lt;/h2&gt;

&lt;h3&gt;
  
  
  .NET (Windows → Schannel)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Uses &lt;strong&gt;Schannel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No control over TLS&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  PowerShell
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://example.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Uses WinHTTP → &lt;strong&gt;Schannel&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Python (Partial Independence)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Uses OpenSSL&lt;/li&gt;
&lt;li&gt;But often embedded in Windows-based infrastructure&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Go (Independent)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Uses Go’s &lt;code&gt;crypto/tls&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fully independent&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Rust (rustls)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Can use &lt;code&gt;rustls&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fully independent&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Node.js
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Uses OpenSSL internally&lt;/li&gt;
&lt;li&gt;Not Schannel&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  curl (Two Worlds)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Windows → often &lt;strong&gt;Schannel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Linux → OpenSSL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-V&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Asymmetry
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;TLS Backend (Windows)&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;.NET&lt;/td&gt;
&lt;td&gt;Schannel&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PowerShell&lt;/td&gt;
&lt;td&gt;Schannel&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IIS&lt;/td&gt;
&lt;td&gt;Schannel&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;OpenSSL (usually)&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;crypto/tls&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;rustls/OpenSSL&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;OpenSSL&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;curl&lt;/td&gt;
&lt;td&gt;Schannel (default)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why PQC Changes Everything
&lt;/h2&gt;

&lt;p&gt;To defend against quantum threats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TLS must support new algorithms&lt;/li&gt;
&lt;li&gt;systems must migrate &lt;strong&gt;before attackers catch up&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On Linux / Go / Rust:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you can upgrade&lt;/li&gt;
&lt;li&gt;experiment&lt;/li&gt;
&lt;li&gt;deploy early&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On Windows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you cannot replace Schannel&lt;/li&gt;
&lt;li&gt;you cannot add new algorithms&lt;/li&gt;
&lt;li&gt;you must wait&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You do not control when you become quantum-resistant&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A Concrete Scenario: The Netherlands Falls Behind
&lt;/h2&gt;

&lt;p&gt;Imagine the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PQC support is rolled out globally&lt;/li&gt;
&lt;li&gt;Microsoft introduces PQC in Schannel&lt;/li&gt;
&lt;li&gt;but rollout is delayed or restricted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now assume:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Netherlands does not receive these updates in time&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Meanwhile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Dutch hospitals run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows Server&lt;/li&gt;
&lt;li&gt;IIS&lt;/li&gt;
&lt;li&gt;.NET APIs&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;All protected by:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;classical TLS&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Outside the Netherlands
&lt;/h2&gt;

&lt;p&gt;Other regions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adopt PQC-enabled TLS&lt;/li&gt;
&lt;li&gt;deploy hybrid cryptography&lt;/li&gt;
&lt;li&gt;reduce exposure&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Crypto State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Netherlands&lt;/td&gt;
&lt;td&gt;Classical TLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Others&lt;/td&gt;
&lt;td&gt;PQC / Hybrid&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Risk
&lt;/h2&gt;

&lt;p&gt;Attackers can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Target Dutch systems&lt;/li&gt;
&lt;li&gt;capture encrypted traffic&lt;/li&gt;
&lt;li&gt;store it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Years later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;quantum capabilities mature&lt;/li&gt;
&lt;li&gt;classical crypto breaks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;historical medical data becomes decryptable&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not because systems failed.&lt;/p&gt;

&lt;p&gt;But because:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;they could not evolve&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why This Is Hard to Fix
&lt;/h2&gt;

&lt;p&gt;Organizations cannot simply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;replace Schannel&lt;/li&gt;
&lt;li&gt;rewrite .NET systems&lt;/li&gt;
&lt;li&gt;migrate instantly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Especially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hospitals&lt;/li&gt;
&lt;li&gt;large enterprises&lt;/li&gt;
&lt;li&gt;government systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are constrained by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;legacy systems&lt;/li&gt;
&lt;li&gt;vendor software&lt;/li&gt;
&lt;li&gt;operational risk&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  This Is Not Just Technical — It’s Geopolitical
&lt;/h2&gt;

&lt;p&gt;Microsoft is a US-based company.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it operates under US law&lt;/li&gt;
&lt;li&gt;it can be compelled by the US government&lt;/li&gt;
&lt;li&gt;legal and political pressure can be applied&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Microsoft controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TLS capabilities (Schannel)&lt;/li&gt;
&lt;li&gt;PQC rollout&lt;/li&gt;
&lt;li&gt;algorithm availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;external influence can affect when entire countries become quantum-safe&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Conclusion: Rethink Your Stack Before It’s Too Late
&lt;/h2&gt;

&lt;p&gt;The core issue is not theoretical.&lt;/p&gt;

&lt;p&gt;It is architectural.&lt;/p&gt;

&lt;p&gt;If your systems depend on Schannel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you do not control your cryptography&lt;/li&gt;
&lt;li&gt;you do not control when new algorithms arrive&lt;/li&gt;
&lt;li&gt;you cannot react independently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not security.&lt;/p&gt;

&lt;p&gt;That is dependency.&lt;/p&gt;




&lt;h2&gt;
  
  
  This Forces a Strategic Question
&lt;/h2&gt;

&lt;p&gt;Organizations must ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do we control our cryptographic evolution?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;you are exposed to delays you cannot fix.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Architecture Matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Platform
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Windows → vendor-controlled crypto&lt;/li&gt;
&lt;li&gt;Linux → operator-controlled crypto&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Programming languages
&lt;/h3&gt;

&lt;p&gt;Control differs per ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go → full control&lt;/li&gt;
&lt;li&gt;Rust → full control&lt;/li&gt;
&lt;li&gt;Node.js → strong control&lt;/li&gt;
&lt;li&gt;Python → partial&lt;/li&gt;
&lt;li&gt;.NET → none (on Windows)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  TLS boundaries
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Move TLS out of OS-controlled layers&lt;/li&gt;
&lt;li&gt;prefer user-space TLS stacks&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tooling Matters Too
&lt;/h2&gt;

&lt;p&gt;Even &lt;code&gt;curl&lt;/code&gt; is not neutral.&lt;/p&gt;

&lt;p&gt;On Windows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it often uses &lt;strong&gt;Schannel&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A Practical Alternative: kemforge
&lt;/h2&gt;

&lt;p&gt;If you want to avoid Schannel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;kemforge&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;written in Go → independent TLS stack&lt;/li&gt;
&lt;li&gt;not tied to Schannel&lt;/li&gt;
&lt;li&gt;same CLI arguments as curl&lt;/li&gt;
&lt;li&gt;open source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ConnectingApps/kemforge" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/kemforge&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installation steps&lt;/li&gt;
&lt;li&gt;usage examples&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;This is the uncomfortable reality:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cryptography is not just math, it is control.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;depend on Schannel&lt;/li&gt;
&lt;li&gt;cannot upgrade independently&lt;/li&gt;
&lt;li&gt;cannot adopt PQC when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then your systems are not just technically constrained.&lt;/p&gt;

&lt;p&gt;They are:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;strategically constrained&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And in a post-quantum world:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;that constraint can turn into exposure faster than you expect.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>security</category>
      <category>openssl</category>
    </item>
    <item>
      <title>Is Your Rust App Ready for the Quantum Age? Find out with pqctracer</title>
      <dc:creator>Daan Acohen</dc:creator>
      <pubDate>Tue, 24 Feb 2026 15:19:59 +0000</pubDate>
      <link>https://dev.to/daan_acohen/is-your-rust-app-ready-for-the-quantum-age-find-out-with-pqctracer-kdh</link>
      <guid>https://dev.to/daan_acohen/is-your-rust-app-ready-for-the-quantum-age-find-out-with-pqctracer-kdh</guid>
      <description>&lt;p&gt;Quantum computers are coming — and when they arrive, much of today's encrypted internet traffic could be cracked wide open. That's not science fiction anymore; it's the reason NIST has already standardized post-quantum cryptography (PQC) algorithms, and why browsers like Chrome already negotiate post-quantum TLS handshakes by default.&lt;/p&gt;

&lt;p&gt;But here's the uncomfortable truth: &lt;strong&gt;even if your client supports PQC, you have no idea whether your actual HTTPS connections are quantum-safe — unless you instrument them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's exactly the problem &lt;a href="https://crates.io/crates/pqctracer" rel="noopener noreferrer"&gt;&lt;code&gt;pqctracer&lt;/code&gt;&lt;/a&gt; solves.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Quick Primer: What Is Post-Quantum Cryptography?
&lt;/h2&gt;

&lt;p&gt;Classical key-exchange algorithms like &lt;strong&gt;X25519&lt;/strong&gt; are considered vulnerable to a sufficiently powerful quantum computer running Shor's algorithm. Once that threshold is crossed, any recorded TLS traffic from today could be retroactively decrypted — a threat known as "harvest now, decrypt later."&lt;/p&gt;

&lt;p&gt;Hybrid schemes like &lt;strong&gt;X25519MLKEM768&lt;/strong&gt; defend against this by combining a classical algorithm with a post-quantum one. The connection stays secure even if one of the two is eventually broken.&lt;/p&gt;

&lt;p&gt;The catch? PQC only works if &lt;strong&gt;both the client and the server&lt;/strong&gt; support it. A client that prefers post-quantum handshakes will silently fall back to classical X25519 when the server doesn't support the hybrid group — and you'll never know unless you look.&lt;/p&gt;

&lt;p&gt;You can also check whether your browser and a given web server support PQC over at &lt;a href="https://quantumsafeaudit.com" rel="noopener noreferrer"&gt;quantumsafeaudit.com&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing pqctracer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://crates.io/crates/pqctracer" rel="noopener noreferrer"&gt;&lt;code&gt;pqctracer&lt;/code&gt;&lt;/a&gt; is a reusable Rust library that wraps &lt;code&gt;reqwest&lt;/code&gt; and &lt;code&gt;rustls&lt;/code&gt; (with &lt;code&gt;prefer-post-quantum&lt;/code&gt; enabled) to give you a &lt;strong&gt;TLS-aware HTTP client&lt;/strong&gt; that captures the exact key-exchange group and cipher suite negotiated for every request.&lt;/p&gt;

&lt;p&gt;No guesswork. No assumptions. Just facts straight from the TLS handshake.&lt;/p&gt;

&lt;p&gt;The source code is available on GitHub: &lt;a href="https://github.com/ConnectingApps/RustPqcTracer" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/RustPqcTracer&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Captures the negotiated TLS key-exchange group (e.g. &lt;code&gt;X25519MLKEM768&lt;/code&gt;) per request&lt;/li&gt;
&lt;li&gt;Captures the negotiated cipher suite (e.g. &lt;code&gt;TLS13_AES_256_GCM_SHA384&lt;/code&gt;) per request&lt;/li&gt;
&lt;li&gt;Built on &lt;code&gt;reqwest&lt;/code&gt; + &lt;code&gt;rustls&lt;/code&gt; with &lt;code&gt;prefer-post-quantum&lt;/code&gt; support&lt;/li&gt;
&lt;li&gt;Single shared connection-pooled client with lock-free per-request capture context across &lt;code&gt;.await&lt;/code&gt; points&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  See It in Action
&lt;/h2&gt;

&lt;p&gt;Here's all it takes to audit your outbound connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;pqctracer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TlsAwareClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;trace_host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tls_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TlsAwareClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Requesting: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to build request"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tls_client&lt;/span&gt;&lt;span class="nf"&gt;.execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"request failed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="py"&gt;.group&lt;/span&gt;&lt;span class="nf"&gt;.as_deref&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not available"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="py"&gt;.cipher&lt;/span&gt;&lt;span class="nf"&gt;.as_deref&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not available"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Status code: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="py"&gt;.response&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Negotiated group: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cipher suite: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Install aws-lc-rs as the process-level crypto provider (required when&lt;/span&gt;
    &lt;span class="c1"&gt;// `prefer-post-quantum` is enabled).&lt;/span&gt;
    &lt;span class="nn"&gt;rustls&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;aws_lc_rs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default_provider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.install_default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to install crypto provider"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Build the reusable TLS-aware client once.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tls_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;TlsAwareClient&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;trace_host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tls_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"www.google.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;trace_host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tls_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"www.bing.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's what you get back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requesting: https://www.google.com
Status code: 200 OK
Negotiated group: X25519MLKEM768
Cipher suite: TLS13_AES_256_GCM_SHA384

Requesting: https://www.bing.com
Status code: 200 OK
Negotiated group: X25519
Cipher suite: TLS13_AES_256_GCM_SHA384
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This output tells the whole story. &lt;strong&gt;Google&lt;/strong&gt; supports the hybrid post-quantum group &lt;code&gt;X25519MLKEM768&lt;/code&gt; — so the handshake upgrades to a quantum-safe key exchange. &lt;strong&gt;Bing&lt;/strong&gt;, on the other hand, only negotiates the classical &lt;code&gt;X25519&lt;/code&gt; group. Even though our client is fully PQC-capable, the connection to Bing gets zero post-quantum protection because the server doesn't support it yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters for Your Services
&lt;/h2&gt;

&lt;p&gt;If you're building or operating services that make outbound HTTPS calls — API clients, microservices, data pipelines — you probably have no visibility into what TLS groups are actually being negotiated. You might assume you're quantum-safe because you're using a modern TLS stack. But as the example above shows, that assumption is wrong half the time (or more).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pqctracer&lt;/code&gt; gives you the observability layer to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audit&lt;/strong&gt; which third-party APIs and services you depend on actually support PQC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track adoption&lt;/strong&gt; over time as servers upgrade&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert&lt;/strong&gt; when a critical connection falls back to a classical group&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Add &lt;code&gt;pqctracer&lt;/code&gt; to your &lt;code&gt;Cargo.toml&lt;/code&gt; and start auditing your connections today. The crate is published at &lt;a href="https://crates.io/crates/pqctracer" rel="noopener noreferrer"&gt;https://crates.io/crates/pqctracer&lt;/a&gt; and the full source code lives at &lt;a href="https://github.com/ConnectingApps/RustPqcTracer" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/RustPqcTracer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The quantum threat isn't theoretical anymore — but the tools to defend against it are already here. Now you can see exactly where you stand.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>rust</category>
      <category>security</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Is Your Node.js App Quantum-Safe? Tracing PQC in TLS Connections</title>
      <dc:creator>Daan Acohen</dc:creator>
      <pubDate>Fri, 20 Feb 2026 21:39:06 +0000</pubDate>
      <link>https://dev.to/daan_acohen/is-your-nodejs-app-quantum-safe-tracing-pqc-in-tls-connections-10b8</link>
      <guid>https://dev.to/daan_acohen/is-your-nodejs-app-quantum-safe-tracing-pqc-in-tls-connections-10b8</guid>
      <description>&lt;p&gt;Post-Quantum Cryptography (PQC) is no longer a distant concern — it is happening now. Google Chrome, Cloudflare, and major cloud providers have already started deploying hybrid post-quantum key exchange in TLS connections. But how do you actually verify, from code, that your Node.js HTTPS requests are using quantum-safe cryptography? That is exactly what &lt;a href="https://www.npmjs.com/package/pqc-tracer" rel="noopener noreferrer"&gt;&lt;code&gt;pqc-tracer&lt;/code&gt;&lt;/a&gt; solves.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Post-Quantum Cryptography (PQC)?
&lt;/h2&gt;

&lt;p&gt;Classical public-key cryptography — RSA, ECDH, and elliptic curve algorithms like X25519 — relies on mathematical problems (factoring large numbers, discrete logarithm) that are computationally hard for classical computers. A sufficiently powerful quantum computer running &lt;a href="https://en.wikipedia.org/wiki/Shor%27s_algorithm" rel="noopener noreferrer"&gt;Shor's algorithm&lt;/a&gt; can solve these problems efficiently, breaking the confidentiality of any TLS session secured by these algorithms.&lt;/p&gt;

&lt;p&gt;Post-Quantum Cryptography refers to algorithms believed to be resistant even against quantum computers. NIST standardized several PQC algorithms in 2024, most notably:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ML-KEM&lt;/strong&gt; (Module Lattice Key Encapsulation Mechanism, also known as CRYSTALS-Kyber) — used for key exchange in TLS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ML-DSA&lt;/strong&gt; (CRYSTALS-Dilithium) — used for digital signatures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, TLS deployments today use &lt;strong&gt;hybrid key exchange&lt;/strong&gt;, combining a classical algorithm with a PQC algorithm. For example, &lt;code&gt;X25519MLKEM768&lt;/code&gt; combines classical X25519 with ML-KEM-768. This ensures security even if one of the two algorithms is broken.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Do We Need a Tool to Check PQC Usage?
&lt;/h2&gt;

&lt;p&gt;TLS handshake details are not exposed by default in high-level HTTP client APIs. When you do &lt;code&gt;fetch('https://example.com')&lt;/code&gt; or use Node's &lt;code&gt;https.get(...)&lt;/code&gt;, you get a response — but you have no visibility into which key exchange group was negotiated during the TLS handshake.&lt;/p&gt;

&lt;p&gt;A tool like &lt;code&gt;pqc-tracer&lt;/code&gt; fills this gap. It:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Makes an outgoing HTTPS request using Node's native TLS stack.&lt;/li&gt;
&lt;li&gt;Intercepts the established TLS socket and, using native FFI calls into &lt;code&gt;libssl.so.3&lt;/code&gt;, reads the negotiated key exchange group directly from the OpenSSL &lt;code&gt;SSL*&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;Returns both the TLS trace (group name + cipher suite) and the full HTTP response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This lets you answer the question: &lt;strong&gt;"Is this HTTPS connection actually quantum-safe?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is particularly valuable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security auditors&lt;/strong&gt; checking whether production services have enabled PQC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developers&lt;/strong&gt; verifying their Node.js version supports PQC before deploying to production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Researchers&lt;/strong&gt; monitoring the rollout of PQC across the internet.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How Does &lt;code&gt;pqc-tracer&lt;/code&gt; Work?
&lt;/h2&gt;

&lt;p&gt;The library uses &lt;a href="https://koffi.dev/" rel="noopener noreferrer"&gt;&lt;code&gt;koffi&lt;/code&gt;&lt;/a&gt; — a fast FFI library for Node.js — to call two OpenSSL C functions directly from native &lt;code&gt;libssl.so.3&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SSL_ctrl(ssl, 134, 0, NULL)&lt;/code&gt;&lt;/strong&gt; — queries the negotiated key exchange group ID (NID). Constant &lt;code&gt;134&lt;/code&gt; is &lt;code&gt;SSL_CTRL_GET_NEGOTIATED_GROUP&lt;/code&gt; from OpenSSL's &lt;code&gt;ssl.h&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SSL_group_to_name(ssl, groupId)&lt;/code&gt;&lt;/strong&gt; — converts the numeric group ID to a human-readable name like &lt;code&gt;"X25519MLKEM768"&lt;/code&gt; or &lt;code&gt;"X25519"&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The native &lt;code&gt;SSL*&lt;/code&gt; pointer is accessed from Node's internal TLS socket through stderr-level fd capture of OpenSSL's &lt;code&gt;SSL_trace&lt;/code&gt; output, which surfaces the group name via OpenSSL's own diagnostic output.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Linux only.&lt;/strong&gt; This approach depends on Node's use of OpenSSL as its TLS backend and &lt;code&gt;libc.so.6&lt;/code&gt; for fd-level redirection. macOS and Windows use different native TLS libraries and are not supported.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;pqc-tracer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js &amp;gt;= 20&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;executeRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pqc-tracer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www.example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;executeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;tlsTrace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Negotiated Group: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tlsTrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Cipher Suite: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tlsTrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cipherSuite&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HTTP Status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What You Get
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;TlsTrace&lt;/code&gt; interface exposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TlsTrace&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// e.g. "X25519MLKEM768"&lt;/span&gt;
  &lt;span class="nl"&gt;cipherSuite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// e.g. "TLS_AES_256_GCM_SHA384"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;group&lt;/code&gt; is &lt;code&gt;X25519MLKEM768&lt;/code&gt;, your connection is using a hybrid PQC key exchange — you are quantum-safe. If it is &lt;code&gt;X25519&lt;/code&gt; or &lt;code&gt;P-256&lt;/code&gt;, your connection uses only classical key exchange.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full Response Type
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HttpResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;statusMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IncomingHttpHeaders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;httpVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RequestResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;tlsTrace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TlsTrace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Low-Level API
&lt;/h3&gt;

&lt;p&gt;If you want to integrate PQC tracing into your own request logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;startStderrCapture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopStderrCapture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getTlsTrace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pqc-tracer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Before your TLS handshake:&lt;/span&gt;
&lt;span class="nf"&gt;startStderrCapture&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// redirects fd 2 to a temp file&lt;/span&gt;

&lt;span class="c1"&gt;// ... perform your HTTPS request ...&lt;/span&gt;

&lt;span class="c1"&gt;// After the handshake completes:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;traceOutput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;stopStderrCapture&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// restores stderr, returns captured output&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tlsTrace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getTlsTrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;traceOutput&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tlsTrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Does the Node.js Version Matter for PQC?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Absolutely — and this is the most important practical insight from this project.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Node.js bundles its own version of OpenSSL. PQC support in TLS (specifically ML-KEM via &lt;code&gt;X25519MLKEM768&lt;/code&gt;) was added in &lt;strong&gt;OpenSSL 3.5.0&lt;/strong&gt;. Whether your HTTPS connections are quantum-safe depends entirely on which OpenSSL version your Node.js build ships with.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/ConnectingApps/NodePqcReader" rel="noopener noreferrer"&gt;NodePqcReader&lt;/a&gt; repository contains a Docker-based test that runs the same HTTPS request to &lt;code&gt;www.google.com&lt;/code&gt; under &lt;strong&gt;Node 20, 22, and 24&lt;/strong&gt; to compare results.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:24.04&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NVM_DIR=/root/.nvm&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_VERSION_BUILD=22&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_VERSION_RUN_1=20&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_VERSION_RUN_2=22&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_VERSION_RUN_3=24&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl ca-certificates &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Install nvm and the required Node versions&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-o-&lt;/span&gt; https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    nvm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nv"&gt;$NODE_VERSION_BUILD&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    nvm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nv"&gt;$NODE_VERSION_RUN_1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    nvm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nv"&gt;$NODE_VERSION_RUN_3&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# 1. Build pqc-tracer first (its dist/ is gitignored, so we must build it here)&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pqc-tracer ./pqc-tracer&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app/pqc-tracer&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; nvm use &lt;span class="nv"&gt;$NODE_VERSION_BUILD&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build

&lt;span class="c"&gt;# 2. Build the main project&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; tsconfig.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; nvm use &lt;span class="nv"&gt;$NODE_VERSION_BUILD&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; run.sh ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x run.sh

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["./run.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Run Script (&lt;code&gt;run.sh&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Running with Node &lt;/span&gt;&lt;span class="nv"&gt;$NODE_VERSION_RUN_1&lt;/span&gt;&lt;span class="s2"&gt; ==="&lt;/span&gt;
nvm use &lt;span class="nv"&gt;$NODE_VERSION_RUN_1&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"OpenSSL Version"&lt;/span&gt;
node &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"process.versions.openssl"&lt;/span&gt;
node dist/index.js

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Running with Node &lt;/span&gt;&lt;span class="nv"&gt;$NODE_VERSION_RUN_2&lt;/span&gt;&lt;span class="s2"&gt; ==="&lt;/span&gt;
nvm use &lt;span class="nv"&gt;$NODE_VERSION_RUN_2&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"OpenSSL Version"&lt;/span&gt;
node &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"process.versions.openssl"&lt;/span&gt;
node dist/index.js

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Running with Node &lt;/span&gt;&lt;span class="nv"&gt;$NODE_VERSION_RUN_3&lt;/span&gt;&lt;span class="s2"&gt; ==="&lt;/span&gt;
nvm use &lt;span class="nv"&gt;$NODE_VERSION_RUN_3&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"OpenSSL Version"&lt;/span&gt;
node &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"process.versions.openssl"&lt;/span&gt;
node dist/index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script uses &lt;code&gt;nvm&lt;/code&gt; to switch between Node versions and prints the bundled OpenSSL version before running the tracer against &lt;code&gt;www.google.com&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Main Script (&lt;code&gt;src/index.ts&lt;/code&gt;)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;executeRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pqc-tracer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;www.google.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User-Agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NodePqcReader/1.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;executeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;tlsTrace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Negotiated Group: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tlsTrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Cipher Suite: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tlsTrace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cipherSuite&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HTTP Status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Response Preview: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Container Output
&lt;/h3&gt;

&lt;p&gt;This is the actual output from a GitHub Actions run of the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== Running with Node 20 ===
Now using node v20.20.0 (npm v10.8.2)
OpenSSL Version
3.0.17
Negotiated Group: X25519
Cipher Suite: TLS_AES_256_GCM_SHA384
HTTP Status: 200
Response Preview: &amp;lt;!doctype 
=== Running with Node 22 ===
Now using node v22.22.0 (npm v10.9.4)
OpenSSL Version
3.5.4
Negotiated Group: X25519MLKEM768
Cipher Suite: TLS_AES_256_GCM_SHA384
HTTP Status: 200
Response Preview: &amp;lt;!doctype 
=== Running with Node 24 ===
Now using node v24.13.1 (npm v11.8.0)
OpenSSL Version
3.5.5
Negotiated Group: X25519MLKEM768
Cipher Suite: TLS_AES_256_GCM_SHA384
HTTP Status: 200
Response Preview: &amp;lt;!doctype 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Does This Output Tell Us?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node Version&lt;/th&gt;
&lt;th&gt;Exact Version&lt;/th&gt;
&lt;th&gt;Bundled OpenSSL&lt;/th&gt;
&lt;th&gt;Negotiated Group&lt;/th&gt;
&lt;th&gt;Quantum-Safe?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;v20.20.0&lt;/td&gt;
&lt;td&gt;3.0.17&lt;/td&gt;
&lt;td&gt;X25519&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;v22.22.0&lt;/td&gt;
&lt;td&gt;3.5.4&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;v24.13.1&lt;/td&gt;
&lt;td&gt;3.5.5&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&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 results are striking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node 20&lt;/strong&gt; bundles OpenSSL 3.0.17, which does not support ML-KEM. Connections to Google (which offers &lt;code&gt;X25519MLKEM768&lt;/code&gt;) fall back to classical &lt;code&gt;X25519&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node 22&lt;/strong&gt; bundles OpenSSL 3.5.4 — and that is already enough for full PQC support. The connection to Google negotiates &lt;code&gt;X25519MLKEM768&lt;/code&gt;, a hybrid PQC key exchange.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node 24&lt;/strong&gt; bundles OpenSSL 3.5.5, also negotiating &lt;code&gt;X25519MLKEM768&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cipher suite (&lt;code&gt;TLS_AES_256_GCM_SHA384&lt;/code&gt;) remains the same across all versions — this is the symmetric encryption used after key exchange and is already quantum-resistant at 256-bit strength.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The takeaway: Node 20 is the odd one out. Both Node 22 and Node 24 ship with OpenSSL 3.5 and support PQC out of the box. If you are still on Node 20, you are missing quantum-safe key exchange entirely.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Node.js Has an Advantage Over Python and .NET
&lt;/h2&gt;

&lt;p&gt;Here is where Node.js shines compared to other languages: &lt;strong&gt;Node bundles its own OpenSSL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In most Linux distributions, Python and .NET use the system's shared OpenSSL (&lt;code&gt;/usr/lib/x86_64-linux-gnu/libssl.so.3&lt;/code&gt; or similar). This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On &lt;strong&gt;Ubuntu 24.04&lt;/strong&gt;, the system OpenSSL is 3.0.x — &lt;strong&gt;no PQC support&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;To get PQC in Python, you would need to compile OpenSSL 3.5 from source, set &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;, and possibly recompile Python itself or use a custom &lt;code&gt;ssl&lt;/code&gt; module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET&lt;/strong&gt; on Linux also links against the system OpenSSL by default. Getting ML-KEM support requires either a custom OpenSSL build or waiting for the distribution to ship 3.5.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Node.js sidesteps all of this.&lt;/strong&gt; When you install Node 24 via &lt;code&gt;nvm&lt;/code&gt;, it ships with its own OpenSSL 3.5 statically or dynamically linked — no system-level changes required. PQC just works, immediately, without any manual setup.&lt;/p&gt;

&lt;p&gt;This makes Node.js currently the most accessible runtime for experimenting with and deploying PQC in outgoing HTTPS requests on Linux.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Package&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.npmjs.com/package/pqc-tracer" rel="noopener noreferrer"&gt;&lt;code&gt;pqc-tracer&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/ConnectingApps/NodePqcReader" rel="noopener noreferrer"&gt;ConnectingApps/NodePqcReader&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Minimum Node for PQC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node 22 (bundles OpenSSL 3.5+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PQC group to look for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X25519MLKEM768&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Platform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Linux only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key advantage vs Python/.NET&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node bundles its own OpenSSL — no system-level setup needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you want to audit whether your Node.js services are making quantum-safe connections, &lt;code&gt;pqc-tracer&lt;/code&gt; gives you a simple, programmatic way to find out. Install it, run it against your endpoints, and check whether you're seeing &lt;code&gt;X25519MLKEM768&lt;/code&gt; — or still stuck in the classical world.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>networking</category>
      <category>node</category>
      <category>security</category>
    </item>
    <item>
      <title>Verify Post-Quantum TLS Negotiation from Python `requests` on Linux</title>
      <dc:creator>Daan Acohen</dc:creator>
      <pubDate>Thu, 19 Feb 2026 16:41:50 +0000</pubDate>
      <link>https://dev.to/daan_acohen/verify-post-quantum-tls-negotiation-from-python-requests-on-linux-719</link>
      <guid>https://dev.to/daan_acohen/verify-post-quantum-tls-negotiation-from-python-requests-on-linux-719</guid>
      <description>&lt;p&gt;Post-quantum cryptography (PQC) is no longer a “someday” topic. Standards are published, vendors are shipping hybrid TLS key exchange groups, and security teams are being asked a deceptively simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are we actually negotiating PQC (or hybrid PQC) on real connections—outside the lab?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pqcreader&lt;/code&gt; is a small Python package that wraps Python &lt;code&gt;requests&lt;/code&gt; calls and exposes TLS handshake metadata—especially the &lt;strong&gt;negotiated key exchange group&lt;/strong&gt; (where hybrid PQC shows up) and the &lt;strong&gt;cipher suite&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;PyPI: &lt;a href="https://pypi.org/project/pqcreader/" rel="noopener noreferrer"&gt;https://pypi.org/project/pqcreader/&lt;/a&gt;&lt;br&gt;
Source repo: &lt;a href="https://github.com/ConnectingApps/PyPqcReader" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/PyPqcReader&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  PQC in two minutes: why it matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  “Harvest now, decrypt later”
&lt;/h3&gt;

&lt;p&gt;Even before large-scale quantum computers exist, attackers can record encrypted traffic today and attempt to decrypt it later when they do. If the data has a long confidentiality lifetime (think: customer data, credentials, health data, intellectual property, state secrets), that risk is practical rather than academic.&lt;/p&gt;

&lt;h3&gt;
  
  
  NIST has finalized initial post-quantum encryption standards
&lt;/h3&gt;

&lt;p&gt;In 2024, NIST finalized its first post-quantum encryption standards, including &lt;strong&gt;FIPS 203 (ML-KEM)&lt;/strong&gt; (derived from CRYSTALS-Kyber and renamed ML-KEM).&lt;/p&gt;

&lt;p&gt;NIST announcement: &lt;a href="https://www.nist.gov/news-events/news/2024/08/nist-releases-first-3-finalized-post-quantum-encryption-standards" rel="noopener noreferrer"&gt;https://www.nist.gov/news-events/news/2024/08/nist-releases-first-3-finalized-post-quantum-encryption-standards&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  TLS migration reality: hybrid key exchange
&lt;/h3&gt;

&lt;p&gt;Most deployments won’t jump directly from “classical-only” to “PQC-only.” The common path is &lt;strong&gt;hybrid key exchange&lt;/strong&gt;: combine classical ECDHE with a post-quantum KEM so that the handshake remains secure even if one component becomes vulnerable.&lt;/p&gt;

&lt;p&gt;IETF hybrid TLS design draft: &lt;a href="https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/" rel="noopener noreferrer"&gt;https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What pqcreader does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pqcreader&lt;/code&gt; focuses on a very operational capability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrap an HTTP request (via &lt;code&gt;requests&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Extract TLS handshake details that are not typically easy to retrieve from standard Python APIs&lt;/li&gt;
&lt;li&gt;Print the negotiated &lt;strong&gt;group&lt;/strong&gt; and &lt;strong&gt;cipher suite&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially useful when testing hybrid PQC deployments, where the negotiated group can be the difference between “we think it’s enabled” and “it is actually happening on the wire.”&lt;/p&gt;

&lt;p&gt;OpenSSL (example of hybrid groups such as X25519MLKEM768): &lt;a href="https://docs.openssl.org/3.6/man3/SSL_CTX_set1_curves/" rel="noopener noreferrer"&gt;https://docs.openssl.org/3.6/man3/SSL_CTX_set1_curves/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚠️ Important constraints
&lt;/h2&gt;

&lt;p&gt;The project states (verbatim):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚠️ IMPORTANT:&lt;/strong&gt; This library is &lt;strong&gt;Linux-only&lt;/strong&gt;. It will &lt;strong&gt;NOT&lt;/strong&gt; work on Windows, macOS, or any other operating system. The library uses Linux-specific OpenSSL library loading and relies on system-level integration that is not available on other platforms. Please ensure you are running on a Linux system before attempting to use this library.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In addition, it is &lt;strong&gt;CPython-only&lt;/strong&gt; and described as &lt;strong&gt;experimental&lt;/strong&gt; across Python/OpenSSL variants. In practice, you should treat &lt;code&gt;pqcreader&lt;/code&gt; as a &lt;strong&gt;diagnostic probe&lt;/strong&gt; (CI checks, rollout validation, troubleshooting), not as a core production dependency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pqcreader
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Requirements (from the project)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10+&lt;/li&gt;
&lt;li&gt;Linux operating system (required for OpenSSL tracing)&lt;/li&gt;
&lt;li&gt;OpenSSL 3.x&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Quick start: trace a real &lt;code&gt;requests.get()&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic usage (from the README)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pqcreader&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pqcreader_request&lt;/span&gt;

&lt;span class="c1"&gt;# Wrap any requests call
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tls_trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pqcreader_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.google.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Negotiated Group: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tls_trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cipher Suite: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tls_trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cipher_suite&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What you’re looking for
&lt;/h3&gt;

&lt;p&gt;If your client, server, and OpenSSL runtime negotiate a hybrid PQC group, the &lt;strong&gt;group name&lt;/strong&gt; is typically where you’ll see it. Hybrid group names are ecosystem-specific, but OpenSSL documentation includes examples like &lt;code&gt;X25519MLKEM768&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;OpenSSL curves/groups doc (includes hybrid group examples): &lt;a href="https://docs.openssl.org/3.6/man3/SSL_CTX_set1_curves/" rel="noopener noreferrer"&gt;https://docs.openssl.org/3.6/man3/SSL_CTX_set1_curves/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;These helpers are useful for quick probes you can drop into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI smoke tests (to detect negotiation regressions)&lt;/li&gt;
&lt;li&gt;environment verification during rollout (dev/staging/prod)&lt;/li&gt;
&lt;li&gt;incident response diagnostics (confirm crypto posture quickly)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How it works (high level)
&lt;/h2&gt;

&lt;p&gt;The package intercepts HTTPS connections (via &lt;code&gt;urllib3&lt;/code&gt; under &lt;code&gt;requests&lt;/code&gt;), accesses the underlying OpenSSL socket, and queries OpenSSL for handshake metadata.&lt;/p&gt;

&lt;p&gt;This is a practical approach for answering the one question that matters operationally:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What did we negotiate for this request, in this environment, at runtime?”&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Where pqcreader fits in a PQC readiness workflow
&lt;/h2&gt;

&lt;p&gt;A pragmatic engineering workflow for PQC readiness looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Standards awareness&lt;/strong&gt;: understand what is being standardized and deployed (e.g., ML-KEM).&lt;br&gt;
&lt;a href="https://www.nist.gov/news-events/news/2024/08/nist-releases-first-3-finalized-post-quantum-encryption-standards" rel="noopener noreferrer"&gt;https://www.nist.gov/news-events/news/2024/08/nist-releases-first-3-finalized-post-quantum-encryption-standards&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Protocol design&lt;/strong&gt;: understand hybrid TLS 1.3 construction and expected negotiation behavior.&lt;br&gt;
&lt;a href="https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/" rel="noopener noreferrer"&gt;https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Runtime verification&lt;/strong&gt;: confirm what is negotiated by your real clients and endpoints.&lt;br&gt;
That is where &lt;code&gt;pqcreader&lt;/code&gt; provides immediate value.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pqcreader&lt;/code&gt; on PyPI: &lt;a href="https://pypi.org/project/pqcreader/" rel="noopener noreferrer"&gt;https://pypi.org/project/pqcreader/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source repository: &lt;a href="https://github.com/ConnectingApps/PyPqcReader" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/PyPqcReader&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NIST PQC standards announcement: &lt;a href="https://www.nist.gov/news-events/news/2024/08/nist-releases-first-3-finalized-post-quantum-encryption-standards" rel="noopener noreferrer"&gt;https://www.nist.gov/news-events/news/2024/08/nist-releases-first-3-finalized-post-quantum-encryption-standards&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;IETF hybrid TLS design draft: &lt;a href="https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/" rel="noopener noreferrer"&gt;https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenSSL groups/curves documentation (hybrid examples): &lt;a href="https://docs.openssl.org/3.6/man3/SSL_CTX_set1_curves/" rel="noopener noreferrer"&gt;https://docs.openssl.org/3.6/man3/SSL_CTX_set1_curves/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you want, Mr Acohen, I can also format it to dev.to conventions (front matter, recommended tags, and a tighter opening hook) while keeping the “direct links only” rule and keeping every README code block intact.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>networking</category>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>Trace Whether Your .NET `HttpClient` Calls Are Quantum-Safe (PQC) 🚦</title>
      <dc:creator>Daan Acohen</dc:creator>
      <pubDate>Wed, 18 Feb 2026 15:55:09 +0000</pubDate>
      <link>https://dev.to/daan_acohen/trace-whether-your-net-httpclient-calls-are-quantum-safe-pqc-hd5</link>
      <guid>https://dev.to/daan_acohen/trace-whether-your-net-httpclient-calls-are-quantum-safe-pqc-hd5</guid>
      <description>&lt;p&gt;Post-quantum cryptography (PQC) has a reputation for sounding “future-ish” and theoretical—until you realize the uncomfortable reality:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data you encrypt today can be harvested today and decrypted later&lt;/strong&gt; when a sufficiently capable quantum computer arrives. This is the “harvest now, decrypt later” problem, and it’s the reason PQC has moved from academic curiosity to &lt;strong&gt;real-world migration work&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As .NET developers, we ship software that talks to &lt;em&gt;lots&lt;/em&gt; of endpoints—internal services, SaaS APIs, identity providers, payment gateways, telemetry backends. And most of those calls happen through one thing:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;&lt;code&gt;HttpClient&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If PQC readiness matters to your organization (and it increasingly does), you need a simple answer to a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which of my outgoing HTTPS calls are already negotiating quantum-resistant key exchange—and which are not?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s exactly what &lt;strong&gt;PqcTracer&lt;/strong&gt; provides: it makes TLS negotiation details visible, so you can see whether connections are using &lt;strong&gt;post-quantum / hybrid key exchange&lt;/strong&gt; (for example, groups like &lt;code&gt;X25519MLKEM768&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NuGet: &lt;strong&gt;&lt;a href="https://www.nuget.org/packages/ConnectingApps.PqcTracer" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/ConnectingApps.PqcTracer&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Source repo: &lt;strong&gt;&lt;a href="https://github.com/ConnectingApps/PqcTracer" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/PqcTracer&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why PQC matters (in practical developer terms)
&lt;/h2&gt;

&lt;p&gt;Most TLS connections today rely on key exchange mechanisms based on classical cryptography (commonly elliptic curve or RSA). The concern is that &lt;strong&gt;Shor’s algorithm&lt;/strong&gt; on a sufficiently powerful quantum computer would break RSA and elliptic-curve cryptography, undermining the security assumptions behind today’s TLS handshakes. &lt;/p&gt;

&lt;p&gt;The industry response is &lt;strong&gt;Post-Quantum Cryptography&lt;/strong&gt;: algorithms designed to resist both classical and quantum attacks. A particularly important piece is &lt;em&gt;key establishment&lt;/em&gt;. One of the NIST-standardized directions here is &lt;strong&gt;ML-KEM&lt;/strong&gt; (formerly known as Kyber), used in modern TLS stacks as part of a &lt;strong&gt;hybrid key exchange&lt;/strong&gt;—combining classical + post-quantum components. &lt;/p&gt;

&lt;p&gt;In practice, if you see a negotiated TLS group like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;X25519MLKEM768&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…you’re looking at a hybrid that includes &lt;strong&gt;ML-KEM&lt;/strong&gt;, i.e., a strong indicator that the connection is negotiating in a way designed to be &lt;strong&gt;quantum-resistant&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;But here’s the engineering problem: &lt;strong&gt;you usually don’t see this from application code.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The typical .NET situation: “TLS happens somewhere below me”
&lt;/h2&gt;

&lt;p&gt;In .NET, outgoing HTTPS calls go through &lt;code&gt;HttpClient&lt;/code&gt;, and TLS negotiation is handled by platform crypto + the underlying TLS library. Your app typically doesn’t expose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which &lt;strong&gt;key exchange group&lt;/strong&gt; was negotiated&lt;/li&gt;
&lt;li&gt;Which &lt;strong&gt;cipher suite&lt;/strong&gt; was chosen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; reach for packet capture tooling, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it’s heavy for day-to-day dev,&lt;/li&gt;
&lt;li&gt;often not feasible in production,&lt;/li&gt;
&lt;li&gt;and not the kind of visibility you want to bolt onto every service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You want something &lt;strong&gt;code-level&lt;/strong&gt;, repeatable, and easy to wire in.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enter PqcTracer: TLS trace headers for &lt;em&gt;your&lt;/em&gt; outgoing &lt;code&gt;HttpClient&lt;/code&gt; calls
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PqcTracer&lt;/strong&gt; is a .NET library that traces TLS negotiation details for both incoming and outgoing HTTPS traffic. This article focuses on the part most applications care about immediately:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Outgoing traffic (&lt;code&gt;HttpClient&lt;/code&gt;)&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Once enabled, you can extract TLS trace info from the response and answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What &lt;strong&gt;TLS group&lt;/strong&gt; did we negotiate?&lt;/li&gt;
&lt;li&gt;What &lt;strong&gt;cipher suite&lt;/strong&gt; did we use?&lt;/li&gt;
&lt;li&gt;Does the group indicate &lt;strong&gt;ML-KEM&lt;/strong&gt; (and therefore PQC/hybrid readiness)?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;

&lt;p&gt;Use the NuGet package:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.nuget.org/packages/ConnectingApps.PqcTracer" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/ConnectingApps.PqcTracer&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package ConnectingApps.PqcTracer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Project source:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ConnectingApps/PqcTracer" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/PqcTracer&lt;/a&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  Outgoing traffic: two ways to trace TLS on &lt;code&gt;HttpClient&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;PqcTracer gives you two approaches, depending on whether you build &lt;code&gt;HttpClient&lt;/code&gt; manually or rely on DI with &lt;code&gt;IHttpClientFactory&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Both produce the same trace output. &lt;/p&gt;




&lt;h1&gt;
  
  
  Approach 1: Direct handler usage (quick, explicit, great for utilities)
&lt;/h1&gt;

&lt;p&gt;This is the simplest possible wiring: create a &lt;code&gt;TlsTracingHandler&lt;/code&gt;, pass it into &lt;code&gt;HttpClient&lt;/code&gt;, and then query the trace from the response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exact example code (as documented):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ConnectingApps.PqcTracer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TlsTracingHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://www.google.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTlsTrace&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Negotiated Group: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Cipher Suite: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CipherSuite&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TLS Trace not found."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Error: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When to use it
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Small tools / diagnostics&lt;/li&gt;
&lt;li&gt;One-off probes in CI&lt;/li&gt;
&lt;li&gt;Console apps that verify endpoints&lt;/li&gt;
&lt;li&gt;Situations where you want &lt;strong&gt;zero DI setup&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Approach 2: DI with &lt;code&gt;IHttpClientFactory&lt;/code&gt; (production-friendly)
&lt;/h1&gt;

&lt;p&gt;If you are building services the “modern .NET way”, you likely use &lt;code&gt;IHttpClientFactory&lt;/code&gt; already (named clients, typed clients, policies, resilience, handler lifetimes). PqcTracer supports that flow via an extension method:&lt;/p&gt;

&lt;p&gt;✅ &lt;code&gt;AddTlsTracing()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exact example code (as documented):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ConnectingApps.PqcTracer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Register the HttpClient via DI&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GoogleClient"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AddTlsTracing&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildServiceProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Resolve IHttpClientFactory from the service provider&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpClientFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IHttpClientFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Create the client using the factory&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpClientFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GoogleClient"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://www.google.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTlsTrace&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Negotiated Group: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Cipher Suite: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CipherSuite&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TLS Trace not found."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Error: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this is usually the right choice
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fits naturally into ASP.NET Core apps&lt;/li&gt;
&lt;li&gt;Works with named/typed clients&lt;/li&gt;
&lt;li&gt;Plays well with established &lt;code&gt;HttpClient&lt;/code&gt; lifetime management&lt;/li&gt;
&lt;li&gt;Easy to apply selectively (trace only specific clients)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Interpreting the output: what you’re actually looking for
&lt;/h2&gt;

&lt;p&gt;Example output shown in the README (on a system &lt;strong&gt;without&lt;/strong&gt; PQC-capable OpenSSL) is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Negotiated Group: x25519
Cipher Suite: TLS_AES_256_GCM_SHA384
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key line is the group:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;x25519&lt;/code&gt; → classical ECDH (not PQC)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X25519MLKEM768&lt;/code&gt; → hybrid classical + ML-KEM (PQC-oriented)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, with PqcTracer in place, you can programmatically (or operationally) identify which outbound connections are already negotiating hybrid/PQC groups and which are not.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this NuGet package is useful (beyond “cool demo output”)
&lt;/h2&gt;

&lt;p&gt;In real systems, PQC isn’t a switch you flip once. It’s a migration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some endpoints support hybrid groups, others don’t.&lt;/li&gt;
&lt;li&gt;Some environments have the right TLS stack, others lag behind.&lt;/li&gt;
&lt;li&gt;Third-party APIs may roll out support gradually.&lt;/li&gt;
&lt;li&gt;You need visibility &lt;strong&gt;per call path&lt;/strong&gt;, not just “we upgraded something”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;PqcTracer turns that uncertainty into data.&lt;/strong&gt; It enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PQC compliance auditing&lt;/strong&gt; across outbound API calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration tracking&lt;/strong&gt; over time (are we improving week over week?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security monitoring&lt;/strong&gt; (are critical calls still classical-only?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging&lt;/strong&gt; TLS negotiation without packet captures &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part is that it’s &lt;em&gt;application-integrated&lt;/em&gt;: you instrument your actual &lt;code&gt;HttpClient&lt;/code&gt; usage, not a synthetic test harness that drifts away from reality.&lt;/p&gt;




&lt;h2&gt;
  
  
  Platform note (important before you try it)
&lt;/h2&gt;

&lt;p&gt;PqcTracer currently targets &lt;strong&gt;Linux&lt;/strong&gt;, using OpenSSL 3.x via P/Invoke to extract negotiated TLS group details. &lt;/p&gt;

&lt;p&gt;Also, if you want to see &lt;strong&gt;ML-KEM&lt;/strong&gt; groups, you need a PQC-capable OpenSSL version (the README notes &lt;strong&gt;OpenSSL 3.5.0 or later&lt;/strong&gt; for ML-KEM support). &lt;/p&gt;




&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;If PQC is on your roadmap (or on your auditor’s checklist), you do not want to “assume TLS is fine.”&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;PqcTracer&lt;/strong&gt;, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;instrument outgoing &lt;code&gt;HttpClient&lt;/code&gt; traffic,&lt;/li&gt;
&lt;li&gt;extract the negotiated TLS &lt;strong&gt;group&lt;/strong&gt; and &lt;strong&gt;cipher suite&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;and identify which connections are already negotiating hybrid/PQC key exchange.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NuGet: &lt;a href="https://www.nuget.org/packages/ConnectingApps.PqcTracer" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/ConnectingApps.PqcTracer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/ConnectingApps/PqcTracer" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/PqcTracer&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then do the satisfying part: run your service, call your real dependencies, and &lt;em&gt;finally see&lt;/em&gt; what’s happening in your TLS handshakes.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>cybersecurity</category>
      <category>dotnet</category>
      <category>security</category>
    </item>
    <item>
      <title>Quantum-Safe TLS Is Not a Future Problem. It’s a Visibility Problem.</title>
      <dc:creator>Daan Acohen</dc:creator>
      <pubDate>Tue, 17 Feb 2026 15:57:53 +0000</pubDate>
      <link>https://dev.to/daan_acohen/quantum-safe-tls-is-not-a-future-problem-its-a-visibility-problem-2kp5</link>
      <guid>https://dev.to/daan_acohen/quantum-safe-tls-is-not-a-future-problem-its-a-visibility-problem-2kp5</guid>
      <description>&lt;p&gt;Most teams can answer “Do we use HTTPS?”&lt;br&gt;
Far fewer can answer “Are we already negotiating post-quantum key exchange in real traffic?”&lt;/p&gt;

&lt;p&gt;That gap matters.&lt;/p&gt;

&lt;p&gt;If an attacker records encrypted traffic today and breaks the key exchange years later with a cryptographically relevant quantum computer, sensitive historical data can be exposed. This is the “harvest now, decrypt later” risk model driving post-quantum migration efforts globally. NIST standardized ML-KEM for this transition in FIPS 203:&lt;br&gt;
&lt;a href="https://csrc.nist.gov/pubs/fips/203/final" rel="noopener noreferrer"&gt;https://csrc.nist.gov/pubs/fips/203/final&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For engineering teams, the first practical step is not a massive rewrite. It is observability.&lt;/p&gt;

&lt;p&gt;That is where &lt;strong&gt;ConnectingApps.PqcTracer&lt;/strong&gt; comes in:&lt;br&gt;
&lt;a href="https://github.com/ConnectingApps/PqcTracer" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/PqcTracer&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.nuget.org/packages/ConnectingApps.PqcTracer" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/ConnectingApps.PqcTracer&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What this NuGet package gives you
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ConnectingApps.PqcTracer&lt;/code&gt; lets your ASP.NET Core app trace TLS handshake details for incoming HTTPS requests and expose them as response headers.&lt;/p&gt;

&lt;p&gt;In plain terms, you can immediately see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which TLS key exchange group was negotiated&lt;/li&gt;
&lt;li&gt;Which cipher suite was negotiated&lt;/li&gt;
&lt;li&gt;Whether hybrid post-quantum groups such as &lt;code&gt;X25519MLKEM768&lt;/code&gt; are appearing in your request flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives platform and security teams an objective way to measure PQC adoption inside the company.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why this is suddenly actionable
&lt;/h2&gt;

&lt;p&gt;OpenSSL 3.5 introduced and enabled stronger PQC behavior in mainstream TLS usage, including hybrid ML-KEM groups such as &lt;code&gt;X25519MLKEM768&lt;/code&gt; in defaults/keyshares:&lt;br&gt;
&lt;a href="https://openssl-library.org/post/2025-04-08-openssl-35-final-release/" rel="noopener noreferrer"&gt;https://openssl-library.org/post/2025-04-08-openssl-35-final-release/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the question is no longer “Is PQC real yet?”&lt;br&gt;
The question is “Can we verify what our services are negotiating right now?”&lt;/p&gt;


&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package ConnectingApps.PqcTracer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;NuGet package page:&lt;br&gt;
&lt;a href="https://www.nuget.org/packages/ConnectingApps.PqcTracer" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/ConnectingApps.PqcTracer&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Use it in two lines of integration
&lt;/h2&gt;

&lt;p&gt;PqcTracer exposes two extension methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TraceTlsConnection()&lt;/code&gt; to capture TLS negotiation data during handshake in Kestrel&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UseTlsTraceHeaders()&lt;/code&gt; to emit those values in HTTP response headers&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Example &lt;code&gt;Program.cs&lt;/code&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ConnectingApps.PqcTracer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Add services to the container.&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebHost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TraceTlsConnection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenApi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseTlsTraceHeaders&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Configure the HTTP request pipeline.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapOpenApi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpsRedirection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapControllers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What you will see in responses
&lt;/h2&gt;

&lt;p&gt;After integration, responses can include headers like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X-Tls-Cipher: TLS_AES_256_GCM_SHA384
X-Tls-Group: X25519MLKEM768
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interpretation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;X-Tls-Group&lt;/code&gt; tells you the negotiated key exchange group.&lt;/li&gt;
&lt;li&gt;If it contains &lt;code&gt;MLKEM&lt;/code&gt;, your connection used a post-quantum-capable hybrid key exchange.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X-Tls-Cipher&lt;/code&gt; tells you the negotiated symmetric cipher suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a security program, this is high-value telemetry that can be aggregated immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical depth for medior/senior engineers
&lt;/h2&gt;

&lt;p&gt;Under the hood, PqcTracer follows a practical architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It hooks into Kestrel’s TLS authentication flow.&lt;/li&gt;
&lt;li&gt;After handshake, it inspects &lt;code&gt;SslStream&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It calls into OpenSSL (&lt;code&gt;libssl.so.3&lt;/code&gt;) via P/Invoke.&lt;/li&gt;
&lt;li&gt;It retrieves the negotiated group using OpenSSL control APIs (&lt;code&gt;SSL_ctrl&lt;/code&gt;) and resolves human-readable names (&lt;code&gt;SSL_group_to_name&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;It stores group/cipher values in connection context.&lt;/li&gt;
&lt;li&gt;Middleware writes those values into response headers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a smart design for production environments because it avoids packet-capture complexity and surfaces negotiation data directly where application teams can use it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Platform requirements you should know upfront
&lt;/h2&gt;

&lt;p&gt;From the project design and README behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current support is &lt;strong&gt;Linux only&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;It relies on OpenSSL via &lt;code&gt;libssl.so.3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For ML-KEM visibility/support, you need &lt;strong&gt;OpenSSL 3.5.0+&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Project link for details and updates:&lt;br&gt;
&lt;a href="https://github.com/ConnectingApps/PqcTracer" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/PqcTracer&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Where this helps immediately inside a company
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PQC readiness checks&lt;/strong&gt;: prove whether traffic already negotiates hybrid PQC groups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration dashboards&lt;/strong&gt;: track percentage of requests using ML-KEM-capable groups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client compatibility analysis&lt;/strong&gt;: identify legacy clients negotiating classical-only groups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit evidence&lt;/strong&gt;: provide concrete runtime proof for architecture and compliance discussions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Post-quantum migration is often treated as a strategic roadmap item.&lt;br&gt;
But roadmaps without runtime evidence are just assumptions.&lt;/p&gt;

&lt;p&gt;If your company wants a clear, engineering-grade view of whether PQC is already present in web requests, start with instrumentation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ConnectingApps.PqcTracer&lt;/code&gt; is a small integration that gives you exactly that visibility:&lt;br&gt;
&lt;a href="https://www.nuget.org/packages/ConnectingApps.PqcTracer" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/ConnectingApps.PqcTracer&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/ConnectingApps/PqcTracer" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/PqcTracer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also see:&lt;br&gt;
&lt;a href="https://github.com/ConnectingApps" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps&lt;/a&gt;&lt;br&gt;
&lt;a href="https://quantumsafeaudit.com" rel="noopener noreferrer"&gt;https://quantumsafeaudit.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tls</category>
      <category>webdev</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Post-Quantum TLS in .NET 10 on Linux: ML-KEM in a Real Dockerized HTTP Client</title>
      <dc:creator>Daan Acohen</dc:creator>
      <pubDate>Sat, 14 Feb 2026 21:53:15 +0000</pubDate>
      <link>https://dev.to/daan_acohen/post-quantum-tls-in-net-10-on-linux-ml-kem-in-a-real-dockerized-http-client-2lnl</link>
      <guid>https://dev.to/daan_acohen/post-quantum-tls-in-net-10-on-linux-ml-kem-in-a-real-dockerized-http-client-2lnl</guid>
      <description>&lt;p&gt;Quantum-safe cryptography is moving from “future topic” to “engineering backlog item.”&lt;br&gt;
The reason is simple: encrypted traffic captured today may be decrypted in the future once sufficiently capable quantum systems exist. If your data needs long-term confidentiality, this risk is relevant now.&lt;/p&gt;

&lt;p&gt;A practical migration path is &lt;strong&gt;hybrid TLS key exchange&lt;/strong&gt;: combine a classical key exchange (for interoperability) with a post-quantum one (for quantum resistance). In the .NET ecosystem, this becomes especially interesting with .NET 10 and ML-KEM support.&lt;/p&gt;

&lt;p&gt;This article walks through the &lt;strong&gt;PQCHttpClient&lt;/strong&gt; demo repo and explains exactly how it proves post-quantum hybrid TLS negotiation from a .NET app on Linux containers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub repo: &lt;a href="https://github.com/ConnectingApps/PQCHttpClient" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/PQCHttpClient&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;.NET 10 libraries (PQC APIs): &lt;a href="https://learn.microsoft.com/dotnet/core/whats-new/dotnet-10/libraries" rel="noopener noreferrer"&gt;https://learn.microsoft.com/dotnet/core/whats-new/dotnet-10/libraries&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenSSL TLS groups (including hybrid ML-KEM groups): &lt;a href="https://docs.openssl.org/3.6/man3/SSL_CTX_set1_curves/" rel="noopener noreferrer"&gt;https://docs.openssl.org/3.6/man3/SSL_CTX_set1_curves/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why this matters for .NET developers
&lt;/h2&gt;

&lt;p&gt;If you run .NET workloads on Linux (VMs, containers, Kubernetes), your TLS and crypto behavior depends on native platform libraries. For ML-KEM scenarios, that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET 10 gives you the managed API surface (&lt;code&gt;System.Security.Cryptography.MLKem&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Linux runtime support depends on the OpenSSL version actually loaded at runtime&lt;/li&gt;
&lt;li&gt;OpenSSL 3.5+ is the key enabler for ML-KEM TLS group support in this context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this is not just about targeting &lt;code&gt;net10.0&lt;/code&gt;; it is also about deterministic runtime linking.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the demo proves
&lt;/h2&gt;

&lt;p&gt;The project demonstrates three concrete things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Capability detection&lt;/strong&gt;: &lt;code&gt;MLKem.IsSupported&lt;/code&gt; reports whether ML-KEM is truly available in the runtime environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real HTTPS traffic&lt;/strong&gt;: standard &lt;code&gt;HttpClient&lt;/code&gt; call, no custom handshake code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negotiation evidence&lt;/strong&gt;: response header output shows which key exchange group was used.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This makes it a strong proof-of-concept for teams evaluating PQC readiness in production-like container environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Program.cs (why it is effective)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// HttpClient GET request demonstration&lt;/span&gt;
&lt;span class="c1"&gt;// Performs a GET request to GitHub API and displays status code and response headers&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"ML-KEM supported: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cryptography&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MLKem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSupported&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Set User-Agent header (required by GitHub API)&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultRequestHeaders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pqcheader-console-app"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Execute GET request&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sending GET request to https://www.quantumsafeaudit.com.."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://www.quantumsafeaudit.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Ensure the request was successful&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Display status code&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Status Code: &lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Display response headers&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Response Headers:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Also display content headers if present&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content Headers:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Error making HTTP request: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Unexpected error: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key takeaways for readers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MLKem.IsSupported&lt;/code&gt; is a &lt;strong&gt;runtime truth signal&lt;/strong&gt;, not a compile-time guarantee.&lt;/li&gt;
&lt;li&gt;The app uses ordinary &lt;code&gt;HttpClient&lt;/code&gt;; PQ/hybrid negotiation happens in the underlying TLS stack.&lt;/li&gt;
&lt;li&gt;Showing response headers gives concrete handshake observability (important in demos and audits).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Dockerfile (the critical engineering part)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="c"&gt;# Stage 1: Build OpenSSL 3.5 from source&lt;/span&gt;
&lt;span class="c"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ubuntu:24.04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;openssl-builder&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    build-essential &lt;span class="se"&gt;\
&lt;/span&gt;    ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="se"&gt;\
&lt;/span&gt;    perl &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; OPENSSL_VERSION=3.5.0&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://github.com/openssl/openssl/releases/download/openssl-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPENSSL_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/openssl-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPENSSL_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.tar.gz &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/openssl.tar.gz &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; /tmp/openssl.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /tmp

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /tmp/openssl-${OPENSSL_VERSION}&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;./Configure &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/openssl-3.5 &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--openssldir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/openssl-3.5/ssl &lt;span class="se"&gt;\
&lt;/span&gt;    linux-x86_64 &lt;span class="se"&gt;\
&lt;/span&gt;    shared &lt;span class="se"&gt;\
&lt;/span&gt;    no-tests &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class="nt"&gt;-j&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make install_sw install_ssldirs

&lt;span class="c"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="c"&gt;# Stage 2: .NET 10 SDK with custom OpenSSL — build the app&lt;/span&gt;
&lt;span class="c"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:10.0-preview-noble&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="c"&gt;# Copy OpenSSL 3.5 from builder stage&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=openssl-builder /opt/openssl-3.5 /opt/openssl-3.5&lt;/span&gt;

&lt;span class="c"&gt;# Make the system use our OpenSSL 3.5&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; LD_LIBRARY_PATH=/opt/openssl-3.5/lib64:/opt/openssl-3.5/lib&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/opt/openssl-3.5/bin:${PATH}"&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;

&lt;span class="c"&gt;# Copy project file first for layer caching&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pqcheader.csproj ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore

&lt;span class="c"&gt;# Copy source and build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Program.cs ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; /app &lt;span class="nt"&gt;--no-restore&lt;/span&gt;

&lt;span class="c"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="c"&gt;# Stage 3: Runtime image with custom OpenSSL&lt;/span&gt;
&lt;span class="c"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/runtime:10.0-preview-noble&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;

&lt;span class="c"&gt;# Install minimal runtime dependencies for OpenSSL&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    ca-certificates &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="c"&gt;# Copy OpenSSL 3.5 libraries&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=openssl-builder /opt/openssl-3.5 /opt/openssl-3.5&lt;/span&gt;

&lt;span class="c"&gt;# Configure dynamic linker to find OpenSSL 3.5 FIRST (before system OpenSSL)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"/opt/openssl-3.5/lib64"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/ld.so.conf.d/openssl-3.5.conf &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"/opt/openssl-3.5/lib"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/ld.so.conf.d/openssl-3.5.conf &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ldconfig

&lt;span class="c"&gt;# Also set LD_LIBRARY_PATH as belt-and-suspenders&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; LD_LIBRARY_PATH=/opt/openssl-3.5/lib64:/opt/openssl-3.5/lib&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; SSL_CERT_DIR=/etc/ssl/certs&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app .&lt;/span&gt;

&lt;span class="c"&gt;# Verify OpenSSL version at build time&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;/opt/openssl-3.5/bin/openssl version

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "pqcheader.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What makes this production-relevant
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Version control over crypto backend&lt;/strong&gt;: avoids silently using distro-default OpenSSL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolated OpenSSL prefix&lt;/strong&gt;: explicit, auditable dependency placement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic loader behavior&lt;/strong&gt;: &lt;code&gt;ld.so.conf.d&lt;/code&gt; + &lt;code&gt;ldconfig&lt;/code&gt; + &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build-time validation&lt;/strong&gt;: &lt;code&gt;openssl version&lt;/code&gt; check inside the image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For security-sensitive software, this is exactly the pattern you want: deterministic cryptography behavior across environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to run
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; pqcheader-mlkem &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; pqcheader-mlkem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ML-KEM supported: True&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;successful HTTP status&lt;/li&gt;
&lt;li&gt;response headers including negotiated key exchange group details&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Practical interpretation of results
&lt;/h2&gt;

&lt;p&gt;When you see &lt;code&gt;ML-KEM supported: True&lt;/code&gt;, it means your runtime stack actually supports ML-KEM in that environment.&lt;br&gt;
When the server indicates a hybrid group such as &lt;code&gt;X25519MLKEM768&lt;/code&gt;, it means the connection negotiated a hybrid classical+PQC key exchange.&lt;/p&gt;

&lt;p&gt;That distinction is important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capability&lt;/strong&gt; (&lt;code&gt;IsSupported&lt;/code&gt;) tells you what the client can do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negotiation result&lt;/strong&gt; tells you what this specific TLS session actually did.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;For .NET teams, this project is a strong template for early PQC adoption:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no app-layer TLS customization required,&lt;/li&gt;
&lt;li&gt;clear runtime capability checks,&lt;/li&gt;
&lt;li&gt;explicit Linux/OpenSSL dependency management in Docker,&lt;/li&gt;
&lt;li&gt;observable handshake outcome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your domain has long-lived confidentiality requirements, this is an excellent starting point to move from theory to implementation.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>docker</category>
      <category>security</category>
    </item>
    <item>
      <title>Alpine-Like Container Security, Debian-Like Compatibility: Why I Picked Chiseled for .NET</title>
      <dc:creator>Daan Acohen</dc:creator>
      <pubDate>Thu, 12 Feb 2026 16:31:00 +0000</pubDate>
      <link>https://dev.to/daan_acohen/alpine-like-container-security-debian-like-compatibility-why-i-picked-chiseled-for-net-4aap</link>
      <guid>https://dev.to/daan_acohen/alpine-like-container-security-debian-like-compatibility-why-i-picked-chiseled-for-net-4aap</guid>
      <description>&lt;p&gt;If you build .NET services in containers, you’ve likely heard the default advice: pick the smallest image.&lt;/p&gt;

&lt;p&gt;For production workloads, that rule is incomplete. In practice, runtime image selection is a trade-off between native compatibility, security posture, and image footprint. To make that trade-off concrete, I built a focused benchmark comparing Debian, Alpine, and Ubuntu Chiseled in a native-library scenario: &lt;a href="https://github.com/ConnectingApps/DockerImageSizes" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/DockerImageSizes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For official context on Chiseled containers in .NET, see Microsoft’s announcement: &lt;a href="https://devblogs.microsoft.com/dotnet/announcing-dotnet-chiseled-containers/" rel="noopener noreferrer"&gt;https://devblogs.microsoft.com/dotnet/announcing-dotnet-chiseled-containers/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this test is useful for real .NET workloads
&lt;/h2&gt;

&lt;p&gt;A managed-only sample often hides the real problems. Many .NET services eventually rely on native Linux libraries, either directly through P/Invoke or indirectly through dependencies.&lt;/p&gt;

&lt;p&gt;So this benchmark uses a .NET 8 app that calls &lt;code&gt;libuuid.so.1&lt;/code&gt; from C#. That creates a clear runtime compatibility signal in each base image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Runtime.InteropServices&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"libuuid.so.1"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;uuid_generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Generating UUID using native glibc library..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nf"&gt;uuid_generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Success."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Exception:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Minimal project file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net8.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker image variants compared
&lt;/h2&gt;

&lt;p&gt;The benchmark compares these runtime bases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debian: &lt;code&gt;mcr.microsoft.com/dotnet/runtime:8.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Alpine: &lt;code&gt;mcr.microsoft.com/dotnet/runtime:8.0-alpine&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ubuntu Chiseled: &lt;code&gt;mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Debian Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ===== Build stage =====&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:8.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; NativeDemo.csproj .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Program.cs .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; /app

&lt;span class="c"&gt;# ===== Runtime stage =====&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/runtime:8.0&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; libuuid1

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app .&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "NativeDemo.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Alpine Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ===== Build stage =====&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:8.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; NativeDemo.csproj .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Program.cs .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; /app

&lt;span class="c"&gt;# ===== Runtime stage =====&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/runtime:8.0-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app .&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "NativeDemo.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ubuntu Chiseled Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ===== Build stage (.NET) =====&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:8.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; NativeDemo.csproj .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Program.cs .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; /app

&lt;span class="c"&gt;# ===== Extract libuuid from Ubuntu =====&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ubuntu:22.04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;uuidstage&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; libuuid1

&lt;span class="c"&gt;# ===== Runtime stage =====&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app .&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=uuidstage /usr/lib/x86_64-linux-gnu/libuuid.so.1 /usr/lib/&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "NativeDemo.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reproduction commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.debian &lt;span class="nt"&gt;-t&lt;/span&gt; demo-debian &lt;span class="nb"&gt;.&lt;/span&gt;
docker run demo-debian

docker build &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.alpine &lt;span class="nt"&gt;-t&lt;/span&gt; demo-alpine &lt;span class="nb"&gt;.&lt;/span&gt;
docker run demo-alpine

docker build &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile.chiseled &lt;span class="nt"&gt;-t&lt;/span&gt; demo-chiseled &lt;span class="nb"&gt;.&lt;/span&gt;
docker run demo-chiseled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional vulnerability scans:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;trivy image demo-debian
trivy image demo-alpine
trivy image demo-chiseled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Measured results
&lt;/h2&gt;

&lt;p&gt;These values are from this repository’s benchmark measurement and can change over time as upstream base images are updated.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variant&lt;/th&gt;
&lt;th&gt;Runtime base image&lt;/th&gt;
&lt;th&gt;Measured size&lt;/th&gt;
&lt;th&gt;Trivy summary (measured)&lt;/th&gt;
&lt;th&gt;Native &lt;code&gt;libuuid.so.1&lt;/code&gt; call&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Debian&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mcr.microsoft.com/dotnet/runtime:8.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;213 MB&lt;/td&gt;
&lt;td&gt;87 CVEs (1 critical, 2 high, 24 medium, 60 low)&lt;/td&gt;
&lt;td&gt;✅ Works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alpine&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mcr.microsoft.com/dotnet/runtime:8.0-alpine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;84 MB&lt;/td&gt;
&lt;td&gt;0 CVEs&lt;/td&gt;
&lt;td&gt;❌ Fails (&lt;code&gt;DllNotFoundException&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu Chiseled&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;85.5 MB&lt;/td&gt;
&lt;td&gt;3 CVEs (all low)&lt;/td&gt;
&lt;td&gt;✅ Works&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Interpretation for .NET developers
&lt;/h2&gt;

&lt;p&gt;The key point is not that one distro is always “best.” The key point is that measurable trade-offs appear immediately once native dependencies are involved.&lt;/p&gt;

&lt;p&gt;In this benchmark:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alpine is smallest and cleanest on the reported scan, but fails the tested glibc-native call.&lt;/li&gt;
&lt;li&gt;Debian works, but has substantially larger footprint and higher vulnerability count in this measurement.&lt;/li&gt;
&lt;li&gt;Ubuntu Chiseled is near Alpine in size while still passing the tested native call, with low reported CVE count.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination is why Chiseled often looks attractive for production .NET services that need both lean images and practical native compatibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical decision model
&lt;/h2&gt;

&lt;p&gt;A pragmatic policy for teams:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with Ubuntu Chiseled for production services.&lt;/li&gt;
&lt;li&gt;Use Alpine when musl compatibility is proven for your full dependency chain.&lt;/li&gt;
&lt;li&gt;Use Debian when you intentionally prioritize convenience/broad compatibility over minimal footprint.&lt;/li&gt;
&lt;li&gt;Always validate native behavior inside the exact runtime image you ship.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That keeps the decision engineering-driven and reproducible.&lt;/p&gt;

&lt;p&gt;Source material:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ConnectingApps/DockerImageSizes" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/DockerImageSizes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devblogs.microsoft.com/dotnet/announcing-dotnet-chiseled-containers/" rel="noopener noreferrer"&gt;https://devblogs.microsoft.com/dotnet/announcing-dotnet-chiseled-containers/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>dotnet</category>
      <category>security</category>
    </item>
    <item>
      <title>TUnit + Dependency Injection in .NET Tests: One Registration, Zero Boilerplate</title>
      <dc:creator>Daan Acohen</dc:creator>
      <pubDate>Sun, 08 Feb 2026 14:42:11 +0000</pubDate>
      <link>https://dev.to/daan_acohen/tunit-dependency-injection-in-net-tests-one-registration-zero-boilerplate-22p3</link>
      <guid>https://dev.to/daan_acohen/tunit-dependency-injection-in-net-tests-one-registration-zero-boilerplate-22p3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If your production app uses dependency injection, your tests should too.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;TUnit&lt;/strong&gt;, you can inject dependencies directly into your test class in a clean, first-class way using &lt;code&gt;DependencyInjectionDataSourceAttribute&amp;lt;TScope&amp;gt;&lt;/code&gt;. TUnit’s &lt;a href="https://tunit.dev/docs/test-lifecycle/test-context" rel="noopener noreferrer"&gt;own docs&lt;/a&gt; explicitly recommend this approach for user-provided services. &lt;/p&gt;

&lt;p&gt;👉 Example repository used in this article:&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/ConnectingApps/TUnitPublicDemo" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/TUnitPublicDemo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The real problem
&lt;/h2&gt;

&lt;p&gt;Many .NET teams do this well in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;register services in &lt;code&gt;IServiceCollection&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;resolve dependencies via constructor injection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But tests often drift into ad-hoc setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;repeated &lt;code&gt;new&lt;/code&gt; calls&lt;/li&gt;
&lt;li&gt;test-specific composition roots&lt;/li&gt;
&lt;li&gt;fixture patterns that diverge from runtime wiring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That drift increases maintenance cost and weakens refactor safety.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why TUnit is interesting here
&lt;/h2&gt;

&lt;p&gt;TUnit provides a &lt;a href="https://tunit.dev/docs/test-lifecycle/test-context" rel="noopener noreferrer"&gt;dedicated DI data source pattern&lt;/a&gt; for test dependencies (&lt;code&gt;DependencyInjectionDataSourceAttribute&amp;lt;TScope&amp;gt;&lt;/code&gt;), instead of hiding everything behind framework internals. &lt;/p&gt;

&lt;p&gt;Also useful context: &lt;a href="https://github.com/thomhurst/TUnit" rel="noopener noreferrer"&gt;TUnit positions itself as a modern .NET testing framework with source generation and fast execution characteristics&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Main pattern (from the repo)
&lt;/h2&gt;

&lt;p&gt;The repository demonstrates one simple architectural rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Use the same service registration extension in production and in tests.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  1) Put registrations in one extension method
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DependencySetup&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="nf"&gt;AddGravity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IGravityConfiguration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GravityConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IGravityCalculator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GravityCalculator&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2) Use it in production (&lt;code&gt;Program.cs&lt;/code&gt;)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddGravity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3) Use it in tests via a TUnit DI attribute
&lt;/h3&gt;

&lt;p&gt;Create a custom attribute inheriting from &lt;code&gt;DependencyInjectionDataSourceAttribute&amp;lt;TScope&amp;gt;&lt;/code&gt;, build your &lt;code&gt;ServiceProvider&lt;/code&gt;, and resolve constructor parameters from there.&lt;/p&gt;
&lt;h3&gt;
  
  
  4) Inject dependencies directly into the test class
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GravityDI&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GravityCalculatorTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IGravityCalculator&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Planet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Earth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;980.7&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Planet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Moon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;162.0&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Planet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;371.0&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;CalculateForce_ReturnsCorrectForce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;mass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Planet&lt;/span&gt; &lt;span class="n"&gt;planet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateForce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;planet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;IsEqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Result: tests consume dependencies exactly like production code.&lt;/p&gt;


&lt;h2&gt;
  
  
  Comparison with other popular test frameworks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;xUnit&lt;/strong&gt;: &lt;a href="https://github.com/Umplify/xunit-dependency-injection" rel="noopener noreferrer"&gt;DI is commonly achieved via external libraries and fixture-based patterns; xUnit itself does not natively provide a general-purpose test-class DI container model in the same way this TUnit pattern does&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NUnit&lt;/strong&gt;: &lt;a href="https://github.com/kalebpederson/nunit.dependencyinjection" rel="noopener noreferrer"&gt;DI is generally extension-driven&lt;/a&gt; (e.g., &lt;code&gt;nunit.dependencyinjection&lt;/code&gt; / related packages), not a built-in, single canonical DI path for test class construction. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSTest&lt;/strong&gt;: &lt;a href="https://github.com/LopezMDidac/mstest-di" rel="noopener noreferrer"&gt;DI patterns are commonly custom or sample-based rather than framework-native conventions.&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly why the TUnit approach in your repo feels elegant: fewer moving parts, clear composition root, and constructor-injected tests.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why this pattern scales
&lt;/h2&gt;

&lt;p&gt;When app and tests share a single registration method:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Refactors are safer&lt;/strong&gt; (service graph changes once)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less setup duplication&lt;/strong&gt; (lower maintenance)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner tests&lt;/strong&gt; (behavior-focused, not wiring-focused)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is especially valuable in medium/large .NET solutions where DI registrations evolve frequently.&lt;/p&gt;


&lt;h2&gt;
  
  
  Try it quickly
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ConnectingApps/TUnitPublicDemo.git
&lt;span class="nb"&gt;cd &lt;/span&gt;TUnitPublicDemo
dotnet build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;Gravity.Test
dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;Gravity
dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;If constructor injection is your production default, this TUnit model gives you the same ergonomics in tests—with minimal ceremony and better symmetry across the codebase.&lt;/p&gt;

&lt;p&gt;If you want a concrete reference implementation, start here:&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/ConnectingApps/TUnitPublicDemo" rel="noopener noreferrer"&gt;https://github.com/ConnectingApps/TUnitPublicDemo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>unittest</category>
    </item>
  </channel>
</rss>
