<?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: Yash Kumar Saini</title>
    <description>The latest articles on DEV Community by Yash Kumar Saini (@yashksaini).</description>
    <link>https://dev.to/yashksaini</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1242333%2F27bdd497-8c93-4290-a901-94eec3759056.jpeg</url>
      <title>DEV Community: Yash Kumar Saini</title>
      <link>https://dev.to/yashksaini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yashksaini"/>
    <language>en</language>
    <item>
      <title>Dev log #15 Refining the Craft: A week of Neovim UI hacking and portfolio architecture</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Thu, 06 Aug 2026 04:18:06 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-15-refining-the-craft-a-week-of-neovim-ui-hacking-and-portfolio-architecture-45a2</link>
      <guid>https://dev.to/yashksaini/dev-log-15-refining-the-craft-a-week-of-neovim-ui-hacking-and-portfolio-architecture-45a2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Balanced the scales this week with a near-perfect 1:1 ratio of additions to deletions. Focused on stripping back my Neovim UI and restructuring how I showcase my career history—33 commits and a 5-day streak of pure flow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;This week was all about the "one for me, one for the world" balance. I spent a significant chunk of time deep in my Neovim configuration, ripping out UI components that didn't spark joy, while simultaneously overhauling my portfolio's experience section. With 3,162 additions and 3,141 deletions, the codebase effectively stayed the same size but feels twice as fast. It’s that rare kind of week where the net line count is almost zero, but the functional improvement is massive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  nvim (The Editor Overhaul)
&lt;/h3&gt;

&lt;p&gt;I’m a firm believer that your editor is a living organism. If you aren't tweaking it, you aren't learning. This week, I went on a bit of a minimalist crusade in my &lt;code&gt;nvim&lt;/code&gt; config. I made 23 commits across 60 files, mostly centered around &lt;code&gt;lua/plugins&lt;/code&gt; and &lt;code&gt;lua/config&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The biggest move was swapping out &lt;code&gt;mini.statusline&lt;/code&gt; for &lt;code&gt;lualine&lt;/code&gt;. Don't get me wrong, the &lt;code&gt;mini&lt;/code&gt; ecosystem is incredible, but I found myself wanting the specific modularity that &lt;code&gt;lualine&lt;/code&gt; offers for my current workflow. Along with that, I decided to drop &lt;code&gt;barbecue&lt;/code&gt; and implement a custom &lt;code&gt;winbar&lt;/code&gt;. It’s cleaner, less distracting, and gives me exactly the context I need without the visual noise.&lt;/p&gt;

&lt;p&gt;I also tackled a bug that has been driving me absolutely wild: my &lt;code&gt;&amp;lt;leader&amp;gt;bd&lt;/code&gt; (buffer delete) mapping. For a while there, it had a nasty habit of taking the whole editor down instead of just closing the current buffer. There’s nothing quite like being in the middle of a deep logic flow, trying to clean up your tabs, and accidentally nuking your entire session. I finally sat down and fixed the logic so it correctly handles the last buffer—now, it re-opens the dashboard instead of exiting. Speaking of the dashboard, I added a "Recent Files" row using &lt;code&gt;snacks&lt;/code&gt;, which has already saved me a dozen &lt;code&gt;telescope&lt;/code&gt; lookups today.&lt;/p&gt;

&lt;p&gt;On the maintenance side, I tweaked my CI. I used to have a GitHub Action running daily to update all my plugins, but it was getting noisy. I shifted that to a weekly Sunday 00:00 IST schedule. It keeps things fresh without cluttering my notification feed every single morning.&lt;/p&gt;

&lt;h3&gt;
  
  
  portfolio (Restructuring the Narrative)
&lt;/h3&gt;

&lt;p&gt;While the Neovim work was about personal productivity, the work on my &lt;code&gt;portfolio&lt;/code&gt; repo was about how I present my work to the world. I pushed 10 commits here, but they were "heavy" commits—over 2,500 lines added and 1,200 removed.&lt;/p&gt;

&lt;p&gt;The core of this work was a total refactor of the experience section. Previously, my roles were just a flat list, which doesn't really capture the story of growing within a company. I opened and merged a PR to group roles under single company cards. It sounds like a small UI change, but the data restructuring in &lt;code&gt;lib&lt;/code&gt; and &lt;code&gt;data&lt;/code&gt; to make this happen cleanly was a fun puzzle. I also made sure to render the "company narrative" on the home section to give visitors more context about the impact I had at each stop.&lt;/p&gt;

&lt;p&gt;I also spent some time in &lt;code&gt;components/common&lt;/code&gt; and &lt;code&gt;components/sections&lt;/code&gt; just obsessing over the layout. I refactored the layout logic to align every section rule to the content box. There’s a specific kind of satisfaction that comes from seeing your CSS grid and TypeScript props align perfectly across different screen sizes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests
&lt;/h2&gt;

&lt;p&gt;I managed to get one significant PR through the pipeline this week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;a href="https://github.com/yashksaini-coder/portfolio/pull/14" rel="noopener noreferrer"&gt;feat(experience): group roles under one company card&lt;/a&gt;&lt;/strong&gt;: This was the heart of my portfolio work. It involved 685 additions and 378 deletions. Beyond just the UI, I had to update the TypeScript interfaces to support nested role structures and ensure the rendering logic didn't break for older, single-role entries. It’s merged and live now, and the "Experience" tab looks significantly more professional.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech Stack &amp;amp; Stats
&lt;/h2&gt;

&lt;p&gt;It was a classic polyglot week, though the commit activity was concentrated in &lt;strong&gt;Lua&lt;/strong&gt; (for the editor) and &lt;strong&gt;TypeScript&lt;/strong&gt; (for the portfolio). &lt;/p&gt;

&lt;p&gt;Looking at the broader stats, I'm currently sitting on a massive pile of code across different languages—everything from a heavy &lt;strong&gt;Python&lt;/strong&gt; and &lt;strong&gt;Rust&lt;/strong&gt; backbone to &lt;strong&gt;Go&lt;/strong&gt;, &lt;strong&gt;C#&lt;/strong&gt;, and even some &lt;strong&gt;Solidity&lt;/strong&gt; and &lt;strong&gt;Nix&lt;/strong&gt; tucked away in various repos. It’s a reminder that as a freelancer, you never really stop adding tools to the belt.&lt;/p&gt;

&lt;p&gt;The most telling stat this week is the &lt;strong&gt;Add/Delete ratio&lt;/strong&gt;. 3,162 up, 3,141 down. That is the definition of a refactoring week. I wasn't just piling new features on top of a shaky foundation; I was digging out the old stuff and replacing it with something better. &lt;/p&gt;

&lt;p&gt;I also hit a &lt;strong&gt;5-day commit streak&lt;/strong&gt;. I don't usually game the green squares, but when you're in the middle of a refactor, it’s hard to stay away for more than 24 hours. The momentum just carries you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;With the Neovim UI finally feeling "quiet" and the portfolio restructuring out of the way, I’m looking to dive back into some deeper backend work next week. I’ve got some ideas for CLI tools that have been rattling around my head while I was tweaking my &lt;code&gt;lua&lt;/code&gt; configs. &lt;/p&gt;

&lt;p&gt;The goal for next week? Keep the deletion energy high. If I can ship features while keeping the total line count stable, I’m winning. See you in the next update!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Generated by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>weeklyui</category>
      <category>ui</category>
    </item>
    <item>
      <title>Dev log #14 Hardening the DHT against Eclipse attacks and the endless battle with flaky p2p tests</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sat, 01 Aug 2026 04:20:17 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-14-hardening-the-dht-against-eclipse-attacks-and-the-endless-battle-with-flaky-p2p-tests-593j</link>
      <guid>https://dev.to/yashksaini/dev-log-14-hardening-the-dht-against-eclipse-attacks-and-the-endless-battle-with-flaky-p2p-tests-593j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Dived deep into libp2p security this week with a new IP subnet diversity feature for the Kad-DHT, while fighting a few stubborn race conditions in the test suite. 3 PRs open and some automated maintenance on my Neovim setup kept the gears turning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Security hardening took center stage this week as I focused on making our Kademlia DHT implementation more resilient against Eclipse attacks. Between drafting logic for IP subnet diversity and chasing down race conditions in the &lt;code&gt;py-libp2p&lt;/code&gt; test suite, it was a week of high-leverage architectural thinking paired with the granular frustration of de-flaking async code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fight for a More Secure DHT
&lt;/h2&gt;

&lt;p&gt;Most of my brainpower this week went into &lt;code&gt;py-libp2p&lt;/code&gt;, specifically around how we handle peer selection in the Kademlia DHT. I opened a significant PR, &lt;a href="https://github.com/libp2p/py-libp2p/pull/1399" rel="noopener noreferrer"&gt;feat(kad_dht): enforce IP subnet diversity in k-buckets (#1383)&lt;/a&gt;, which adds 226 lines of new logic to ensure our routing table doesn't get saturated by peers from the same network neighborhood.&lt;/p&gt;

&lt;p&gt;If you're not deep in the p2p weeds, here's the deal: an Eclipse attack happens when an attacker manages to surround a target node with their own malicious nodes. If all your "neighbors" in the network are controlled by one person, they can effectively cut you off from the real network or feed you fake data. By enforcing IP subnet diversity, we make it much harder (and more expensive) for an attacker to pull this off because they can't just spin up a thousand nodes on a single cloud provider's subnet and expect to fill up our k-buckets. &lt;/p&gt;

&lt;p&gt;It’s one of those features that feels invisible until it’s not there, but it’s critical for the long-term health of the library.&lt;/p&gt;

&lt;p&gt;I also kicked off a deep-dive discussion titled &lt;a href="https://github.com/libp2p/py-libp2p/discussions/1400" rel="noopener noreferrer"&gt;Eclipse-attack hardening for kad-dht: our approach, and why we dropped randomized lookup selection (#1383 / #1384)&lt;/a&gt;. We had originally looked at randomized lookup selection as a defense mechanism, but after some back-and-forth, we decided to drop it in favor of more robust bucket management. It’s always better to realize an approach is suboptimal &lt;em&gt;before&lt;/em&gt; you merge the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tax of Asynchronous Testing
&lt;/h2&gt;

&lt;p&gt;When you're working on a library that relies heavily on asynchronous networking, you eventually have to pay the "flaky test tax." This week, I spent a good chunk of time hunting down two particularly annoying race conditions that have been haunting our CI.&lt;/p&gt;

&lt;p&gt;The first was &lt;a href="https://github.com/libp2p/py-libp2p/pull/1409" rel="noopener noreferrer"&gt;test(timed_cache): de-flake test_expiry_removal (#1408)&lt;/a&gt;. The issue was a classic: the test was racing against a background sweeper thread. We were using &lt;code&gt;trio.sleep&lt;/code&gt;, which is usually fine, but in a CI environment where CPU cycles can be unpredictable, the sweeper wasn't always finishing its cleanup before the test checked the results. I also opened an issue to track this specifically: &lt;a href="https://github.com/libp2p/py-libp2p/issues/1408" rel="noopener noreferrer"&gt;flaky: test_expiry_removal races the background sweeper thread&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The second one was &lt;a href="https://github.com/libp2p/py-libp2p/pull/1402" rel="noopener noreferrer"&gt;test(pubsub): de-flake test_gossip_gate_filters_peers (#1401)&lt;/a&gt;. Similar story here—subscription propagation in Gossipsub is inherently timing-dependent. If the test moves faster than the network (even a mocked one), everything breaks. Fixing these isn't the most glamorous work, but there is a specific kind of satisfaction in seeing a red CI pipeline finally turn green after you've tightened up the timing logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintenance and Tooling
&lt;/h2&gt;

&lt;p&gt;On the side, I kept my environment healthy. My &lt;a href="https://github.com/yashksaini-coder/nvim" rel="noopener noreferrer"&gt;nvim&lt;/a&gt; config saw 6 commits this week, though they were mostly automated chores. I have a CI setup that keeps all my plugins updated to the latest versions. It’s a "set it and forget it" kind of thing, but it ensures that when I sit down to code, my editor is actually ready for me. &lt;/p&gt;

&lt;p&gt;With 7 additions and 7 deletions across those 6 commits, it was a perfectly balanced week of maintenance. No new bloat, just keeping the tools sharp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack &amp;amp; Vibe
&lt;/h2&gt;

&lt;p&gt;This week was heavily skewed toward Python, given the work on &lt;code&gt;py-libp2p&lt;/code&gt;. With over 96MB of Python code in the mix (thanks to the sheer scale of the libp2p ecosystem), it’s definitely my primary focus right now. But looking at my language stats, the polyglot life is still very much real—TypeScript, Rust, and Lua are all sitting there in the background, ready for when I pivot back to frontend or systems work.&lt;/p&gt;

&lt;p&gt;The add/delete ratio was interestingly tight this week (7 up, 7 down in the repo I committed to directly). It reflects a week of refinement rather than raw feature expansion. Even the libp2p PRs, while adding new logic, were very focused on fixing specific behaviors rather than just piling on more code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I have three PRs currently sitting in "OPEN" status on &lt;code&gt;py-libp2p&lt;/code&gt;. My priority for next week is getting those merged. The IP subnet diversity work is the big one—I want to make sure the implementation is bulletproof before it hits main. Once those are in, I’m hoping to get back to some net-new feature work, but as anyone in OSS knows, the next flaky test is probably just around the corner.&lt;/p&gt;

&lt;p&gt;Catch you in the next update. Keep shipping.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Generated by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>opensource</category>
      <category>networking</category>
      <category>python</category>
    </item>
    <item>
      <title>Dev log #13 Hardening Kademlia: A week of p2p security architecture and Neovim maintenance</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Thu, 23 Jul 2026 04:06:15 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-1-hardening-kademlia-a-week-of-p2p-security-architecture-and-neovim-maintenance-12jo</link>
      <guid>https://dev.to/yashksaini/dev-log-1-hardening-kademlia-a-week-of-p2p-security-architecture-and-neovim-maintenance-12jo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Spent the week laying the groundwork for major p2p security upgrades while keeping my dev environment sharp. 9 commits and a 7-day streak, but the real work was in the three security-focused issues and a deep-dive discussion on DHT hardening.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Sometimes the most productive weeks are the ones where you don't actually merge a single PR. This week was all about the long game—specifically, hardening the Kademlia DHT in &lt;code&gt;py-libp2p&lt;/code&gt; against eclipse attacks. I pushed 9 commits across two repos, maintained a perfect 7-day streak, and opened three critical security issues that I’m itching to start coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT I BUILT
&lt;/h2&gt;

&lt;p&gt;While I didn't close out any massive features this week, I spent a lot of time in the guts of &lt;code&gt;py-libp2p&lt;/code&gt;. If you've ever worked on p2p networking, you know that the "boring" maintenance work is actually what keeps the whole house from falling down.&lt;/p&gt;

&lt;h3&gt;
  
  
  py-libp2p
&lt;/h3&gt;

&lt;p&gt;I spent most of my active coding time in &lt;code&gt;tests/core&lt;/code&gt; and the &lt;code&gt;newsfragments&lt;/code&gt; directory. I managed to push two significant commits here that were more about stability and refactoring than net-new features. &lt;/p&gt;

&lt;p&gt;First, I tackled a nagging issue with the TCP transport. I re-enabled a stale IPv6 listen/dial integration test that had been sitting there (closing issue #1189). There’s something deeply satisfying about turning a skipped test back to green, especially when it involves the finicky nature of IPv6 networking. &lt;/p&gt;

&lt;p&gt;I also did some refactoring on the &lt;code&gt;pubsub-test&lt;/code&gt; suite. I implemented predicate-based readiness in the &lt;code&gt;subscribed_mesh&lt;/code&gt; fixture (PR #1315). Testing asynchronous p2p systems is a nightmare if your timing isn't perfect, and moving to a predicate-based readiness check makes the whole test suite much less flaky. I added 137 lines and only pulled out 9, mostly because robust testing logic usually requires a bit more verbosity than the "hope and pray" method of &lt;code&gt;time.sleep()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  nvim
&lt;/h3&gt;

&lt;p&gt;The rest of my commits (7 of them, to be exact) were in my &lt;code&gt;nvim&lt;/code&gt; config. This is the background hum of my development life. Most of these were automated via CI—keeping plugins updated to their latest versions. You’ll see a bunch of &lt;code&gt;ci: update all plugins to latest [skip ci]&lt;/code&gt; messages in my history. It’s not "feature work" in the traditional sense, but keeping my editor sharp is a non-negotiable part of the craft. Even with these small updates, I touched 7 different files. It’s a net-zero change (+12/-12), which is exactly what you want for maintenance: keep it moving, don't break the flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  ISSUES &amp;amp; DISCUSSIONS
&lt;/h2&gt;

&lt;p&gt;This is where the real "brain work" happened this week. I've been thinking a lot about the security of the Kademlia Distributed Hash Table (DHT), specifically how to make it more resilient against eclipse attacks. I opened three issues and started a major discussion in the &lt;code&gt;py-libp2p&lt;/code&gt; repo to map out the path forward.&lt;/p&gt;

&lt;p&gt;I opened Issue #1383 to address IP/subnet diversity enforcement in &lt;code&gt;KBucket&lt;/code&gt;. If a single attacker can fill up a bucket with nodes from the same subnet, they can effectively "eclipse" a node from the rest of the network. Enforcing diversity is a standard but critical defense.&lt;/p&gt;

&lt;p&gt;Following that, I opened Issue #1384 for adding disjoint lookup paths to &lt;code&gt;find_closest_peers_network&lt;/code&gt;. Parallel lookups are great for speed, but if they aren't disjoint, a single malicious node can still influence the outcome of the entire search. &lt;/p&gt;

&lt;p&gt;Finally, I opened Issue #1385 regarding monotonic sequence numbers for silent-withholding detection in &lt;code&gt;GET_VALUE&lt;/code&gt; requests. This one is a bit more nuanced—it's about detecting when a node is intentionally not giving you the data it should have.&lt;/p&gt;

&lt;p&gt;I tied all of these together in a discussion titled "Hardening the Kademlia DHT against eclipse attacks — three issues I want to work on" (#1386). I put this in the "Ideas" category because I want to make sure the maintainers are aligned with the architectural changes before I start dropping massive PRs. It’s about building consensus before building the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  TECH STACK
&lt;/h2&gt;

&lt;p&gt;This week was a deep dive into Python and Lua. &lt;/p&gt;

&lt;p&gt;Python remains my heavy lifter for backend and p2p work. With over 95MB of Python code in my current environment, it’s where I do my most complex logic. The +149/-21 line ratio this week shows I was mostly in "build and expand" mode rather than "refactor and destroy" mode.&lt;/p&gt;

&lt;p&gt;Lua is my "glue" language, primarily for Neovim. It’s lightweight and does exactly what I need it to do for tooling.&lt;/p&gt;

&lt;p&gt;I also kept my 7-day streak alive. I’m not a fan of the "grind" for the sake of a green square, but when you're deep into a security architecture problem, it's hard &lt;em&gt;not&lt;/em&gt; to check in every day. The flow state is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT'S NEXT
&lt;/h2&gt;

&lt;p&gt;Next week is going to be about turning those issues into code. Now that I’ve laid out the plan for Kademlia hardening and got the discussion started, I’ll be diving into the &lt;code&gt;KBucket&lt;/code&gt; implementation to start enforcing that subnet diversity. I’m also keeping an eye on the &lt;code&gt;py-libp2p&lt;/code&gt; discussion board to see if the maintainers have any feedback on the disjoint lookup path proposal. &lt;/p&gt;

&lt;p&gt;It’s one thing to talk about p2p security; it’s another thing to ship it. Time to get to work.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Generated by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>automation</category>
    </item>
    <item>
      <title>Dev log #12 Hardening WebRTC and Polishing the UI: A Week of Networking and Refinement</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Mon, 13 Jul 2026 03:13:05 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-12-hardening-webrtc-and-polishing-the-ui-a-week-of-networking-and-refinement-29m2</link>
      <guid>https://dev.to/yashksaini/dev-log-12-hardening-webrtc-and-polishing-the-ui-a-week-of-networking-and-refinement-29m2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Spent the week balancing deep p2p networking work in Python with some much-needed UI polish on my personal site. 11 commits and 6 PRs later, I hit a perfect 7-day streak and made the codebase a bit more secure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;This week was all about the "invisible" work that makes software feel solid. I spent a good chunk of time in the weeds of p2p networking, specifically hardening WebRTC implementations, while also carving out time to refine the typography and feel of my personal portfolio. With 11 commits across 5 repos and 6 PRs in flight, I managed to keep the momentum going every single day of the week.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT I BUILT
&lt;/h2&gt;

&lt;p&gt;Most of my direct commit activity this week was split between keeping my dev environment sharp and making my portfolio feel a bit more "me."&lt;/p&gt;

&lt;h3&gt;
  
  
  Portfolio &amp;amp; Personal Branding
&lt;/h3&gt;

&lt;p&gt;I spent some quality time in &lt;code&gt;yashksaini-coder/portfolio&lt;/code&gt;. If you're like me, you can't leave your personal site alone for more than a month. I pushed a few updates to the blog content, but the real fun was in the UI/UX tweaks. I swapped out the primary typography for &lt;strong&gt;JetBrains Mono&lt;/strong&gt;—there’s just something about a good monospace font that makes a dev portfolio feel right. &lt;/p&gt;

&lt;p&gt;I also went through a "make-interfaces-feel-better" phase. I refactored the &lt;code&gt;selectedwork&lt;/code&gt; section, specifically dropping a cursor-follow preview tile that felt a bit too "heavy" and replaced it with something more streamlined. I also polished the index rows to make the transitions feel snappier. It’s about +452/-279 lines of code, which is a healthy amount of churn for a week that was supposed to be about "minor" updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Maintenance Grind
&lt;/h3&gt;

&lt;p&gt;My &lt;code&gt;nvim&lt;/code&gt; config is basically a living organism at this point. I have CI set up to automatically track plugin updates, and this week was particularly noisy with 6 commits just keeping the toolchain current. It’s [skip ci] territory, but it ensures that when I sit down to actually write code, my editor isn't lagging behind the latest Lua API changes.&lt;/p&gt;

&lt;p&gt;I also did a quick version bump for &lt;code&gt;trx&lt;/code&gt; (v0.1.13) and merged a branch in &lt;code&gt;openslate&lt;/code&gt; to fix some styling and search logic in the command palette. It’s the kind of small-scale maintenance that prevents technical debt from snowballing.&lt;/p&gt;

&lt;h2&gt;
  
  
  PULL REQUESTS
&lt;/h2&gt;

&lt;p&gt;The PR list is where the "real" engineering happened this week. I’ve got five open PRs and one merged, mostly focused on the libp2p ecosystem and some documentation DX.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deep Diving into WebRTC (py-libp2p)
&lt;/h3&gt;

&lt;p&gt;The bulk of my brainpower went into &lt;code&gt;libp2p/py-libp2p&lt;/code&gt;. I’ve been working on a few critical pieces for the WebRTC-Direct transport:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Shared-port dispatching&lt;/strong&gt;: I opened a PR to add &lt;code&gt;UdpMux&lt;/code&gt;. This is a big one for anyone running nodes behind restrictive NATs. It allows for shared-port inbound dispatching, which is essential for making WebRTC-Direct viable in the real world. That PR alone is +541 lines of new logic.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Security &amp;amp; Hardening&lt;/strong&gt;: I submitted a fix to harden the &lt;code&gt;/sdp&lt;/code&gt; HTTP server against memory-amplification DoS attacks. Networking code is always a target, and making sure we aren't vulnerable to simple memory exhaustion is a priority.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Refactoring &amp;amp; Cleanup&lt;/strong&gt;: I also managed to delete some code! I deduplicated &lt;code&gt;_varint.py&lt;/code&gt; in favor of the centralized &lt;code&gt;libp2p.utils.varint&lt;/code&gt;. It’s only a net loss of about 60 lines, but every line of code removed is a line I don't have to debug later.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Testing&lt;/strong&gt;: Re-enabled some stale IPv6 integration tests for the TCP transport. Green CI is the only CI I trust.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Documentation DX
&lt;/h3&gt;

&lt;p&gt;Over at Hugging Face, I opened a PR for &lt;code&gt;tau&lt;/code&gt; to add a "copy" button to code blocks in the docs. It’s a small thing, but as a dev, there is nothing more annoying than trying to manually select a multi-line command and accidentally grabbing the line numbers. I opened an issue for it first to track the requirement and then followed up with the implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  CODE REVIEWS
&lt;/h2&gt;

&lt;p&gt;I spent some time on the other side of the fence this week, too. I reviewed and approved a PR for &lt;code&gt;trx&lt;/code&gt; that was updating the main branch with new contributor changes. It was a relatively light review week, but I always try to make sure I’m unblocking others, especially in smaller OSS projects where contributor momentum is everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  TECH STACK
&lt;/h2&gt;

&lt;p&gt;This week was a polyglot's dream. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Python&lt;/strong&gt;: My primary tool for the p2p networking work. Between &lt;code&gt;UdpMux&lt;/code&gt; and DoS protection, I was deep in the Python standard library and libp2p internals.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;TypeScript &amp;amp; MDX&lt;/strong&gt;: Used these for the portfolio updates. The JetBrains Mono swap and the UI refactor kept me busy in the frontend.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Rust&lt;/strong&gt;: Handled the version bumping and chore work for &lt;code&gt;trx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Lua&lt;/strong&gt;: Mostly automated, but still the backbone of my editing environment.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Svelte&lt;/strong&gt;: Touched this briefly while helping out with the &lt;code&gt;openslate&lt;/code&gt; command palette fix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a &lt;strong&gt;7-day streak&lt;/strong&gt;, I’ve stayed consistent. The line delta (+609 / -306) shows that while I'm building new features (like the WebRTC muxing), I'm still making a conscious effort to refactor and delete the old cruft.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT'S NEXT
&lt;/h2&gt;

&lt;p&gt;The WebRTC PRs in &lt;code&gt;py-libp2p&lt;/code&gt; are the priority for next week. I need to shepherd those through the review process and address any feedback on the &lt;code&gt;UdpMux&lt;/code&gt; implementation. I’m also looking forward to seeing the copy button land in the &lt;code&gt;tau&lt;/code&gt; docs—it’s a small win for developer experience. &lt;/p&gt;

&lt;p&gt;On the personal side, now that the typography on the portfolio is settled, I might start looking into some more interactive elements for the blog section. But for now, I’m just happy to have a week of green squares and solid networking code under my belt. See you in the next push!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Generated by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;

</description>
      <category>portfolio</category>
      <category>opensource</category>
      <category>weekendchallenge</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Adding WebRTC to py-libp2p: Two Runtimes, Three Specs, and One Unforgiving Private Slot</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Mon, 13 Jul 2026 03:07:12 +0000</pubDate>
      <link>https://dev.to/yashksaini/adding-webrtc-to-py-libp2p-two-runtimes-three-specs-and-one-unforgiving-private-slot-3n22</link>
      <guid>https://dev.to/yashksaini/adding-webrtc-to-py-libp2p-two-runtimes-three-specs-and-one-unforgiving-private-slot-3n22</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — py-libp2p &lt;a href="https://github.com/libp2p/py-libp2p/pull/1309" rel="noopener noreferrer"&gt;PR #1309&lt;/a&gt; adds an &lt;em&gt;experimental&lt;/em&gt; WebRTC transport (&lt;code&gt;/webrtc-direct&lt;/code&gt; and &lt;code&gt;/webrtc&lt;/code&gt;) as a v1 node-to-node foundation. It's ~6,700 lines across 41 files, gated by &lt;code&gt;libp2p[webrtc]&lt;/code&gt; + &lt;code&gt;enable_webrtc&lt;/code&gt;, and merged after four rounds of maintainer review. This is the story of what's inside, why the seams are where they are, and the three bugs that would have shipped silently if the loopback test hadn't been written.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  A quick honest note before we start
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about how this post came together. This is a &lt;em&gt;reflective&lt;/em&gt; engineering write-up on a PR I authored, with the goal of turning the review conversation, spec cross-references, and multi-round fixes into something a future reader can learn from without having to scroll 400 GitHub comments.&lt;/p&gt;

&lt;p&gt;Where I quote maintainers, those are their words. Where I explain "why we did it this way," I've tried to preserve the trade-offs as they actually came up in review — not the polished, past-tense version. This isn't a marketing post. WebRTC in py-libp2p is &lt;em&gt;experimental v1&lt;/em&gt;; browser interop is not supported; there are follow-ups filed. I'll say that plainly throughout.&lt;/p&gt;

&lt;p&gt;If you're new to libp2p, WebRTC, or trio, there's enough background woven in to keep you oriented. If you're deep in this ecosystem already, the interesting sections are the framing, the certificate pin, and the two-runtime bridge — feel free to jump.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem: why a Python libp2p wants WebRTC at all
&lt;/h2&gt;

&lt;p&gt;libp2p is a modular network stack: identity, transports, security, multiplexing, routing, and pub/sub, each swappable. Its Go and JS implementations have had WebRTC for a while. Python did not — &lt;a href="https://github.com/libp2p/py-libp2p/issues/546" rel="noopener noreferrer"&gt;issue #546&lt;/a&gt; tracks the gap.&lt;/p&gt;

&lt;p&gt;Why care? Three reasons the ecosystem keeps coming back to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;NAT traversal for real users.&lt;/strong&gt; Most consumer machines sit behind at least one NAT, often more. WebRTC's ICE + STUN + TURN dance is the only widely-deployed answer that browsers speak natively. If your Python peer wants to talk to a browser peer — or two NATed Python peers want to talk directly with only a light-touch relay — WebRTC is the door.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native multiplexing.&lt;/strong&gt; WebRTC data channels give you real per-stream framing over SCTP. That means the transport itself carries a "muxer," so you can skip the Yamux / Mplex upgrade step that a TCP-based libp2p connection needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-to-browser without an HTTP server.&lt;/strong&gt; With &lt;code&gt;/webrtc-direct&lt;/code&gt;, a Python node can accept an inbound connection from a browser peer &lt;em&gt;purely&lt;/em&gt; via a UDP socket + a certificate hash embedded in the multiaddr. No signaling server, no HTTPS termination, no TURN unless traversal forces you there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the "why." Now for the "how" — and specifically, why "how" turned into 6,700 lines of Python that took four review rounds to get right.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually landed
&lt;/h2&gt;

&lt;p&gt;The PR body says it, but here it is in one paragraph so you know the surface area:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WebRTCDirectTransport&lt;/code&gt; (&lt;code&gt;/webrtc-direct&lt;/code&gt;) and &lt;code&gt;WebRTCPrivateTransport&lt;/code&gt; (&lt;code&gt;/webrtc&lt;/code&gt;, relay-signaled).&lt;/li&gt;
&lt;li&gt;Spec-compliant data-channel framing — uvarint length-prefixed protobuf, with the full FIN / FIN_ACK / STOP_SENDING / RESET state machine.&lt;/li&gt;
&lt;li&gt;In-band data channels for application streams; the Noise handshake channel (&lt;code&gt;id=0&lt;/code&gt;) stays pre-negotiated per spec.&lt;/li&gt;
&lt;li&gt;Signaling with the bilateral &lt;code&gt;ICE_DONE&lt;/code&gt; fix from &lt;a href="https://github.com/libp2p/specs/pull/585" rel="noopener noreferrer"&gt;libp2p/specs#585&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Noise XX with a prologue that binds the handshake to the DTLS certificate fingerprints.&lt;/li&gt;
&lt;li&gt;ECDSA P-256 cert generation with multihash / multibase fingerprint encoding.&lt;/li&gt;
&lt;li&gt;A DTLS cert pin that actually pins the cert. More on that shortly.&lt;/li&gt;
&lt;li&gt;A trio ↔ asyncio bridge, because py-libp2p is trio and aiortc is asyncio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final tests count: &lt;strong&gt;153 / 153 passing&lt;/strong&gt;, including a real &lt;code&gt;RTCPeerConnection&lt;/code&gt; ↔ &lt;code&gt;RTCPeerConnection&lt;/code&gt; loopback test that exercises framing, chunked writes over the 16 KiB SCTP ceiling, and the SDP-fingerprint canary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's out of scope&lt;/strong&gt;, and this matters: browser interop is &lt;em&gt;not&lt;/em&gt; supported here; go / js &lt;code&gt;/webrtc-direct&lt;/code&gt; interop is &lt;em&gt;not&lt;/em&gt; wired (they reconstruct the SDP from STUN &lt;code&gt;USERNAME&lt;/code&gt;, we don't have that hook yet); the &lt;code&gt;/webrtc&lt;/code&gt; dial path is a listener + signaling skeleton only. All of that is deliberate. This is v1. v2 (&lt;a href="https://github.com/libp2p/specs/pull/715" rel="noopener noreferrer"&gt;specs#715&lt;/a&gt;) is where browser dial actually happens, and we wanted the v1 skeleton in the tree so it stops being vaporware.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture in one picture
&lt;/h2&gt;

&lt;p&gt;Two flavors of WebRTC show up in py-libp2p, and it took me a while to keep them straight. Here they are, side by side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  subgraph Direct["/webrtc-direct (server-reachable)"]
    A[Client] -- SDP offer built&amp;lt;br/&amp;gt;from server multiaddr --&amp;gt; B[Server&amp;lt;br/&amp;gt;publishes cert hash]
    A &amp;lt;-. DTLS + data channels .-&amp;gt; B
  end

  subgraph Private["/webrtc (both peers NATed)"]
    C[Peer A] --- R[Relay v2]
    D[Peer B] --- R
    C -. signaling over relay:&amp;lt;br/&amp;gt;SDP + ICE + ICE_DONE .-&amp;gt; D
    C &amp;lt;-. direct DTLS + data channels .-&amp;gt; D
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two transports, one shared substrate: certs, SDP, Noise binding, stream framing, and the trio↔asyncio bridge. Only signaling differs.&lt;/p&gt;

&lt;p&gt;Inside a single peer, the runtime layout looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TB
  subgraph Trio["Trio world (py-libp2p)"]
    T1[Host / Swarm]
    T2[WebRTCConnection]
    T3[WebRTCStream × N]
    T4[Application handlers]
  end

  subgraph Bridge["AsyncioBridge&amp;lt;br/&amp;gt;(background daemon thread)"]
    BR[asyncio event loop]
  end

  subgraph Asyncio["Asyncio world (aiortc)"]
    A1[RTCPeerConnection]
    A2[RTCDataChannel × N]
    A3[DTLS / ICE / SCTP]
  end

  T1 --&amp;gt; T2 --&amp;gt; T3
  T2 -- run_coro --&amp;gt; BR
  BR -- runs on --&amp;gt; A1
  A1 --&amp;gt; A2 --&amp;gt; A3
  A2 -. on_data via trio_token .-&amp;gt; T3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting seam is the arrow labelled &lt;em&gt;trio_token&lt;/em&gt;: aiortc calls into us from an asyncio thread, and we need those callbacks to hand data to trio memory channels &lt;em&gt;safely&lt;/em&gt;. If we skip the token, we get &lt;code&gt;RunFinishedError&lt;/code&gt; and worse. That code lives in &lt;code&gt;stream.py:382-410&lt;/code&gt; — I'll come back to it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep dive #1 — framing, or "why bare protobuf was the wrong wire"
&lt;/h2&gt;

&lt;p&gt;If you read the first draft of this PR, application-level writes were serialized as bare protobuf &lt;code&gt;Message&lt;/code&gt; bytes and shoved straight into &lt;code&gt;data_channel.send(...)&lt;/code&gt;. That "works" on the happy path for small payloads. It's also wrong for two independent reasons — and once you understand &lt;em&gt;both&lt;/em&gt;, you understand why the spec is written the way it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reason 1: SCTP has a 16 KiB message ceiling
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/libp2p/specs/blob/master/webrtc/webrtc.md" rel="noopener noreferrer"&gt;libp2p WebRTC spec&lt;/a&gt; caps each data-channel message at 16 384 bytes. Below that, you're fine. Above, aiortc will happily hand SCTP an oversized message that gets fragmented in ways your peer can't reassemble, or your peer will drop it, or SCTP itself will complain — the failure modes vary by implementation, which is worse than a clean rejection.&lt;/p&gt;

&lt;p&gt;The fix is to chunk on the sender's side and let the read side see one wire message per SCTP message.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reason 2: bare protobuf isn't self-delimiting inside a stream frame
&lt;/h3&gt;

&lt;p&gt;Even inside the 16 KiB limit, a Go or JS peer decoding your message needs a length prefix. The spec is explicit: &lt;strong&gt;each SCTP message is one uvarint length prefix + one protobuf &lt;code&gt;Message&lt;/code&gt;&lt;/strong&gt;. Not "raw protobuf." Not "concatenated protobufs." Exactly one framed pair per SCTP datagram.&lt;/p&gt;

&lt;p&gt;Here's the on-wire shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────┬──────────────────────────────────────────┐
│  uvarint length   │  protobuf Message (flag? + message? bytes)│
└───────────────────┴──────────────────────────────────────────┘
                    ← up to 16 KiB total, hard ceiling ──────→
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The uvarint itself is straightforward — 7 bits of payload per byte, top bit for continuation, capped at 10 bytes so it fits &lt;code&gt;uint64&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# libp2p/transport/webrtc/_varint.py
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encode_uvarint&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uvarint cannot encode negative values&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mh"&gt;0x7F&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0x7F&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mh"&gt;0x80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0x7F&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sender side of a stream then becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# libp2p/transport/webrtc/stream.py
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;StreamState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RESET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;WebRTCStreamError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stream was reset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_write_closed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;WebRTCStreamError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write side is closed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;MAX_PAYLOAD_SIZE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chunk&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;MAX_PAYLOAD_SIZE&lt;/code&gt; is chosen so that even with the largest reasonable varint length prefix plus protobuf tag bytes, the full framed wire message stays under 16 KiB. The chunk boundary is on the sender; the reader just decodes one frame per SCTP message.&lt;/p&gt;

&lt;p&gt;And this is worth calling out — there's one spec detail that's easy to miss:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The spec allows a message to carry both data and FIN, and the data must be delivered to the reader before the read channel is closed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the receive path enqueues payload &lt;em&gt;first&lt;/em&gt;, then processes the flag. Getting that order wrong gives you a data loss bug that only shows up on the last chunk of a stream close. From &lt;code&gt;stream.py:352-380&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# libp2p/transport/webrtc/stream.py
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_apply_on_trio_thread&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Enqueue payload BEFORE processing flags — the spec allows a
&lt;/span&gt;    &lt;span class="c1"&gt;# message to carry both data and FIN, and the data must be
&lt;/span&gt;    &lt;span class="c1"&gt;# delivered to the reader before the read channel is closed.
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;has_payload&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_read_send&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_nowait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;trio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WouldBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;has_flag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_read_closed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_enqueue_eof_sentinel_locked&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_schedule_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIN_ACK&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIN_ACK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_fin_ack_received&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comment isn't decoration; it's a spec citation for a future reader who wonders why the ordering matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep dive #2 — the certificate pin that actually pins
&lt;/h2&gt;

&lt;p&gt;This is my favourite bug in the PR. It's the kind of bug that survives green tests because the failure is silent, the API looks correct, and the wrong value is only observable if you cross-check two places that most people cross-check exactly once — at spec-reading time, then never again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting the scene
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/webrtc-direct&lt;/code&gt; works by embedding the server's DTLS certificate hash &lt;em&gt;in the multiaddr&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ip4/127.0.0.1/udp/9090/webrtc-direct/certhash/uEiA…/p2p/12D3K…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client dials that address, constructs an SDP offer locally (there's no signaling server), and expects the server's DTLS handshake to present a cert whose SHA-256 matches the &lt;code&gt;certhash&lt;/code&gt; in the multiaddr. If they don't match, the client bails — that's the whole security story of &lt;code&gt;/webrtc-direct&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Which means: whatever cert the server &lt;em&gt;advertises&lt;/em&gt;, it must actually &lt;em&gt;use&lt;/em&gt; in the DTLS handshake. If those two paths diverge, you either get a broken connection or, worse, a working connection that isn't actually pinned to anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  The naïve pin
&lt;/h3&gt;

&lt;p&gt;In aiortc, you'd expect to set the cert on the &lt;code&gt;RTCPeerConnection&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RTCPeerConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_certificates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;my_cert&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# ← looks fine, right?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a silent no-op. &lt;code&gt;RTCPeerConnection&lt;/code&gt; reads only its own name-mangled private attribute — &lt;code&gt;self.__certificates&lt;/code&gt; inside the class, which mangles to &lt;code&gt;self._RTCPeerConnection__certificates&lt;/code&gt; from the outside. The &lt;code&gt;pc._certificates&lt;/code&gt; attribute lives on the object, sure, but aiortc never looks at it. It happily generates its &lt;em&gt;own&lt;/em&gt; certificate at construction time and uses that in DTLS. Your advertised &lt;code&gt;/certhash/&lt;/code&gt; is a completely different digest.&lt;/p&gt;

&lt;p&gt;The maintainer review flagged this indirectly — the loopback test caught it directly. Every test that ran without an actual peer connection passed. The moment we ran an SDP round-trip and grepped the &lt;code&gt;a=fingerprint&lt;/code&gt; line, the pinned cert was nowhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# libp2p/transport/webrtc/_aiortc_helpers.py
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_peer_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;rtc_cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RTCCertificate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ice_servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;RTCPeerConnection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Create an RTCPeerConnection pinned to the given certificate.

    aiortc &amp;gt;= 1.5 no longer accepts certificates= in RTCConfiguration —
    passing it raises TypeError.  We build the peer connection with ICE
    servers only, then replace the auto-generated DTLS certificate before
    any SDP operation triggers the DTLS handshake.  The fingerprint and
    the actual handshake cert are read from a name-mangled private
    attribute (self.__certificates inside RTCPeerConnection →
    self._RTCPeerConnection__certificates from the outside).  Setting
    the un-mangled pc._certificates is a no-op that aiortc never reads.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RTCConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iceServers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ice_servers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ice_servers&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;pc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RTCPeerConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Must use the mangled name — aiortc reads only self.__certificates.
&lt;/span&gt;    &lt;span class="n"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_RTCPeerConnection__certificates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rtc_cert&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore[attr-defined]
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two characters (&lt;code&gt;__&lt;/code&gt;) that would look like a typo in a code review, guarding a subtle security property.&lt;/p&gt;

&lt;h3&gt;
  
  
  The canary
&lt;/h3&gt;

&lt;p&gt;Fixing the bug is one thing; making sure it stays fixed is another. The loopback test now includes a &lt;em&gt;canary&lt;/em&gt; — after the SDP exchange, it parses the &lt;code&gt;a=fingerprint:sha-256&lt;/code&gt; line out of the offer and asserts it equals the SHA-256 of the pinned cert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tests/core/transport/webrtc/test_multiplexing_loopback.py
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_sdp_fingerprint_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebRTCCertificate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Render *cert*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s SHA-256 fingerprint in the colon-separated upper-hex
    form aiortc writes into SDP a=fingerprint:sha-256 ... lines.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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;join&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If somebody in the future refactors the pin — or aiortc renames &lt;code&gt;__certificates&lt;/code&gt; — this test fails immediately, with a message that points straight at the divergence. That's the whole point of a canary: it wouldn't have been added if the bug hadn't happened, and it stays there so the bug can't come back.&lt;/p&gt;

&lt;p&gt;The subplot here isn't "aiortc is buggy" — it's that libraries with public APIs sometimes leak private-attribute contracts into their integrators, and when they do, the only defense is a test that observes the on-wire outcome, not the API you &lt;em&gt;think&lt;/em&gt; you called.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep dive #3 — bridging two runtimes without blowing up
&lt;/h2&gt;

&lt;p&gt;py-libp2p is trio. aiortc is asyncio. Both are event loops. Neither will &lt;code&gt;await&lt;/code&gt; the other's coroutines. This isn't a limitation of Python; it's a design decision by trio (structured concurrency, no thread-safety-by-default) that leans hard against interop.&lt;/p&gt;

&lt;p&gt;The options are: pick one runtime, run both loops in one thread with a bridge library, or run the second loop in its own thread. Option one wasn't available — swapping either dependency was a non-starter. Option two (&lt;code&gt;anyio&lt;/code&gt;, &lt;code&gt;trio-asyncio&lt;/code&gt;) has enough corner cases with aiortc's assumptions that we didn't want to chase them under a review deadline. Option three is boring and explicit, which is what we wanted.&lt;/p&gt;

&lt;p&gt;The bridge is a small class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# libp2p/transport/webrtc/_asyncio_bridge.py
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AsyncioBridge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Manages a background asyncio event loop for running aiortc coroutines.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_loop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AbstractEventLoop&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_thread&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="c1"&gt;# Protects start/stop against concurrent trio calls
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="c1"&gt;# Protects _loop/_started/_stopped reads from run_coro against
&lt;/span&gt;        &lt;span class="c1"&gt;# concurrent stop() — needed because run_coro_sync is called
&lt;/span&gt;        &lt;span class="c1"&gt;# from non-trio threads (asyncio callbacks).
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_state_lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two locks — a &lt;code&gt;trio.Lock&lt;/code&gt; for the trio side of start/stop, and a &lt;code&gt;threading.Lock&lt;/code&gt; because &lt;em&gt;asyncio callbacks&lt;/em&gt; can hit the bridge from a foreign thread. If you only have the trio lock, you race with those callbacks and it manifests as intermittent &lt;code&gt;AsyncioBridgeError: not running&lt;/code&gt; right at shutdown.&lt;/p&gt;

&lt;p&gt;The heart of it is &lt;code&gt;run_coro&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# libp2p/transport/webrtc/_asyncio_bridge.py
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_coro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coro&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Coroutine&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_state_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_loop&lt;/span&gt;
        &lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_started&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_stopped&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;coro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AsyncioBridgeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AsyncioBridge is not running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_coroutine_threadsafe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_wait_for_result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&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;# abandon_on_cancel=True lets trio cancel the scope immediately.
&lt;/span&gt;        &lt;span class="c1"&gt;# The background thread is abandoned but we cancel the asyncio
&lt;/span&gt;        &lt;span class="c1"&gt;# future below so it doesn't leak.
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;trio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;_wait_for_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abandon_on_cancel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;trio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cancelled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two subtle things worth pausing on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;abandon_on_cancel=True&lt;/code&gt;&lt;/strong&gt; — without it, trio's cancellation waits for the thread to unwind before actually cancelling. With aiortc's coroutines that can mean seconds, or forever. With &lt;code&gt;abandon_on_cancel=True&lt;/code&gt;, trio releases the scope immediately, we forward the cancellation to the asyncio future, and the background loop cleans up on its own timeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;_state_lock&lt;/code&gt; snapshot&lt;/strong&gt; — we read &lt;code&gt;loop&lt;/code&gt; and &lt;code&gt;running&lt;/code&gt; under the lock, then use the local &lt;code&gt;loop&lt;/code&gt; variable. If &lt;code&gt;stop()&lt;/code&gt; runs concurrently, we either dispatched to a valid loop (which will handle the cancel) or we raise cleanly. Never a race where we scheduled on a partially-torn-down loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reverse direction — aiortc calling &lt;em&gt;us&lt;/em&gt; — needs a different trick. When a data channel fires an &lt;code&gt;on_message&lt;/code&gt; event, we're on the asyncio thread, and we need to push bytes into a &lt;code&gt;trio.MemorySendChannel&lt;/code&gt;. Direct calls into trio primitives from a foreign thread are undefined behavior. The trick is &lt;code&gt;trio.lowlevel.current_trio_token()&lt;/code&gt;, captured at construction time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# libp2p/transport/webrtc/stream.py
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_trio_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lowlevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_trio_token&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_trio_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_run_on_trio_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[],&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_trio_token&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;trio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lowlevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_task&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;in_trio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;in_trio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;in_trio&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;fn&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;trio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trio_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;trio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunFinishedError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we're already on a trio task (tests), we call inline. If we're on an asyncio callback thread, we go through &lt;code&gt;trio.from_thread.run_sync&lt;/code&gt; with the token. If trio has already shut down, we log and drop — not raise, because the callback is fire-and-forget from aiortc's point of view.&lt;/p&gt;

&lt;p&gt;This is the code that makes the whole transport not-flaky. It's ~30 lines. It took two review cycles to converge on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep dive #4 — Noise XX, but bound to DTLS
&lt;/h2&gt;

&lt;p&gt;WebRTC already gives you an encrypted DTLS channel between two peers. Why do we still run Noise on top of it?&lt;/p&gt;

&lt;p&gt;Because DTLS authenticates the &lt;em&gt;certificate&lt;/em&gt;, not the &lt;em&gt;libp2p peer&lt;/em&gt;. Anyone with a valid ECDSA P-256 cert can complete the DTLS handshake. But libp2p peers are identified by their libp2p identity key, which is a completely different keypair. So we need a step where each side proves control of a libp2p identity — and the natural way to do that is Noise XX, which is already how libp2p peers authenticate on TCP.&lt;/p&gt;

&lt;p&gt;But there's a subtle attack here: what if an attacker sits between the two peers, has their own valid DTLS cert, and forwards both DTLS sessions? Both peers see a valid DTLS handshake, then run Noise on top — and if Noise doesn't know anything about DTLS, it authenticates fine end-to-end &lt;em&gt;through&lt;/em&gt; the attacker.&lt;/p&gt;

&lt;p&gt;The fix is the &lt;em&gt;Noise prologue&lt;/em&gt; — a byte string that's mixed into the Noise handshake state at the start. The libp2p spec binds Noise to DTLS by putting both certificate fingerprints into the prologue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# libp2p/transport/webrtc/noise_handshake.py
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_noise_prologue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;local_fingerprint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;remote_fingerprint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Build the Noise prologue that binds the handshake to the DTLS session.

    Prologue format:
        b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;libp2p-webrtc-noise:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; + encode(local_fp) + encode(remote_fp)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;local_mh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_MH_SHA256_HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;local_fingerprint&lt;/span&gt;
    &lt;span class="n"&gt;remote_mh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_MH_SHA256_HEADER&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;remote_fingerprint&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;NOISE_PROLOGUE_PREFIX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;local_mh&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;remote_mh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an attacker sits in the middle, each side sees a &lt;em&gt;different&lt;/em&gt; remote fingerprint (the attacker's), so the prologues mismatch, and Noise fails. Clean. Nothing needs to change in the Noise state machine itself — the prologue is a parameter it already accepts.&lt;/p&gt;

&lt;p&gt;The rest of the handshake reuses py-libp2p's existing &lt;code&gt;PatternXX&lt;/code&gt; implementation. That's what "in-band data channels" is buying us — the Noise handshake rides over data channel 0, which is pre-negotiated (&lt;code&gt;negotiated=True, id=0&lt;/code&gt;) so both peers agree on its identity without SDP renegotiation. Application streams get their own in-band channels, so the &lt;code&gt;on('datachannel')&lt;/code&gt; event fires on the peer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; in the first draft of the PR, application streams used &lt;code&gt;negotiated=True&lt;/code&gt; as well. The consequence was that on the remote peer, the &lt;code&gt;datachannel&lt;/code&gt; event &lt;em&gt;never fired&lt;/em&gt;, because pre-negotiated channels bypass it entirely. &lt;code&gt;accept_stream()&lt;/code&gt; blocked forever. It was a classic "the spec is clear, we read it too quickly" fix — Noise uses &lt;code&gt;negotiated=True&lt;/code&gt; because it's channel 0 and both sides know it's there; application streams use &lt;code&gt;negotiated=False&lt;/code&gt; because they need to be &lt;em&gt;announced&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep dive #5 — signaling, ICE_DONE, and a spec fix in flight
&lt;/h2&gt;

&lt;p&gt;For the &lt;code&gt;/webrtc&lt;/code&gt; (relay-signaled) case, two NATed peers need to exchange SDP over some other channel to get started. That channel is a Circuit Relay v2 stream, and the protocol is &lt;code&gt;/webrtc-signaling/0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The interesting part isn't the SDP exchange — it's how you know it's &lt;em&gt;done&lt;/em&gt;. ICE candidates trickle over the signaling stream as each peer's ICE agent discovers them. At some point, both sides are done trickling, and the stream can close. But if one side closes prematurely, the other side loses candidates it hadn't received yet.&lt;/p&gt;

&lt;p&gt;The old spec left this timing ambiguous. &lt;a href="https://github.com/libp2p/specs/pull/585" rel="noopener noreferrer"&gt;libp2p/specs#585&lt;/a&gt; fixed it by adding a &lt;em&gt;bilateral&lt;/em&gt; &lt;code&gt;ICE_DONE&lt;/code&gt; message — each side signals it's done, and neither closes until both &lt;code&gt;ICE_DONE&lt;/code&gt;s are seen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initiator                              Responder (via relay)
  ──── SDP_OFFER ─────────────────────────&amp;gt;
  &amp;lt;─── SDP_ANSWER ─────────────────────────
  &amp;lt;──&amp;gt; ICE_CANDIDATE (trickle, both ways) &amp;lt;&amp;gt;
  ──── ICE_DONE ───────────────────────────&amp;gt;
  &amp;lt;─── ICE_DONE ───────────────────────────
  (both sides close signaling stream)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's what &lt;code&gt;signaling.py&lt;/code&gt; implements. It's not the most complicated code in the PR, but it's the piece that &lt;em&gt;cannot&lt;/em&gt; be handwaved with "we'll add ICE_DONE later" — it changes the state machine of when both peers agree the signaling phase has ended.&lt;/p&gt;




&lt;h2&gt;
  
  
  The seam that will save us a rewrite: &lt;code&gt;_apply_ice_credentials()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;There's one more piece of "spec is in flight" that we chose to handle &lt;em&gt;architecturally&lt;/em&gt; rather than by writing a TODO.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/libp2p/specs/issues/672" rel="noopener noreferrer"&gt;libp2p/specs#672&lt;/a&gt; discussion is about Chrome removing SDP munging for ICE credentials — a change that will eventually break the v1 way of injecting the peer's ICE ufrag into the SDP. When that lands, code that has ICE credential handling &lt;em&gt;scattered&lt;/em&gt; across the SDP builder is in for a bad time. Code that has all of it in &lt;em&gt;one function&lt;/em&gt; just changes that function.&lt;/p&gt;

&lt;p&gt;So we put all of it in one function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# libp2p/transport/webrtc/sdp.py
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_apply_ice_credentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sdp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ufrag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fingerprint_hex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;remote_ufrag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;remote_pwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Apply ICE credentials to the SDP.

    **This is the single seam for libp2p/specs#672.**  When Chrome drops
    ICE credential munging support, only this function needs to change.
    Currently a no-op passthrough — local credentials are already in the
    SDP template.  The remote credentials are accepted but unused until
    the spec changes require injecting them via a separate mechanism.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sdp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Today it's a passthrough. Tomorrow, when v2 dispatches by &lt;code&gt;libp2p+webrtc+v1/&lt;/code&gt; / &lt;code&gt;libp2p+webrtc+v2/&lt;/code&gt; ufrag prefixes, this function grows a small branch on the ufrag prefix and everything else in the builder stays intact.&lt;/p&gt;

&lt;p&gt;The maintainer's response, verbatim, was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The v1 foundation here (framing, Noise prologue, certhash, &lt;code&gt;_apply_ice_credentials()&lt;/code&gt; as a seam) is not throwaway — it feeds v2, but &lt;code&gt;_apply_ice_credentials()&lt;/code&gt; will likely evolve into a version-dispatch layer rather than a single swap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the value of a seam. It reserves the space for future evolution without pre-committing to which evolution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing: 153 tests, but only one of them mattered
&lt;/h2&gt;

&lt;p&gt;Unit tests are cheap; they run in ~10 seconds and give you fast feedback on individual modules. Integration tests are expensive; the two-&lt;code&gt;RTCPeerConnection&lt;/code&gt; loopback test spins up an ICE agent, waits for connectivity, and pushes data through the SCTP stack. It takes a few seconds and is annoyingly flaky under heavy CI load.&lt;/p&gt;

&lt;p&gt;But that expensive test caught three bugs the cheap ones missed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bug&lt;/th&gt;
&lt;th&gt;Symptom in unit tests&lt;/th&gt;
&lt;th&gt;Symptom in loopback&lt;/th&gt;
&lt;th&gt;Caught by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;pc._certificates = [...]&lt;/code&gt; no-ops&lt;/td&gt;
&lt;td&gt;✅ Green&lt;/td&gt;
&lt;td&gt;Advertised certhash ≠ SDP fingerprint&lt;/td&gt;
&lt;td&gt;SDP-fingerprint canary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application channels used &lt;code&gt;negotiated=True&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;✅ Green&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;accept_stream()&lt;/code&gt; hangs forever&lt;/td&gt;
&lt;td&gt;Real accept-stream round trip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bare protobuf, no length prefix&lt;/td&gt;
&lt;td&gt;✅ Green&lt;/td&gt;
&lt;td&gt;16 KiB payload gets fragmented, reader dumps garbage&lt;/td&gt;
&lt;td&gt;&amp;gt; 16 KiB echo round-trip&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each of these was &lt;em&gt;reachable&lt;/em&gt; by unit tests only if you knew to look for them, which no one did. That's the lesson of integration testing WebRTC: unit tests are a floor, not a ceiling, and the ceiling has real hardware in it.&lt;/p&gt;

&lt;p&gt;The loopback test's docstring calls this out explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Integration test: two real RTCPeerConnections, end-to-end loopback.

Validates the WebRTC data-channel layer at the wire level — the three
ecosystem-compatibility fixes documented in PR #1309 review notes:

  - In-band data channels (negotiated=False): the responder&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s
    &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;datachannel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; event must fire so WebRTCConnection.accept_stream()
    unblocks.
  - uvarint length-prefixed framing: every send must stay within the
    16 KiB spec ceiling; &amp;gt; 16 KiB payload round-trips through chunking
    and reassembly.
  - DTLS certificate pinning: aiortc &amp;gt;= 1.5 dropped &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;certificates=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; in
    RTCConfiguration; the cert override must take effect before
    createOffer/createAnswer so the local DTLS fingerprint in the SDP
    matches the one our peer expects.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If somebody reads this file six months from now, they know exactly &lt;em&gt;why&lt;/em&gt; those three assertions exist. That's the value of leaving the review context in the code, not just in the PR comments.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the review looked like from the inside
&lt;/h2&gt;

&lt;p&gt;Four review pushes on top of the initial scaffolding, roughly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Correctness pass.&lt;/strong&gt; Copilot flagged 22 inline issues, mostly F401 unused imports and one substantial one — that &lt;code&gt;dial()&lt;/code&gt; was returning connections without actually running ICE/DTLS/Noise. We raised &lt;code&gt;NotImplementedError&lt;/code&gt; on the paths that weren't wired yet, so a caller would fail loudly instead of getting a phantom connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wire the transport into aiortc.&lt;/strong&gt; This is where the cert-pin bug lived, silent, until we started running real handshakes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framing + in-band channels + cert pin.&lt;/strong&gt; The three bugs above, all in one series of commits. The commit messages are worth reading — each one names exactly what was wrong and why the fix is right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-PC loopback test + newsfragment reframe.&lt;/strong&gt; Landing the integration test that guards the three fixes, and rewriting the newsfragment so "experimental v1 node-to-node scaffolding" is the first thing a changelog reader sees.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The maintainer's approving comment, in full:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Merging this as &lt;strong&gt;experimental WebRTC&lt;/strong&gt; scaffolding behind &lt;code&gt;libp2p[webrtc]&lt;/code&gt; + &lt;code&gt;enable_webrtc&lt;/code&gt;. The critical-path fixes from the re-review (uvarint framing, in-band channels + DCEP wait, cert pinning, loopback tests, docstring/newsfragment accuracy) are in place, and CI is green.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#546 stays open&lt;/strong&gt; (&lt;code&gt;Refs #546&lt;/code&gt; — do not auto-close on merge). This PR advances the parent issue but does not satisfy it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last line matters. Issue #546 is "WebRTC support in py-libp2p." This PR is not that. This PR is &lt;em&gt;scaffolding for&lt;/em&gt; that. The follow-ups — browser interop, go/js interop, full private dial — are separate issues that get their own PRs on this foundation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where it goes next
&lt;/h2&gt;

&lt;p&gt;The concrete follow-ups, in the order they'll probably ship:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;STUN &lt;code&gt;USERNAME&lt;/code&gt; exposure spike in aiortc.&lt;/strong&gt; Go and JS &lt;code&gt;/webrtc-direct&lt;/code&gt; listeners reconstruct the SDP offer from the STUN username on the inbound packet. Our listener uses an HTTP &lt;code&gt;POST /sdp&lt;/code&gt; exchange as a py-to-py harness. If aiortc exposes the inbound STUN username via a hook, our listener can switch and interop lights up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private &lt;code&gt;/webrtc&lt;/code&gt; dial.&lt;/strong&gt; The listener is here, the signaling protocol is here, the &lt;code&gt;WebRTCPrivateTransport&lt;/code&gt; is here. The dial path currently constructs and returns a connection without running relay signaling — that's the next PR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version dispatch in &lt;code&gt;_apply_ice_credentials()&lt;/code&gt;.&lt;/strong&gt; When &lt;code&gt;libp2p+webrtc+v1/&lt;/code&gt; and &lt;code&gt;libp2p+webrtc+v2/&lt;/code&gt; ufrag prefixes are standardized, that function grows a branch and &lt;code&gt;WebRTCDirectListener&lt;/code&gt; learns to look at the prefix. v2 is the browser-compatible path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interop tests against go-libp2p and js-libp2p.&lt;/strong&gt; These will fail today, deliberately. When the STUN username hook lands, they start passing, and we know we're on the ecosystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are architecturally scary. The scary architecture is what this PR did: two runtimes, one wire format, one lifecycle, three specs. That's done.&lt;/p&gt;




&lt;h2&gt;
  
  
  The one takeaway I want you to leave with
&lt;/h2&gt;

&lt;p&gt;If you're building anything that crosses two runtimes, two libraries, or two specs, &lt;strong&gt;the seams are the API&lt;/strong&gt;. Not the surface API — the &lt;em&gt;internal&lt;/em&gt; seams. Where does ICE credential handling live? One function. Where does the DTLS cert pin land? One helper. Where does the trio ↔ asyncio boundary get crossed? One class.&lt;/p&gt;

&lt;p&gt;The maintainer review didn't just catch bugs. It repeatedly pushed us toward &lt;em&gt;fewer&lt;/em&gt; places where the interesting decisions were expressed. Every review round pulled scattered logic into a named function, so that when the next spec revision lands (v1 → v2, &lt;code&gt;specs#672&lt;/code&gt;, &lt;code&gt;specs#715&lt;/code&gt;), the change is localized.&lt;/p&gt;

&lt;p&gt;Scaffolding isn't just "the parts that don't work yet." Scaffolding is &lt;em&gt;the shape of the seams&lt;/em&gt;, so the parts you fill in later fit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it, and honest expectations
&lt;/h2&gt;

&lt;p&gt;You can install and inspect it today:&lt;br&gt;
&lt;/p&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; &lt;span class="s1"&gt;'libp2p[webrtc]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in code:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;new_host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enable_webrtc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ...multiaddr: /ip4/…/udp/…/webrtc-direct/certhash/u…/p2p/…
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Realistic expectations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Two Python peers talking &lt;code&gt;/webrtc-direct&lt;/code&gt; on a loopback interface will work.&lt;/li&gt;
&lt;li&gt;⚠️ Python peer ↔ Go peer or Python peer ↔ JS peer over &lt;code&gt;/webrtc-direct&lt;/code&gt; will &lt;em&gt;not&lt;/em&gt; work yet (the STUN username reconstruction gap).&lt;/li&gt;
&lt;li&gt;❌ Browser peer dialing a Python peer will not work. That's v2's job.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to help, the follow-up sub-issues under &lt;a href="https://github.com/libp2p/py-libp2p/issues/546" rel="noopener noreferrer"&gt;#546&lt;/a&gt; are the map. The seams are ready. The wires need connecting.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/libp2p/specs/tree/master/webrtc" rel="noopener noreferrer"&gt;libp2p WebRTC spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/libp2p/specs/blob/master/webrtc/webrtc-direct.md" rel="noopener noreferrer"&gt;libp2p WebRTC Direct spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/libp2p/specs/pull/585" rel="noopener noreferrer"&gt;libp2p/specs#585 — bilateral ICE_DONE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/libp2p/specs/issues/672" rel="noopener noreferrer"&gt;libp2p/specs#672 — ICE credential munging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/libp2p/specs/pull/715" rel="noopener noreferrer"&gt;libp2p/specs#715 — WebRTC v2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/libp2p/js-libp2p/pull/3480" rel="noopener noreferrer"&gt;js-libp2p#3480 — reference v2 implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/libp2p/py-libp2p/pull/1309" rel="noopener noreferrer"&gt;py-libp2p#1309 — this PR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/libp2p/py-libp2p/issues/546" rel="noopener noreferrer"&gt;py-libp2p#546 — parent issue, still open&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aiortc/aiortc" rel="noopener noreferrer"&gt;aiortc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://trio.readthedocs.io/" rel="noopener noreferrer"&gt;trio&lt;/a&gt; — structured concurrency&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Written by &lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;Yash Kumar Saini&lt;/a&gt; — reflections on a merged py-libp2p PR. Corrections and pushback welcome; I'll update this post in place if I got anything wrong. The PR itself is the source of truth.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>libp2p</category>
      <category>webrtc</category>
      <category>python</category>
      <category>networking</category>
    </item>
    <item>
      <title>Hardening Kademlia DHT: The Eclipse Attack That Record Signing Doesn't Stop</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Thu, 09 Jul 2026 05:34:38 +0000</pubDate>
      <link>https://dev.to/yashksaini/hardening-kademlia-dht-the-eclipse-attack-that-record-signing-doesnt-stop-2k79</link>
      <guid>https://dev.to/yashksaini/hardening-kademlia-dht-the-eclipse-attack-that-record-signing-doesnt-stop-2k79</guid>
      <description>&lt;h1&gt;
  
  
  Hardening Kademlia DHT: The Eclipse Attack That Record Signing Doesn't Stop
&lt;/h1&gt;

&lt;p&gt;A few days ago I published &lt;a href="https://dev.to/yashksaini/dev-log-9-hardening-kademlia-dht-and-automating-the-neovim-grind-43f3"&gt;Dev Log #9&lt;/a&gt;, which covered a security fix I landed in py-libp2p: binding a signed PeerRecord to its signer's identity so an attacker can't relay another peer's record as their own.&lt;/p&gt;

&lt;p&gt;Then this comment came in from Valentyn Kit, a systems engineer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Binding the record to its signer kills the forged-record path, nice. The attack that tends to show up right after in Kademlia is eclipse/Sybil, where nobody forges anything: libp2p IDs are already pubkey-derived, but nothing makes it costly to mint a batch of valid IDs that land closest to a target key and quietly own that slice of the routing table. Curious whether py-libp2p has appetite for the S/Kademlia counters (crypto-puzzle IDs + disjoint lookup paths), or if that's considered out of scope for the DHT."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is one of those comments where someone hands you a full threat model in two sentences. I spent the next few days digging into it — reading the &lt;a href="https://doi.org/10.1109/NCA.2007.1" rel="noopener noreferrer"&gt;S/Kademlia paper&lt;/a&gt;, tracing through py-libp2p's routing code, and checking what rust-libp2p has already shipped. Here's what I found.&lt;/p&gt;




&lt;h2&gt;
  
  
  What my fix actually closed
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/libp2p/py-libp2p/pull/1338" rel="noopener noreferrer"&gt;PR #1338&lt;/a&gt; closed the &lt;strong&gt;record-forgery path&lt;/strong&gt;. Before the fix, an attacker could inject a signed PeerRecord into the DHT claiming to represent peer X — even without X's private key. The fix ties the envelope's signer identity to the record payload, so &lt;code&gt;maybe_consume_signed_record&lt;/code&gt; now rejects any record where those don't match.&lt;/p&gt;

&lt;p&gt;That's a real fix. But Valentyn is pointing at something it doesn't touch at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  The attack that signing doesn't stop
&lt;/h2&gt;

&lt;p&gt;Here's the thing: the eclipse/Sybil attack doesn't need to forge anything.&lt;/p&gt;

&lt;p&gt;A libp2p peer ID is &lt;code&gt;multihash(pubkey)&lt;/code&gt;. Generating a keypair takes microseconds. There is nothing preventing anyone from generating 10,000 valid, spec-compliant peer IDs in a few seconds.&lt;/p&gt;

&lt;p&gt;Now here's why that matters for Kademlia. Kademlia routes by XOR distance — every node's routing table stores the peers closest to it in XOR space. For any target key &lt;code&gt;T&lt;/code&gt;, I can generate keypairs in bulk, compute each one's XOR distance to &lt;code&gt;T&lt;/code&gt;, and keep the ones that land closest. With enough trials I get 20 IDs that cluster tighter around &lt;code&gt;T&lt;/code&gt; than any honest node. I connect them to the network, let them bootstrap normally, and now every honest node's routing table has my attacker nodes occupying the "closest to &lt;code&gt;T&lt;/code&gt;" slots.&lt;/p&gt;

&lt;p&gt;From that point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any &lt;code&gt;FIND_NODE(T)&lt;/code&gt; returns my nodes as the nearest&lt;/li&gt;
&lt;li&gt;Any &lt;code&gt;GET_VALUE(T)&lt;/code&gt; routes through my nodes first&lt;/li&gt;
&lt;li&gt;My nodes return each other as "even closer" — it's a closed loop&lt;/li&gt;
&lt;li&gt;The honest node has no way to detect this&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And critically: &lt;strong&gt;every message is signed by a valid keypair&lt;/strong&gt;. The fix I landed passes all my attacker messages just fine, because they're all authentic. The attack lives in the routing layer, not the record layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What S/Kademlia proposes
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://doi.org/10.1109/NCA.2007.1" rel="noopener noreferrer"&gt;S/Kademlia paper (Baumgart &amp;amp; Mies, 2007)&lt;/a&gt; defines two specific counters for this class of attack.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Crypto-puzzle IDs
&lt;/h3&gt;

&lt;p&gt;Instead of just &lt;code&gt;PeerID = H(pubkey)&lt;/code&gt;, require that the ID also satisfies a proof-of-work constraint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PeerID = H(pubkey)
H(H(pubkey), nonce) &amp;lt; D     # PoW puzzle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To generate an ID that lands near a specific target in XOR space, an attacker now has to solve &lt;code&gt;2^d&lt;/code&gt; extra puzzles per attempt. Minting thousands of targeted IDs goes from milliseconds to hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem: this is a dead end for py-libp2p.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;libp2p peer IDs are specified uniformly across all implementations — &lt;a href="https://github.com/libp2p/go-libp2p" rel="noopener noreferrer"&gt;go-libp2p&lt;/a&gt;, &lt;a href="https://github.com/libp2p/rust-libp2p" rel="noopener noreferrer"&gt;rust-libp2p&lt;/a&gt;, &lt;a href="https://github.com/libp2p/js-libp2p" rel="noopener noreferrer"&gt;js-libp2p&lt;/a&gt;, nim-libp2p. They're all &lt;code&gt;multihash(pubkey)&lt;/code&gt; with no additional constraints. If py-libp2p started rejecting peers without PoW IDs it would disconnect from every other node in the ecosystem. This needs a spec-level change with buy-in from every implementation. Not something one library can ship unilaterally.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Disjoint lookup paths
&lt;/h3&gt;

&lt;p&gt;This one is different — and it's in scope.&lt;/p&gt;

&lt;p&gt;Instead of one iterative lookup, run &lt;code&gt;d ≥ 2&lt;/code&gt; &lt;strong&gt;independent&lt;/strong&gt; lookups, each starting from a disjoint partition of your initial closest-peer set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Path 1 starts from peers &lt;code&gt;[p1, p2, ..., pk/2]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Path 2 starts from peers &lt;code&gt;[pk/2+1, ..., pk]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Peers discovered on path 1 are never introduced to path 2's candidate set, and vice versa&lt;/li&gt;
&lt;li&gt;Results merge only at the end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To eclipse this, an attacker now needs to dominate &lt;em&gt;both&lt;/em&gt; paths simultaneously. With &lt;code&gt;d = 2&lt;/code&gt; and bucket size &lt;code&gt;k = 20&lt;/code&gt;, that's 40 targeted IDs instead of 20. With &lt;code&gt;d = 3&lt;/code&gt; it's 60. Each path multiplies the cost linearly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The important thing: this requires zero protocol changes.&lt;/strong&gt; It uses the existing &lt;code&gt;FIND_NODE&lt;/code&gt; wire format. Other nodes don't need to implement it. It's a pure algorithmic change to how the lookup query is structured — which means it's entirely within the scope of a single-library contribution to py-libp2p.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where py-libp2p stands today
&lt;/h2&gt;

&lt;p&gt;I went through the routing code carefully. Here's the honest state:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single-path iterative lookup&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/libp2p/py-libp2p/blob/main/libp2p/kad_dht/peer_routing.py" rel="noopener noreferrer"&gt;&lt;code&gt;kad_dht/peer_routing.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Implemented — one candidate set, converges on a single path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;k-bucket with liveness eviction&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/libp2p/py-libp2p/blob/main/libp2p/kad_dht/routing_table.py" rel="noopener noreferrer"&gt;&lt;code&gt;kad_dht/routing_table.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Implemented — pings oldest peer before evicting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sliding-window ALPHA concurrency&lt;/td&gt;
&lt;td&gt;&lt;code&gt;peer_routing.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Implemented — I shipped this in &lt;a href="https://github.com/libp2p/py-libp2p/pull/1274" rel="noopener noreferrer"&gt;PR #1274&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eclipse attack simulation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/libp2p/py-libp2p/tree/main/tests/examples/attack_simulation/eclipse_attack" rel="noopener noreferrer"&gt;&lt;code&gt;tests/examples/attack_simulation/eclipse_attack/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Simulation only — fake string IDs, never touches the real routing table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sybil attack simulation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/libp2p/py-libp2p/tree/main/tests/examples/attack_simulation/sybil_attack" rel="noopener noreferrer"&gt;&lt;code&gt;tests/examples/attack_simulation/sybil_attack/&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Simulation only — IDs are Python strings like &lt;code&gt;"peer_sybil_0"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP/subnet diversity in k-buckets&lt;/td&gt;
&lt;td&gt;&lt;code&gt;routing_table.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Missing&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disjoint lookup paths&lt;/td&gt;
&lt;td&gt;&lt;code&gt;peer_routing.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Missing&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any actual Eclipse/Sybil defense&lt;/td&gt;
&lt;td&gt;anywhere&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Missing&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the one that matters. The simulation code — merged under &lt;a href="https://github.com/libp2p/py-libp2p/issues/57" rel="noopener noreferrer"&gt;issue #57&lt;/a&gt; via &lt;a href="https://github.com/libp2p/py-libp2p/pull/950" rel="noopener noreferrer"&gt;PR #950&lt;/a&gt; — shows what an attack looks like but doesn't implement any counter-measure. The &lt;code&gt;MaliciousPeer&lt;/code&gt; class in &lt;code&gt;malicious_peer.py&lt;/code&gt; creates fake IDs as Python strings; it never touches a real &lt;code&gt;KBucket&lt;/code&gt; or &lt;code&gt;RoutingTable&lt;/code&gt;. And issue #57 itself is still open — PR #950 never formally closed it.&lt;/p&gt;

&lt;p&gt;The gap between "we have simulations" and "we have defenses" is exactly where the contribution lives.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I want to build
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For py-libp2p: disjoint lookup paths
&lt;/h3&gt;

&lt;p&gt;The target is &lt;code&gt;find_closest_peers_network&lt;/code&gt; in &lt;a href="https://github.com/libp2p/py-libp2p/blob/main/libp2p/kad_dht/peer_routing.py" rel="noopener noreferrer"&gt;&lt;code&gt;peer_routing.py&lt;/code&gt;&lt;/a&gt;. Right now it maintains one shared &lt;code&gt;closest_peers&lt;/code&gt; list and one shared &lt;code&gt;queried_peers&lt;/code&gt; set. All ALPHA-concurrent queries feed into the same pool. If an attacker owns the closest-k slots for a target, every round of the lookup converges deeper into the eclipse.&lt;/p&gt;

&lt;p&gt;The change I'm planning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Partition the initial closest-peer set into &lt;code&gt;d&lt;/code&gt; disjoint subsets (configurable, default &lt;code&gt;d = 1&lt;/code&gt; to preserve current behaviour)&lt;/li&gt;
&lt;li&gt;Spawn &lt;code&gt;d&lt;/code&gt; parallel but isolated iterative lookups — each with its own candidate list and queried-peers tracker&lt;/li&gt;
&lt;li&gt;No peer crosses from one path to another mid-lookup&lt;/li&gt;
&lt;li&gt;Merge results by XOR distance at the end&lt;/li&gt;
&lt;li&gt;Expose &lt;code&gt;disjoint_paths: int = 1&lt;/code&gt; on &lt;code&gt;KadDHT.__init__&lt;/code&gt; so users opt in to &lt;code&gt;d = 2&lt;/code&gt; or &lt;code&gt;d = 3&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Files involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/libp2p/py-libp2p/blob/main/libp2p/kad_dht/peer_routing.py" rel="noopener noreferrer"&gt;&lt;code&gt;libp2p/kad_dht/peer_routing.py&lt;/code&gt;&lt;/a&gt; — the lookup refactor&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/libp2p/py-libp2p/blob/main/libp2p/kad_dht/kad_dht.py" rel="noopener noreferrer"&gt;&lt;code&gt;libp2p/kad_dht/kad_dht.py&lt;/code&gt;&lt;/a&gt; — expose the parameter&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/libp2p/py-libp2p/blob/main/libp2p/kad_dht/common.py" rel="noopener noreferrer"&gt;&lt;code&gt;libp2p/kad_dht/common.py&lt;/code&gt;&lt;/a&gt; — add &lt;code&gt;DISJOINT_LOOKUP_PATHS = 1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This would be the first actual eclipse mitigation (not simulation) in py-libp2p.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secondary: IP/subnet diversity in k-buckets
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;KBucket.add_peer&lt;/code&gt; in &lt;a href="https://github.com/libp2p/py-libp2p/blob/main/libp2p/kad_dht/routing_table.py" rel="noopener noreferrer"&gt;&lt;code&gt;routing_table.py&lt;/code&gt;&lt;/a&gt;, before accepting a new peer, check whether its &lt;code&gt;/24&lt;/code&gt; subnet already has &lt;code&gt;MAX_PEERS_PER_SUBNET&lt;/code&gt; entries. If yes, reject rather than evict an existing peer. This forces an eclipse attacker to control IPs across many different subnets — not just many keypairs from a single VPS.&lt;/p&gt;

&lt;p&gt;go-libp2p enforces &lt;code&gt;/16&lt;/code&gt; subnet diversity. rust-libp2p currently has nothing at this level (more on that below). ~30–50 lines of Python.&lt;/p&gt;




&lt;h2&gt;
  
  
  What rust-libp2p has — and what it's missing
&lt;/h2&gt;

&lt;p&gt;I checked &lt;a href="https://github.com/libp2p/rust-libp2p/tree/master/protocols/kad/src" rel="noopener noreferrer"&gt;rust-libp2p's Kademlia implementation&lt;/a&gt; while researching this, because it's the most production-hardened libp2p implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The good news:&lt;/strong&gt; rust-libp2p has had disjoint lookup paths since &lt;a href="https://github.com/libp2p/rust-libp2p/blob/master/protocols/kad/CHANGELOG.md" rel="noopener noreferrer"&gt;v0.20.0 (2020)&lt;/a&gt;. The config flag is &lt;a href="https://docs.rs/libp2p-kad/latest/libp2p_kad/struct.Config.html#method.disjoint_query_paths" rel="noopener noreferrer"&gt;&lt;code&gt;disjoint_query_paths(true)&lt;/code&gt;&lt;/a&gt; on &lt;code&gt;libp2p_kad::Config&lt;/code&gt;. The number of paths equals the &lt;code&gt;set_parallelism()&lt;/code&gt; value (ALPHA).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bad news:&lt;/strong&gt; it's opt-in, and almost no application enables it explicitly. More importantly, rust-libp2p has &lt;strong&gt;no IP/subnet diversity in &lt;a href="https://github.com/libp2p/rust-libp2p/blob/master/protocols/kad/src/kbucket.rs" rel="noopener noreferrer"&gt;&lt;code&gt;kbucket.rs&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; at all. The &lt;code&gt;kbucket_inserts&lt;/code&gt; strategy only controls whether connected or disconnected peers are preferred — not IP locality. An attacker with a /24 full of targeted IDs faces zero additional resistance.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;py-libp2p&lt;/th&gt;
&lt;th&gt;rust-libp2p&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Disjoint lookup paths&lt;/td&gt;
&lt;td&gt;Missing&lt;/td&gt;
&lt;td&gt;Present, opt-in, off by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP/subnet diversity&lt;/td&gt;
&lt;td&gt;Missing&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Missing&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eclipse attack testbed (real nodes)&lt;/td&gt;
&lt;td&gt;Simulation only&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Rust project: &lt;code&gt;kad-eclipse-guard&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Because rust-libp2p is missing the subnet diversity piece, and because I want empirical data before writing the py-libp2p PR, I'm planning a standalone Rust crate that uses &lt;a href="https://github.com/libp2p/rust-libp2p" rel="noopener noreferrer"&gt;rust-libp2p&lt;/a&gt; as a dependency.&lt;/p&gt;

&lt;p&gt;The idea: build a real testbed that proves the attack works, shows what each defense does to the success rate, and ships a &lt;code&gt;SubnetDiversityFilter&lt;/code&gt; behaviour that can eventually be proposed upstream.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"kad-eclipse-guard"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2021"&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;libp2p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.55"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"kad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"noise"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"yamux"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"identify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"macros"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;tokio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"full"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;tracing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1"&lt;/span&gt;
&lt;span class="py"&gt;tracing-subscriber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"env-filter"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;clap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"derive"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"derive"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;
&lt;span class="py"&gt;ipnet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt;
&lt;span class="py"&gt;rand&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.8"&lt;/span&gt;
&lt;span class="py"&gt;sha2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.10"&lt;/span&gt;
&lt;span class="py"&gt;libp2p-identity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What it builds
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Targeted ID generator&lt;/strong&gt; (&lt;code&gt;src/attack/targeted_id_gen.rs&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Generates Ed25519 keypairs whose &lt;code&gt;PeerId&lt;/code&gt; lands XOR-closest to a target key. This is the core of the attack proof — 100k keypair trials in ~200ms, take the 20 closest to target T, done.&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;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;TargetedIdGenerator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;target_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PeerId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;best_n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;TargetedIdGenerator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Keypair&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&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;&lt;strong&gt;Subnet diversity filter&lt;/strong&gt; (&lt;code&gt;src/hardening/subnet_diversity.rs&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;NetworkBehaviour&lt;/code&gt; wrapper around &lt;code&gt;libp2p_kad::Behaviour&lt;/code&gt; that enforces per-subnet caps at the &lt;code&gt;NetworkBehaviour&lt;/code&gt; composition layer — no changes needed to upstream kad internals.&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;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;SubnetDiversityFilter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TStore&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;libp2p_kad&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Behaviour&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TStore&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subnet_counts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IpNet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_per_subnet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// default: 2 per /24&lt;/span&gt;
    &lt;span class="n"&gt;prefix_len_v4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// default: 24&lt;/span&gt;
    &lt;span class="n"&gt;prefix_len_v6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// default: 64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;libp2p_kad&lt;/code&gt; fires &lt;code&gt;RoutingUpdated&lt;/code&gt; for a new peer, the filter checks the peer's IP subnet against &lt;code&gt;subnet_counts&lt;/code&gt;. If the subnet is saturated, it emits &lt;code&gt;RemovePeer&lt;/code&gt; back to the inner behaviour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testbed&lt;/strong&gt; (&lt;code&gt;src/testbed/network.rs&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Runs 4 configurations with real rust-libp2p nodes over TCP:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Config&lt;/th&gt;
&lt;th&gt;&lt;code&gt;disjoint_query_paths&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;SubnetDiversityFilter&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disjoint only&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diversity only&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardened&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Metrics collected per config: routing table contamination ratio, lookup success rate, time-to-eclipse (TTFE).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CLI&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;USAGE:
    kad-eclipse-guard [OPTIONS]

OPTIONS:
&lt;/span&gt;&lt;span class="gp"&gt;    --honest-peers &amp;lt;N&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;default: 50]
&lt;span class="gp"&gt;    --attacker-peers &amp;lt;M&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;default: 30]
&lt;span class="gp"&gt;    --trials &amp;lt;T&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;keypair generation trials &lt;span class="o"&gt;[&lt;/span&gt;default: 100000]
&lt;span class="gp"&gt;    --config &amp;lt;C&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;baseline|disjoint|diversity|hardened|all
&lt;span class="gp"&gt;    --output &amp;lt;FILE&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;JSON metrics output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What this proves
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The attack is cheap.&lt;/strong&gt; 100k keypair trials → 20 targeted IDs in ~200ms. No special hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disjoint paths help but multiply rather than block.&lt;/strong&gt; With &lt;code&gt;parallelism=3&lt;/code&gt; and disjoint enabled, the attacker needs 3× the IDs — still microseconds per ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnet diversity raises the cost meaningfully.&lt;/strong&gt; Filling a bucket of 20 across /24 caps of 2 requires IPs in at least 10 different subnets — that's a real operational barrier, not a CPU barrier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combined is strongest.&lt;/strong&gt; Attacker must simultaneously control many subnets AND eclipse all &lt;code&gt;d&lt;/code&gt; independent paths.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What's off the table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Idea&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Crypto-puzzle peer IDs&lt;/td&gt;
&lt;td&gt;Requires spec change + buy-in from all libp2p implementations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reputation / trust scoring&lt;/td&gt;
&lt;td&gt;No spec, breaks with churn, too complex to get right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proof-of-work ID validation&lt;/td&gt;
&lt;td&gt;Would reject every non-py-libp2p peer on the network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DHT access control&lt;/td&gt;
&lt;td&gt;Contradicts libp2p's open-participation design&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The contribution path
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Build and benchmark &lt;code&gt;kad-eclipse-guard&lt;/code&gt; with real rust-libp2p nodes — get hard numbers.&lt;/li&gt;
&lt;li&gt;Open a &lt;a href="https://github.com/libp2p/rust-libp2p" rel="noopener noreferrer"&gt;rust-libp2p&lt;/a&gt; discussion proposing &lt;code&gt;SubnetDiversityFilter&lt;/code&gt; as a companion behaviour or eventual built-in, backed by the testbed metrics.&lt;/li&gt;
&lt;li&gt;Open a &lt;a href="https://github.com/libp2p/py-libp2p" rel="noopener noreferrer"&gt;py-libp2p&lt;/a&gt; issue for disjoint lookup paths, referencing &lt;a href="https://github.com/libp2p/py-libp2p/issues/57" rel="noopener noreferrer"&gt;issue #57&lt;/a&gt; — gauge maintainer appetite before writing code.&lt;/li&gt;
&lt;li&gt;Once there's signal, open the PR. Disjoint paths first (higher impact, no protocol change), subnet diversity second (smaller, easier to review standalone).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Valentyn's comment identified something real: the fix I shipped in &lt;a href="https://github.com/libp2p/py-libp2p/pull/1338" rel="noopener noreferrer"&gt;PR #1338&lt;/a&gt; closes one attack surface but leaves the routing layer undefended. Record authentication and routing table hardening are separate problems, and py-libp2p currently has zero actual defenses for the second one — only simulations that never touch the real &lt;code&gt;KBucket&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The path forward is concrete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disjoint lookup paths in &lt;code&gt;peer_routing.py&lt;/code&gt; — in scope, no protocol changes, the biggest impact&lt;/li&gt;
&lt;li&gt;Subnet diversity in &lt;code&gt;routing_table.py&lt;/code&gt; — smaller, modeled on go-libp2p's approach&lt;/li&gt;
&lt;li&gt;A Rust testbed to build the empirical case first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/libp2p/py-libp2p/issues/57" rel="noopener noreferrer"&gt;Issue #57&lt;/a&gt; has been open since 2018. It asked for attack simulation (done, via &lt;a href="https://github.com/libp2p/py-libp2p/pull/950" rel="noopener noreferrer"&gt;PR #950&lt;/a&gt;) and security assessment (not done). The actual hardening is still waiting.&lt;/p&gt;

</description>
      <category>kademlia</category>
      <category>libp2p</category>
      <category>rust</category>
      <category>security</category>
    </item>
    <item>
      <title>Shipping Docs, Rust Tooling, and a Week of Deep Reviews</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Tue, 07 Jul 2026 15:01:14 +0000</pubDate>
      <link>https://dev.to/yashksaini/shipping-docs-rust-tooling-and-a-week-of-deep-reviews-9i6</link>
      <guid>https://dev.to/yashksaini/shipping-docs-rust-tooling-and-a-week-of-deep-reviews-9i6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;14 commits, 2 PRs, 1 issue, and 5 reviews across 3 repos. This week was all about hardening my Rust CLI 'trx' with mdBook and format checks, while doing some heavy lifting on Python backend reviews for the community.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;It’s been a solid, high-energy week. I managed to hit a 7-day commit streak, which always feels great when the flow state actually sticks. The stats tell a story of expansion: 14 commits, 2 merged PRs, and 5 deep-dive reviews. I spent most of my "writing" time in Rust, specifically getting the documentation and CI for &lt;code&gt;trx&lt;/code&gt; into a professional state, but my "thinking" time was heavily invested in Python backend architecture through some gnarly code reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT I BUILT
&lt;/h2&gt;

&lt;p&gt;The star of the show this week was &lt;strong&gt;trx&lt;/strong&gt;, my Rust-based project. I’ve been pushing this one forward for a while, but this week I realized that code without documentation is just a riddle for my future self. I spent a significant chunk of time setting up a proper documentation site using mdBook. &lt;/p&gt;

&lt;p&gt;If you look at the numbers, I added over 1,500 lines and only deleted about 160. Usually, I’m a fan of the "less is more" refactoring approach, but this was a pure growth week. Most of those additions were the &lt;a href="https://github.com/pie-314/trx/pull/22" rel="noopener noreferrer"&gt;mdBook documentation site&lt;/a&gt;. I wanted a place where users (and I) could actually see the vision for the tool, and mdBook is just so snappy for Rust projects. It feels right.&lt;/p&gt;

&lt;p&gt;I also spent some time on the "boring but important" stuff for &lt;code&gt;trx&lt;/code&gt;. I &lt;a href="https://github.com/pie-314/trx/pull/13" rel="noopener noreferrer"&gt;integrated format checks&lt;/a&gt; into the workflow. There’s nothing that kills my momentum faster than a PR that fails because of a stray space or a missing newline, so getting &lt;code&gt;cargo fmt&lt;/code&gt; enforced in CI was a priority. It’s one of those things you do once and thank yourself for every day after.&lt;/p&gt;

&lt;p&gt;Outside of the Rust world, I did some maintenance on my &lt;strong&gt;nvim&lt;/strong&gt; config and &lt;strong&gt;GitBanner&lt;/strong&gt;. For &lt;code&gt;nvim&lt;/code&gt;, it was mostly Lua-based tweaks to keep my environment feeling fast. I’m a firm believer that your editor should evolve with your workflow, so I’m constantly pruning and grafting my config. &lt;code&gt;GitBanner&lt;/code&gt; saw a couple of TypeScript commits as well—just keeping the lights on and ensuring everything is running smoothly.&lt;/p&gt;

&lt;h2&gt;
  
  
  PULL REQUESTS
&lt;/h2&gt;

&lt;p&gt;I kept the PR count low but the impact high this week. Both of my PRs were in the &lt;code&gt;trx&lt;/code&gt; repo and both are now merged. &lt;/p&gt;

&lt;p&gt;The first was the &lt;a href="https://github.com/pie-314/trx/pull/22" rel="noopener noreferrer"&gt;documentation overhaul&lt;/a&gt;. Adding 1,281 lines of documentation might sound like a slog, but it actually helped me find a few inconsistencies in how I was thinking about the CLI's API. Writing about your code is the best way to realize where your code is confusing.&lt;/p&gt;

&lt;p&gt;The second was the &lt;a href="https://github.com/pie-314/trx/pull/13" rel="noopener noreferrer"&gt;format check PR&lt;/a&gt;. It was a mix of 292 additions and 161 deletions. This wasn't just adding a YAML file for GitHub Actions; it was also about running the formatter across the whole codebase and cleaning up the technical debt that had accumulated while I was "just hacking." It feels much cleaner now.&lt;/p&gt;

&lt;h2&gt;
  
  
  ISSUES &amp;amp; DISCUSSIONS
&lt;/h2&gt;

&lt;p&gt;I opened one new issue this week in &lt;code&gt;trx&lt;/code&gt;: &lt;a href="https://github.com/pie-314/trx/issues/14" rel="noopener noreferrer"&gt;Feature: Add a update keymap option&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;As I was writing the documentation, I realized that the current way I handle keybindings is a bit too rigid. I want users to be able to rebind things without having to recompile the whole world. It’s currently marked as OPEN, and it’s likely going to be my main focus for the upcoming weekend. It’s one of those features that sounds simple until you start thinking about conflict resolution and nested maps.&lt;/p&gt;

&lt;h2&gt;
  
  
  CODE REVIEWS
&lt;/h2&gt;

&lt;p&gt;This was a heavy review week, and honestly, I love that. I gave 5 reviews, which is more than double the number of PRs I opened myself. Most of this work happened over at &lt;strong&gt;opensre&lt;/strong&gt;, a project I’ve been following closely.&lt;/p&gt;

&lt;p&gt;I spent a lot of time looking at Python code this week. One of the more interesting reviews was for a fix in the &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/2047" rel="noopener noreferrer"&gt;Anthropic SDK client&lt;/a&gt;. We were seeing some weird &lt;code&gt;TypeError&lt;/code&gt; issues when an API key was missing, and the PR introduced a "fail fast" mechanism. I’m a huge advocate for failing fast—there’s nothing worse than a silent failure that manifests as a cryptic error ten layers deep in your stack.&lt;/p&gt;

&lt;p&gt;I also reviewed some &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/1424" rel="noopener noreferrer"&gt;AWS session caching logic&lt;/a&gt; and a &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/1905" rel="noopener noreferrer"&gt;psutil probe integration&lt;/a&gt;. The &lt;code&gt;psutil&lt;/code&gt; one was particularly cool—it’s wiring a background sampler into a REPL. It’s the kind of low-level systems work that makes Python feel a lot more powerful than people give it credit for.&lt;/p&gt;

&lt;p&gt;Reviewing other people's code is such a great way to stay sharp. You see patterns you wouldn't have thought of, and you catch bugs that you’ve definitely made yourself in the past. (We’ve all forgotten to validate a host boundary at least once, right? Looking at you, &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/2009" rel="noopener noreferrer"&gt;PR #2009&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  TECH STACK
&lt;/h2&gt;

&lt;p&gt;This week was a polyglot's dream. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Rust&lt;/strong&gt; was my primary language for building, especially with the &lt;code&gt;trx&lt;/code&gt; work. The type safety and the tooling (like &lt;code&gt;cargo fmt&lt;/code&gt; and &lt;code&gt;mdBook&lt;/code&gt;) just make the developer experience so top-tier.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Python&lt;/strong&gt; dominated my review time. Between LLM clients and AWS SDKs, I was deep in the weeds of asynchronous Python and cloud infrastructure.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;TypeScript&lt;/strong&gt; and &lt;strong&gt;Lua&lt;/strong&gt; provided the glue for my personal tools and editor setup. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My add/delete ratio was heavily skewed toward additions (1573 vs 161), which makes sense given the documentation push. It wasn't a "cleanup" week; it was a "foundation" week. And hitting that 7-day streak? That’s the cherry on top. It’s not about the grind; it’s about the rhythm.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT'S NEXT
&lt;/h2&gt;

&lt;p&gt;Next week is going to be all about that keymap feature for &lt;code&gt;trx&lt;/code&gt;. I want to get the configuration logic sorted so I can finally move it out of the "experimental" phase. I’m also planning to dive back into some of the open issues in the &lt;code&gt;opensre&lt;/code&gt; repo—there’s some interesting work happening around agent reliability that I want to be a part of.&lt;/p&gt;

&lt;p&gt;Until then, keep shipping. (And maybe run &lt;code&gt;cargo fmt&lt;/code&gt; before you push—your reviewers will thank you).&lt;/p&gt;




&lt;p&gt;4 min read · Generated 2026-05-17 by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>python</category>
      <category>documentation</category>
      <category>consistency</category>
    </item>
    <item>
      <title>Dev log #6 Networking Deep Dives and Scaling Docs: My 30k Line Week in OSS</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Tue, 07 Jul 2026 14:59:01 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-6-networking-deep-dives-and-scaling-docs-my-30k-line-week-in-oss-3pbb</link>
      <guid>https://dev.to/yashksaini/dev-log-6-networking-deep-dives-and-scaling-docs-my-30k-line-week-in-oss-3pbb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;16 commits, 13 PRs, 4 issues, and 13 reviews across 7 repos. This week was a massive balancing act between deep p2p networking fixes in Python, scaling documentation with mdBook, and a heavy rotation of SRE code reviews.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;It’s been one of those weeks where the line count looks absolutely ridiculous on paper—nearly 30,000 additions—but the reality was a mix of high-level architectural setup and low-level bug hunting. I pushed 16 commits, opened 13 PRs, and stayed in the flow with a perfect 7-day streak. The bulk of the volume came from launching a new documentation site, but the real "brain power" went into debugging IPv6 4-tuple handling in &lt;code&gt;py-libp2p&lt;/code&gt; and refining AI-ready structured outputs for &lt;code&gt;screenpipe&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Push: The Rust Book Simplified
&lt;/h2&gt;

&lt;p&gt;If you look at my stats and see +29,945 lines, don't worry—I didn't suddenly become a superhuman typing machine. Most of that was the heavy lifting involved in setting up &lt;a href="https://github.com/yashksaini-coder/the-rust-book-simplified" rel="noopener noreferrer"&gt;the-rust-book-simplified&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I’ve been wanting to make the Rust book more accessible, and this week I finally sat down to build the infrastructure for it. I started by setting up &lt;code&gt;mdBook&lt;/code&gt; with a custom theme and a landing page that doesn't just look like a standard doc site. In &lt;a href="https://github.com/yashksaini-coder/the-rust-book-simplified/pull/1" rel="noopener noreferrer"&gt;PR #1&lt;/a&gt; and &lt;a href="https://github.com/yashksaini-coder/the-rust-book-simplified/pull/2" rel="noopener noreferrer"&gt;PR #2&lt;/a&gt;, I focused on the themed landing and playground integration. &lt;/p&gt;

&lt;p&gt;There was a bit of a learning curve with the GitHub Pages deployment—I actually had to revert and drop a test step in &lt;a href="https://github.com/yashksaini-coder/the-rust-book-simplified/pull/4" rel="noopener noreferrer"&gt;PR #4&lt;/a&gt; because the CI was being finicky with the &lt;code&gt;mdbook&lt;/code&gt; binary. By &lt;a href="https://github.com/yashksaini-coder/the-rust-book-simplified/pull/5" rel="noopener noreferrer"&gt;PR #5&lt;/a&gt;, I had the core content merged and the site live. It’s a net-positive week for the Rust community, and honestly, seeing those green checkmarks on a fresh docs site is incredibly satisfying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep in the Networking Weeds: py-libp2p
&lt;/h2&gt;

&lt;p&gt;When I wasn't playing with CSS and Markdown, I was back in my natural habitat: p2p networking. I’ve been contributing to &lt;code&gt;py-libp2p&lt;/code&gt; for a while, and this week I hit a particularly gnarly issue with IPv6.&lt;/p&gt;

&lt;p&gt;I opened &lt;a href="https://github.com/libp2p/py-libp2p/issues/1316" rel="noopener noreferrer"&gt;Issue #1316&lt;/a&gt; because the websocket listener was failing to bind on IPv6. The culprit? A classic &lt;code&gt;getsockname()&lt;/code&gt; unpack error. When you're dealing with IPv4, you expect a 2-tuple (address, port). But IPv6 gives you a 4-tuple (address, port, flow info, scope id). If your code isn't expecting those extra two values, everything blows up. I submitted the fix in &lt;a href="https://github.com/libp2p/py-libp2p/pull/1317" rel="noopener noreferrer"&gt;PR #1317&lt;/a&gt;, ensuring we handle the 4-tuple correctly.&lt;/p&gt;

&lt;p&gt;I also spent some time refactoring the test suite. In &lt;a href="https://github.com/libp2p/py-libp2p/pull/1315" rel="noopener noreferrer"&gt;PR #1315&lt;/a&gt;, I moved the &lt;code&gt;subscribed_mesh&lt;/code&gt; fixture to a predicate-based readiness check. If you've ever worked on p2p tests, you know how flaky they can be—waiting for peers to discover each other and settle into a mesh is a recipe for race conditions. Moving to a predicate-based approach makes the tests much more resilient. (And yes, I also closed &lt;a href="https://github.com/libp2p/py-libp2p/issues/1314" rel="noopener noreferrer"&gt;Issue #1314&lt;/a&gt; to remove the &lt;code&gt;nursery&lt;/code&gt; parameter from &lt;code&gt;IListener.listen()&lt;/code&gt;, which was a long-overdue breaking change to clean up the API).&lt;/p&gt;

&lt;h2&gt;
  
  
  SRE Tooling and AI Contexts
&lt;/h2&gt;

&lt;p&gt;Over at &lt;code&gt;opensre&lt;/code&gt;, I was tackling a bug where Azure SQL integrations weren't being populated correctly. It’s one of those silent failures where the tools report as "unavailable" even when the config is perfect. I tracked it down to &lt;code&gt;detect_sources.py&lt;/code&gt; and pushed a fix in &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/722" rel="noopener noreferrer"&gt;PR #722&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I also had a "developer moment" with Grafana. I opened &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/786" rel="noopener noreferrer"&gt;PR #786&lt;/a&gt; to use relative provisioning paths in the local compose setup, realized I’d missed a side effect, and immediately had to revert it in &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/787" rel="noopener noreferrer"&gt;PR #787&lt;/a&gt;. We've all been there—the "fix" that breaks the dev environment for everyone else. Better to revert and rethink than to leave the team stranded.&lt;/p&gt;

&lt;p&gt;On the AI front, I’ve been working on &lt;a href="https://github.com/screenpipe/screenpipe" rel="noopener noreferrer"&gt;screenpipe&lt;/a&gt;. I’m really excited about the Model Context Protocol (MCP) work I’m doing there. In &lt;a href="https://github.com/screenpipe/screenpipe/pull/3072" rel="noopener noreferrer"&gt;PR #3072&lt;/a&gt;, I’m building out a structured output format specifically for AI consumption. The goal is to make the data coming out of screenpipe easily "digestible" for LLMs without a ton of brittle parsing logic. It’s still open, but the foundation is solid.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reviewer's Hat
&lt;/h2&gt;

&lt;p&gt;This week was heavy on the mentorship and review side. I gave 13 reviews, which is almost a 1:1 ratio with my own PRs. Most of my time was spent in the &lt;code&gt;opensre&lt;/code&gt; repo, helping the team move faster.&lt;/p&gt;

&lt;p&gt;I reviewed everything from Victoria Logs integrations (&lt;a href="https://github.com/Tracer-Cloud/opensre/pull/663" rel="noopener noreferrer"&gt;PR #663&lt;/a&gt;) to Apache Airflow evidence gathering (&lt;a href="https://github.com/Tracer-Cloud/opensre/pull/570" rel="noopener noreferrer"&gt;PR #570&lt;/a&gt;). One of the more interesting reviews was &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/780" rel="noopener noreferrer"&gt;PR #780&lt;/a&gt;, which dealt with redacting overlapping spans in guardrails. It’s a tricky logic problem—ensuring you don't leak sensitive "bookends" of data when two redacted sections overlap. &lt;/p&gt;

&lt;p&gt;I also pushed for some better project structure, requesting changes on &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/945" rel="noopener noreferrer"&gt;PR #945&lt;/a&gt; to move the Notion client into a dedicated services directory. Keeping the architecture clean now saves us a world of hurt six months down the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack &amp;amp; Vibe
&lt;/h2&gt;

&lt;p&gt;This was a very Python-heavy week, but the "lines changed" tell a story of two different worlds. On one hand, I was doing high-volume work in Rust and TypeScript for the docs and AI layers. On the other, I was doing surgical, low-line-count fixes in Python and C (shoutout to the 1 commit in &lt;code&gt;PiEngine&lt;/code&gt;—sometimes you just need to tweak one thing in the core).&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;nvim&lt;/code&gt; config also got some love with 7 commits. No major additions there, just constant sharpening of the saw. If my editor isn't feeling right, my code doesn't feel right.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language Mix:&lt;/strong&gt; Python (89M bytes processed), TypeScript, and Rust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add/Delete Ratio:&lt;/strong&gt; Massive additions due to the &lt;code&gt;mdBook&lt;/code&gt; launch. It’s a "building" phase, not a "cleaning" phase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streak:&lt;/strong&gt; 7 days. The momentum is real.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Next week, I’m focusing on getting that MCP structured output merged in &lt;code&gt;screenpipe&lt;/code&gt;. I also want to circle back to &lt;code&gt;py-libp2p&lt;/code&gt; and see if there are more IPv6 edge cases hiding in the other transport layers. If this week was about breadth (docs, networking, SRE, AI), next week is going to be about depth. &lt;/p&gt;

&lt;p&gt;Catch you on the next push!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Total Commits: 16 | PRs: 13 | Issues: 4 | Reviews: 13&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Streak: 7 days of shipping.&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  python #rust #open-source #backend #consistency · 4 min read · Generated 2026-04-26 by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;
&lt;/h1&gt;

</description>
      <category>python</category>
      <category>rust</category>
      <category>opensource</category>
      <category>backend</category>
    </item>
    <item>
      <title>Dev log #11 Kanagawa Dreams and Test Suites: A Week of Refactoring my Neovim and Hardening the Backend</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Tue, 07 Jul 2026 04:30:43 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-11-kanagawa-dreams-and-test-suites-a-week-of-refactoring-my-neovim-and-hardening-the-nb3</link>
      <guid>https://dev.to/yashksaini/dev-log-11-kanagawa-dreams-and-test-suites-a-week-of-refactoring-my-neovim-and-hardening-the-nb3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Hit a perfect 7-day streak this week, splitting my time between a massive aesthetic pivot in my Neovim config and a relentless testing marathon for openslate. With 22 commits and 10 PRs, I managed to delete over 2,000 lines of code while shipping a more stable integration layer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;There is something deeply satisfying about a week that ends with a net-positive refactor and a perfect 7-day streak. I spent the last seven days oscillating between the purely aesthetic—tearing down and rebuilding my Neovim environment—and the purely functional—writing enough integration tests for &lt;code&gt;openslate&lt;/code&gt; to sleep soundly at night. Between 22 commits and 10 PRs, I touched everything from Lua config files to Svelte components and network retry logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Neovim Pivot (&lt;code&gt;nvim&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;If you’re like me, your editor is a living organism. This week, I decided it was time for a major evolution. I pushed 21 commits to my &lt;code&gt;nvim&lt;/code&gt; repo, which resulted in about 3,200 additions and 2,000 deletions. That’s a lot of churn for a config, but the result was worth it: I’ve fully pivoted to a "Kanagawa modern-minimal" aesthetic. &lt;/p&gt;

&lt;p&gt;I spent a significant chunk of time in &lt;code&gt;lua/plugins&lt;/code&gt; and &lt;code&gt;lua/config&lt;/code&gt;, cleaning up my &lt;code&gt;&amp;lt;leader&amp;gt;&lt;/code&gt; notation and ensuring the UI felt snappy. I even updated the title logo and added a site HTML file to the repo. Most of the heavy lifting was actually maintenance—I have a CI job that updates all plugins to the latest versions, and I spent a few sessions manually resolving the fallout from those updates. There’s a specific kind of peace that comes from applying whitespace and lint fixes across 42 changed files. It’s the digital equivalent of vacuuming your office; you don’t realize how much the clutter was bothering you until it’s gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hardening the Core (&lt;code&gt;openslate&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;While the Neovim work was about "feel," my work on &lt;code&gt;openslate&lt;/code&gt; was all about "fact." I’m currently in the middle of a massive testing push. Even though the commit count looks low on the main branch, the PR activity tells the real story. I’m building out a robust integration suite to cover the entire application lifecycle.&lt;/p&gt;

&lt;p&gt;I spent a lot of time in &lt;code&gt;api/src&lt;/code&gt;, specifically focusing on authentication. I merged a critical PR that asserts the auth middleware returns a 401 when no cookie is present—it’s a small thing, but it’s the foundation of the whole security model. I also have several open PRs right now that cover the signup and signin flows, ensuring that the first user created actually gets the correct cookie back. &lt;/p&gt;

&lt;p&gt;One of the trickier parts of the week was working on the &lt;code&gt;build_fts_query&lt;/code&gt; logic. Full-text search (FTS) is always a bit of a nightmare when it comes to wildcard and quoting rules. I opened a PR specifically to test those edge cases because I noticed some weird behavior when users tried to search with special characters. Better to catch that in a test suite than in a bug report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests &amp;amp; Pipeline Fixes
&lt;/h2&gt;

&lt;p&gt;This was a heavy PR week. I opened 10 in total, and they really represent the "breadth" of my current projects.&lt;/p&gt;

&lt;p&gt;Over at &lt;code&gt;opensre&lt;/code&gt;, I tackled a frustrating UI bug. I noticed that the interactive shell loading bar wasn't spinning while the LLM was doing its investigation. There’s nothing worse than staring at a static screen wondering if the backend has died or if it’s just thinking. I submitted a fix with about 150 lines of changes to ensure the progress state is correctly reflected in the terminal. Even though that PR is currently closed, the investigation into why the state wasn't bubbling up was a great deep dive into the shell's event loop.&lt;/p&gt;

&lt;p&gt;I also spent some time on &lt;code&gt;DevNotion&lt;/code&gt; (the very tool I'm using to track this!). I opened a PR to handle transient network errors during the "harvest" phase. When you're pulling data from multiple APIs, something is bound to flake out. Adding a retry strategy and a secure deployment plan (about 800 lines of new code) makes the whole system feel much more "production-ready" and less like a weekend hack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack &amp;amp; The Streak
&lt;/h2&gt;

&lt;p&gt;Looking at the language breakdown for the week, it’s a bit of a polyglot explosion. While Lua dominated my commit count because of the Neovim refactor, the actual logic was spread across TypeScript, Svelte, and a bit of Python. &lt;/p&gt;

&lt;p&gt;The additions-to-deletions ratio (3226 vs 2063) is exactly where I like it to be during a refactor week. It shows that I’m not just piling on new features; I’m actively removing the old, brittle parts of the codebase to make room for the new stuff. &lt;/p&gt;

&lt;p&gt;And then there’s the streak. Seven straight days of commits. It wasn't a forced grind; it was just one of those weeks where every time I sat down, there was something clear and actionable to do. Whether it was a 2am tweak to a Kanagawa color highlight or a mid-afternoon integration test for a media MIME filter, the momentum stayed high.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week is all about closing the loop. I have a mountain of open PRs in &lt;code&gt;openslate&lt;/code&gt;—everything from the command palette styling to the media DB filters. My goal is to get the integration test suite merged so I can start building out the next set of features on top of a "green" CI pipeline.&lt;/p&gt;

&lt;p&gt;I’m also planning to dive deeper into the &lt;code&gt;DevNotion&lt;/code&gt; harvest logic. Now that the retry strategy is in place, I want to see if I can optimize the data fetching to be a bit more parallel. &lt;/p&gt;

&lt;p&gt;Until then, I’ll be enjoying the new, minimal look of my editor. It’s amazing how a fresh coat of paint can make you want to write another thousand lines of code. See you all next week!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Generated by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;

</description>
      <category>neovim</category>
      <category>productivity</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Dev log #10 The Power of the Zero-Commit Week: Stepping Back to Move Faster</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Fri, 03 Jul 2026 04:49:00 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-10-the-power-of-the-zero-commit-week-stepping-back-to-move-faster-1e5h</link>
      <guid>https://dev.to/yashksaini/dev-log-10-the-power-of-the-zero-commit-week-stepping-back-to-move-faster-1e5h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A rare week of total radio silence on GitHub—no commits, no PRs, and zero green squares. Sometimes the best thing for a codebase is for the developer to step away from the keyboard and focus on the bigger picture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;If you looked at my GitHub contribution graph for this past week, you’d see a whole lot of nothing. Zero commits, zero pull requests, and a broken streak. It wasn't a failure of productivity, but a conscious choice to trade the keyboard for a whiteboard and some much-needed headspace; I finished the week with 0 lines added and 0 lines deleted, but a much clearer roadmap for what's coming next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Sound of Silence
&lt;/h2&gt;

&lt;p&gt;I’ve always been a bit of a "green square" addict. As a freelancer and active open-source contributor, there’s a certain dopamine hit that comes with shipping a PR or seeing a complex CI pipeline finally turn green. But this week, for the first time in a long while, I didn't touch a single repository. &lt;/p&gt;

&lt;p&gt;There’s a specific kind of pressure that comes with being deep into p2p networking and backend tooling. When you're contributing to projects like libp2p or maintaining your own CLI tools, the "to-do" list never actually ends. There is always a dependency to bump, a bug to triage, or a refactor that's been nagging at the back of your brain. However, I’ve learned the hard way that if you don't intentionally schedule a "zero week," your brain will eventually schedule one for you in the form of burnout.&lt;/p&gt;

&lt;p&gt;This week was about breaking the cycle of reactive coding. Instead of jumping into the &lt;code&gt;src&lt;/code&gt; directory of my latest Python API or wrestling with borrow checker errors in my Rust crates, I spent my time reading, planning, and—dare I say it—resting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflecting on the Stack
&lt;/h2&gt;

&lt;p&gt;Even though I didn't push code, looking at my language distribution gives a pretty clear picture of where my head is usually at. My local environment is a graveyard of half-finished experiments across a massive range of technologies. &lt;/p&gt;

&lt;p&gt;Python remains my heavy hitter, sitting at over 100MB of source code across my various projects. It’s my go-to for data pipelines and AI backend work because of the sheer speed of iteration. But when I’m looking for safety and performance, especially in the p2p space, I’m almost always reaching for Rust or TypeScript. Seeing nearly 12MB of Rust and 16MB of TypeScript in my stats reminds me of the shift I've made lately toward more memory-safe, robust systems.&lt;/p&gt;

&lt;p&gt;I also noticed a significant amount of Nix, Lua, and even some OCaml and Nim in my historical data. It’s a reminder that being a developer isn't just about the code you write today; it's about the cumulative knowledge of all the paradigms you've explored. This week, instead of writing more Python, I spent time reading through some OCaml documentation and exploring how different languages handle concurrency. It’s the kind of "invisible work" that doesn't show up on a contribution graph but fundamentally changes how I'll approach my next PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Invisible" Work
&lt;/h2&gt;

&lt;p&gt;We often talk about "shipping" as the only metric that matters. But what about the work that happens before the first line of code is written? &lt;/p&gt;

&lt;p&gt;I spent a good chunk of this week thinking about architecture. I have a few open ideas for some CLI tools that help with developer experience (DX), and I realized that if I had started coding them on Monday, I would have built them the wrong way. By stepping back, I realized I was over-complicating the state management. I didn't need a complex database; I needed a simple, flat-file structure that stayed out of the user's way. &lt;/p&gt;

&lt;p&gt;This is why I’m not sweating the 0% merge rate or the lack of reviews this week. As a developer, your value isn't just in your ability to type; it's in your ability to solve problems. Sometimes, the best solution is the code you decide &lt;em&gt;not&lt;/em&gt; to write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack &amp;amp; Consistency
&lt;/h2&gt;

&lt;p&gt;Usually, I’m bragging about a 7-day streak or a high review-to-PR ratio. This week, the streak is at zero. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language Mix:&lt;/strong&gt; My history is polyglot by nature. While Python, Rust, and TypeScript dominate the byte count, the presence of things like Solidity, Go, and even some C++ shows the breadth of the projects I’ve touched. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add/Delete Ratio:&lt;/strong&gt; 0/0. It’s a perfect balance, if a bit boring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review Balance:&lt;/strong&gt; No reviews given, no reviews received. This was a week of total internal focus.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It feels strange to see "0 commits" at the top of a dev log, but I think it’s important to be honest about it. We aren't machines. We don't just produce code at a constant rate until we're decommissioned. &lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The "rest" phase is officially over. My brain is already starting to itch for a terminal window. &lt;/p&gt;

&lt;p&gt;Next week, I’m planning to dive back into the TypeScript and Rust side of my portfolio. I have some ideas for hardening a few SDKs I've been working on, and the architectural clarity I gained this week is going to make that process much smoother. I’m also looking at some of the issues I left hanging in my Python repos—now that I’ve had a break, the solutions to those "impossible" bugs are starting to feel a lot more obvious.&lt;/p&gt;

&lt;p&gt;To anyone else feeling the pressure of the empty contribution graph: it’s okay to have a quiet week. The code will still be there on Monday. The goal isn't to have the most green squares; the goal is to build things that actually matter, and sometimes that requires stepping away from the screen.&lt;/p&gt;

&lt;p&gt;See you all in the commit logs next week.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Generated by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>travel</category>
    </item>
    <item>
      <title>Dev log #9 Hardening Kademlia DHT and automating the Neovim grind</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Fri, 03 Jul 2026 03:48:03 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-9-hardening-kademlia-dht-and-automating-the-neovim-grind-43f3</link>
      <guid>https://dev.to/yashksaini/dev-log-9-hardening-kademlia-dht-and-automating-the-neovim-grind-43f3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Seven days of flow. Fixed a peer identity binding issue in py-libp2p, automated my Neovim lockfile merges, and added a massive batch of notes on xv6 and Category Theory. 10 commits and a solid PR in the works.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I managed to hit a perfect seven-day streak this week, balancing some deep-dive p2p networking work with necessary maintenance on my local environment. The highlight was opening a PR in &lt;code&gt;py-libp2p&lt;/code&gt; to tighten up how PeerRecords are handled in the Kademlia DHT. On the side, I spent time automating the annoying parts of my Neovim config and dumping a fresh batch of notes into my knowledge base. 10 commits, 204 lines added, and a much cleaner workflow to show for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Neovim Configuration &amp;amp; CI
&lt;/h3&gt;

&lt;p&gt;I’m a firm believer that your editor should work for you, not the other way around. My &lt;code&gt;nvim&lt;/code&gt; repo saw a lot of action this week—9 commits in total—but most of it was under-the-hood maintenance. I’ve been leaning on Lua to keep things snappy, and this week was about ensuring my plugin ecosystem doesn't rot.&lt;/p&gt;

&lt;p&gt;I pushed several updates to keep plugins at their latest versions, but the real "quality of life" improvement was adding a chore to auto-resolve &lt;code&gt;lazy-lock.json&lt;/code&gt; merge conflicts. If you’ve ever worked on your Neovim config across multiple machines, you know the headache of the lockfile drifting. I set up a flow to prioritize incoming changes, which saves me from manually triaging JSON diffs every time I pull from &lt;code&gt;dev&lt;/code&gt;. It’s a small tweak, but it removes a recurring friction point in my daily flow. I also spent time cleaning up the root of the config, with about 37 additions and 33 deletions—refactoring is a constant process when you live in your terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Knowledge Base
&lt;/h3&gt;

&lt;p&gt;I also put some serious time into &lt;code&gt;main-notes&lt;/code&gt;. I’m currently going deep on a few different subjects, and I use this repo as my "second brain." I added 167 lines of new material across 13 files, covering a pretty diverse range of topics: xv6 (the re-implementation of Unix V6), Category Theory, HQSE, and the Rust Handbook. &lt;/p&gt;

&lt;p&gt;Writing notes for xv6 is always a trip because it forces you to think about the C-level reality of operating systems. It’s a nice contrast to the high-level Python and TypeScript work I do during the day. Most of these additions were in the &lt;code&gt;Books&lt;/code&gt; and &lt;code&gt;Books/Covers&lt;/code&gt; directories, effectively building out a digital library that I can reference whenever I’m stuck on a systems design problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests
&lt;/h2&gt;

&lt;p&gt;The most significant piece of code I wrote this week is currently sitting as an open PR in &lt;code&gt;py-libp2p&lt;/code&gt;. I've been looking at how we handle the Kademlia Distributed Hash Table (DHT), specifically regarding security and record integrity.&lt;/p&gt;

&lt;p&gt;The PR, titled &lt;code&gt;fix(kad-dht): bind signed PeerRecord to its signer identity&lt;/code&gt;, addresses a subtle but important issue in how peer records are validated. In a p2p network, you can’t just trust data because it exists; you have to verify the source. By explicitly binding the signed PeerRecord to the identity of the signer, we’re making the DHT more resilient against certain types of spoofing or stale data propagation. It was 103 lines of Python that touched the core logic of the DHT implementation. It’s still open for review, and I’m looking forward to the feedback from the other libp2p contributors. There’s something uniquely satisfying about contributing to the foundational layers of the decentralized web.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;This was a polyglot week, as is usually the case when you're jumping between open-source contributions and personal tooling.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Python:&lt;/strong&gt; This was the heavy lifter for the libp2p work. Python remains my go-to for quick prototyping in the p2p space, even if the eventual goal is often a Rust or Go implementation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Lua:&lt;/strong&gt; Purely for the Neovim environment. Lua’s integration into the Nvim ecosystem has made it so much easier to build custom logic for things like that lockfile auto-resolution I mentioned earlier.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;C:&lt;/strong&gt; This showed up in the context of my xv6 notes. Even when I’m not writing production C code, reading and documenting it is essential for keeping my systems knowledge sharp.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Numbers:&lt;/strong&gt; I added 204 lines and deleted 33. It wasn't a massive "tear it all down" refactoring week, but rather one of steady growth and precise fixes. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 7-day streak is what I’m most proud of, though. It wasn’t about grinding out 12-hour days; it was about showing up every single day to move the needle, whether that was a complex p2p fix or just documenting a chapter of a book I’m reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week, I’m hoping to get that &lt;code&gt;py-libp2p&lt;/code&gt; PR merged. Once that’s landed, I want to look at more edge cases in the Python DHT implementation—there’s always more to harden. I’m also planning to dive deeper into the Category Theory notes. It’s abstract stuff, but the more I read, the more I see how those patterns emerge in functional programming and API design. &lt;/p&gt;

&lt;p&gt;On the tooling side, my Neovim config feels stable for now, but I might look into optimizing my CI pipeline further to handle plugin updates more gracefully without cluttering my commit history. Until then, I'll be in the terminal. See you on GitHub!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Generated by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;

</description>
      <category>neovim</category>
      <category>linux</category>
      <category>network</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Dev log #8 Hardening the Orchestrator: A Week of Making dev-publish Resilient</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Fri, 03 Jul 2026 03:47:12 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-8-hardening-the-orchestrator-a-week-of-making-dev-publish-resilient-14lh</link>
      <guid>https://dev.to/yashksaini/dev-log-8-hardening-the-orchestrator-a-week-of-making-dev-publish-resilient-14lh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Spent the week deep-diving into my dev-publish tool, focusing on durability and orchestrator resilience. 21 commits across two repos, with a massive cleanup of the publishing logic and some much-needed architecture documentation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;There is a specific kind of satisfaction that comes from taking a tool you use every day and finally giving it the "production-grade" treatment it deserves. This week was exactly that. I spent most of my time in the guts of &lt;code&gt;dev-publish&lt;/code&gt;, moving past the "it works on my machine" phase and into "it works even if the world is on fire" territory. With 21 commits and over 11,000 lines of code churn, I focused on making the publishing orchestrator resilient and the state durable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;The star of the show this week was &lt;a href="https://github.com/yashksaini-coder/dev-publish" rel="noopener noreferrer"&gt;dev-publish&lt;/a&gt;. If you’ve ever tried to automate cross-platform technical writing, you know that the edge cases are where the real pain lives. I pushed 16 commits here, touching about 45 files. The diff was pretty wild: +6,926 additions and -4,289 deletions. That net positive tells part of the story, but the deletions represent me ripping out brittle logic that just wasn't cutting it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hardening the Orchestrator
&lt;/h3&gt;

&lt;p&gt;The biggest win was a massive fix to make the publish state durable and the orchestrator resilient. In the previous iteration, if a network request to an API (like Dev.to) failed halfway through a multi-platform push, the state was... let's just say "vague." I spent a lot of time in &lt;code&gt;src&lt;/code&gt; ensuring that the orchestrator can now pick up where it left off. I also documented the published-flag semantics and re-run resilience in the README. It sounds like a small thing, but knowing that a re-run won't accidentally double-post your article is a huge weight off my mind.&lt;/p&gt;

&lt;p&gt;I also spent some time on the "boring but important" stuff. I normalized how tags are handled to make them safer across different platforms and implemented a much stricter resolution for cover images. If a local image is required but missing, the tool now yells at you immediately rather than failing silently mid-upload.&lt;/p&gt;

&lt;p&gt;One of my favorite commits this week was &lt;code&gt;refactor: drop unused assetsDir config&lt;/code&gt;. There is nothing quite like the feeling of deleting configuration options that you realized were over-engineered and unnecessary. It keeps the surface area small and the DX (developer experience) clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture and Docs
&lt;/h3&gt;

&lt;p&gt;I’m a firm believer that if you can't draw it, you don't understand it. I spent a chunk of time in &lt;code&gt;docs/diagrams&lt;/code&gt; and actually embedded an architecture diagram directly into the README. It helps me visualize the flow between the local markdown files, the image resolver, and the final API calls. &lt;/p&gt;

&lt;p&gt;I also did a specific dive into the Dev.to integration. I added better validation for their API responses and exposed a properly typed error system. No more guessing why a request failed—now I get a clear, typed reason that the orchestrator can actually act upon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping the Saw Sharp
&lt;/h3&gt;

&lt;p&gt;Outside of the heavy lifting on &lt;code&gt;dev-publish&lt;/code&gt;, I spent a bit of time on my &lt;a href="https://github.com/yashksaini-coder/nvim" rel="noopener noreferrer"&gt;nvim&lt;/a&gt; config. This wasn't anything groundbreaking—mostly just keeping the environment healthy. I have a set of CI workflows that update all my plugins to the latest versions. It’s five quick commits that keep my editor snappy and ensure I’m not missing out on any new features or bug fixes in the Lua ecosystem. It’s the digital equivalent of cleaning your desk at the end of the day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests &amp;amp; Issues
&lt;/h2&gt;

&lt;p&gt;You might notice there are zero PRs this week. Since I’m the primary driver on these specific projects right now, I’ve been pushing directly to the main branches to keep the velocity high. It’s a bit of a "move fast and fix things" approach, but since I’m also the one writing the tests and the docs, the quality bar stays where it needs to be. No issues opened or discussions started either—just pure, unadulterated flow state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;This was a very TypeScript-heavy week. When you're building CLI tools and orchestrators, the type safety of TS is basically a superpower. Being able to define exactly what a "PublishState" looks like and having the compiler yell at me when I forget an edge case is the only way I stay sane.&lt;/p&gt;

&lt;p&gt;The line count this week was pretty high (+6,935 / -4,298), which usually signals a mix of new feature work and heavy refactoring. I managed to maintain a 3-day streak, which felt good. It wasn't a "grind" week, but rather a few days of deep, focused work where I could really get into the zone.&lt;/p&gt;

&lt;p&gt;Even though my total language profile shows a massive amount of Python and Rust from past projects, this week was all about the TypeScript and Lua. It’s a reminder that as a dev, your "current" stack is often just whatever tool is best for the problem you're solving right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Now that the orchestrator in &lt;code&gt;dev-publish&lt;/code&gt; is stable and the state is durable, I want to start looking at expanding the platform support. The foundation is there, the architecture is documented, and the error handling is typed. &lt;/p&gt;

&lt;p&gt;I’m also keeping an eye on my Neovim setup. Those automated plugin updates sometimes introduce small breaking changes in the Lua APIs, so I might need to spend a morning next week tweaking my &lt;code&gt;init.lua&lt;/code&gt; to accommodate any upstream shifts.&lt;/p&gt;

&lt;p&gt;It was a solid week of building. Nothing beats the feeling of seeing a tool you built yourself get faster, stronger, and more reliable. Catch you in the next update!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Generated by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
