<?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 #21 Hardening WebRTC-Direct and solving SCM leaks</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sat, 19 Sep 2026 05:49:17 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-hardening-webrtc-direct-and-solving-scm-leaks-i5k</link>
      <guid>https://dev.to/yashksaini/dev-log-hardening-webrtc-direct-and-solving-scm-leaks-i5k</guid>
      <description>&lt;p&gt;Hi, glad you found your way here. I'm Yash, a developer who contributes to open source, mostly p2p networking and AI tooling. Each week I write up what I shipped, what broke, and what fixing it taught me.&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;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;This week was all about the "glue" that holds distributed systems together. I spent my time jumping between the Python implementation of libp2p and a Go-based agent orchestrator. It was a week of deep-dives into networking internals—specifically getting WebRTC-Direct to behave across different language implementations—and fixing some frustrating SCM polling issues. Across 8 commits and 11 PRs, I focused on making sure connections are actually closed when they're supposed to be and that our agents aren't hallucinating CI failures.&lt;/p&gt;

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

&lt;p&gt;Most of my energy went into &lt;a href="https://github.com/libp2p/py-libp2p" rel="noopener noreferrer"&gt;py-libp2p&lt;/a&gt;, where I’m currently neck-deep in WebRTC-Direct work. Networking is one of those things where it works 99% of the time, and then that last 1% takes up 90% of your brainpower.&lt;/p&gt;

&lt;h3&gt;
  
  
  Networking Internals (py-libp2p)
&lt;/h3&gt;

&lt;p&gt;I pushed 6 commits to &lt;code&gt;py-libp2p&lt;/code&gt; this week, mostly centered on stabilizing the transport layer. One of the big wins was fixing a race condition in the WebRTC dialer where ICE credentials weren't being set correctly before the first-contact replay. This was specifically blocking interoperability between Go and Python nodes—one of those gnarly "it works in isolation but fails in the wild" bugs. I also spent some time on the housekeeping side, fixing socket leaks in both &lt;code&gt;mplex&lt;/code&gt; and the &lt;code&gt;swarm&lt;/code&gt;. If you've ever wondered why your long-running nodes eventually run out of file descriptors, it’s usually because of unclosed secured connections. I made sure that when we shut down, we actually release the underlying sockets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent Orchestration (agent-orchestrator)
&lt;/h3&gt;

&lt;p&gt;Over in &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator" rel="noopener noreferrer"&gt;agent-orchestrator&lt;/a&gt;, the work was more about the developer experience and SCM integration. I tackled a weird edge case where workspace files with backslashes in their names were resolving incorrectly on Linux and macOS. It’s a classic cross-platform path handling headache, but getting it right means one less "it works on my machine" conversation. I also looked into how the orchestrator handles GitHub polling—specifically an issue where it was re-listing every open PR because the &lt;code&gt;since&lt;/code&gt; parameter wasn't being respected by the endpoint.&lt;/p&gt;

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

&lt;p&gt;It was a heavy PR week—11 in total. I had 4 merged, 3 still open, and 4 closed as I refined my approach.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;py-libp2p&lt;/code&gt; world, &lt;a href="https://github.com/libp2p/py-libp2p/pull/1513" rel="noopener noreferrer"&gt;PR #1513&lt;/a&gt; was a big documentation push. I documented the STUN listener versus the SDP harness and provided guidance on v1 vs v2 dialing. Clear docs are just as important as clean code when you’re building p2p primitives. I also have &lt;a href="https://github.com/libp2p/py-libp2p/pull/1532" rel="noopener noreferrer"&gt;PR #1532&lt;/a&gt; open, which introduces a true ICE-Lite listener for WebRTC-Direct. This is a significant addition (+166 lines) that will make the listener much more robust.&lt;/p&gt;

&lt;p&gt;On the bug-squashing front, &lt;a href="https://github.com/libp2p/py-libp2p/pull/1497" rel="noopener noreferrer"&gt;PR #1497&lt;/a&gt; and &lt;a href="https://github.com/libp2p/py-libp2p/pull/1495" rel="noopener noreferrer"&gt;PR #1495&lt;/a&gt; landed to fix the connection shutdown and WebRTC dial issues I mentioned earlier. &lt;/p&gt;

&lt;p&gt;For &lt;code&gt;agent-orchestrator&lt;/code&gt;, I had a few PRs that I ended up closing (&lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5089" rel="noopener noreferrer"&gt;#5089&lt;/a&gt;, &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5088" rel="noopener noreferrer"&gt;#5088&lt;/a&gt;, &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5057" rel="noopener noreferrer"&gt;#5057&lt;/a&gt;, and &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5056" rel="noopener noreferrer"&gt;#5056&lt;/a&gt;). Sometimes you start a fix and realize there's a better architectural way to handle it, or you're iterating fast and a better solution emerges. I’d rather close a PR and open a better one than merge something that isn't quite right. However, &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5055" rel="noopener noreferrer"&gt;PR #5055&lt;/a&gt; did land, which fixed that backslash filename bug on non-Windows systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Discussions
&lt;/h2&gt;

&lt;p&gt;I opened and closed 6 issues this week, which really tells the story of the week's detective work. &lt;/p&gt;

&lt;p&gt;The most interesting one is &lt;a href="https://github.com/libp2p/py-libp2p/issues/1498" rel="noopener noreferrer"&gt;Issue #1498&lt;/a&gt; regarding residual unclosed-socket leaks. This is currently gating our &lt;code&gt;filterwarnings&lt;/code&gt; guard in the test suite. Until we can guarantee the tests aren't leaking resources, we can't turn on the stricter CI checks. It's a bit of a slog, but it's the kind of maintenance that prevents technical debt from spiraling.&lt;/p&gt;

&lt;p&gt;I also opened &lt;a href="https://github.com/libp2p/py-libp2p/issues/1512" rel="noopener noreferrer"&gt;Issue #1512&lt;/a&gt; for the ICE-Lite listener work. It's one of those features that started as a "nice to have" and quickly became a "need to have" for proper WebRTC-Direct support.&lt;/p&gt;

&lt;p&gt;On the &lt;code&gt;agent-orchestrator&lt;/code&gt; side, &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/issues/5046" rel="noopener noreferrer"&gt;Issue #5046&lt;/a&gt; was a fun find: the Copilot adapter was adding a permanent line to &lt;code&gt;.git/info/exclude&lt;/code&gt; for every single session. That’s a great way to end up with a massive, messy exclude file if you’re not careful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Reviews
&lt;/h2&gt;

&lt;p&gt;Reviewing is where I get to see how others are thinking about the same problems. I did 2 reviews this week:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;py-libp2p&lt;/strong&gt;: I requested changes on &lt;a href="https://github.com/libp2p/py-libp2p/pull/1139" rel="noopener noreferrer"&gt;PR #1139&lt;/a&gt;, which adds Gossipsub comparison and standalone examples. Gossipsub is complex, and while examples are great, they need to be rock-solid so they don't mislead new users.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;agent-orchestrator&lt;/strong&gt;: I approved &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5137" rel="noopener noreferrer"&gt;PR #5137&lt;/a&gt;, which adds support for Arch Linux and—thankfully—stops self-updates on system installs. System-managed packages should be handled by the package manager, not the app itself.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;It was a Python-heavy week (6 commits/PRs) with a solid side of Go (2 commits/PRs). &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt;: My focus here was almost entirely on the networking stack—handling sockets, bytes, and protocols.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt;: Used for the orchestrator work, dealing more with SCM APIs and file system abstractions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With 508 additions and only 41 deletions, this was definitely a "building and fixing" week rather than a "cleaning and refactoring" week. I’m adding new capabilities to the WebRTC stack, and that naturally comes with a higher line count.&lt;/p&gt;

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

&lt;p&gt;The goal for next week is to get the ICE-Lite listener PR (#1532) in &lt;code&gt;py-libp2p&lt;/code&gt; across the finish line. I also want to circle back to those socket leaks in the test suite. It's not the most glamorous work, but seeing a clean, green CI with no resource warnings is a special kind of satisfying. On the orchestrator side, I'll be keeping an eye on the SCM polling fixes to make sure our agent's view of the world stays in sync with GitHub.&lt;/p&gt;

&lt;p&gt;Catch you next week!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>devrel</category>
      <category>discuss</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>The receipt should come from the person who received it</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sun, 06 Sep 2026 17:33:22 +0000</pubDate>
      <link>https://dev.to/yashksaini/the-receipt-should-come-from-the-person-who-received-it-4kog</link>
      <guid>https://dev.to/yashksaini/the-receipt-should-come-from-the-person-who-received-it-4kog</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-09-03"&gt;Weekend Challenge: Generosity Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Every donation platform proves the same single fact: money left your account. The fact you actually care about is the second one, that it reached a person, and the receipt for the first is issued by the organisation holding the money. The thing you're trusting and the thing producing the evidence are the same thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cairn&lt;/strong&gt; changes who issues the receipt.&lt;/p&gt;

&lt;p&gt;You lock SOL in an escrow no intermediary can open. The only thing that moves it is a signature from the recipient's own wallet, and that transaction carries the hash of a recording where they say, in their own words, what they received. The receipt comes from the person at the end of the chain, and the chain welds it to the transfer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvypf89ux881gr37ibyc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvypf89ux881gr37ibyc.png" alt="Cairn — proof of arrival for charitable giving" width="799" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three commands, and the third one is the whole project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cairn give     &lt;span class="nt"&gt;--to&lt;/span&gt; &amp;lt;them&amp;gt; &lt;span class="nt"&gt;--amount&lt;/span&gt; 0.05 &lt;span class="nt"&gt;--for&lt;/span&gt; &lt;span class="s2"&gt;"one term of school fees"&lt;/span&gt;
cairn receive  &lt;span class="nt"&gt;--escrow&lt;/span&gt; &amp;lt;pubkey&amp;gt; &lt;span class="nt"&gt;--audio&lt;/span&gt; receipt.wav   &lt;span class="c"&gt;# they run this&lt;/span&gt;
cairn verify   &amp;lt;signature&amp;gt;                             &lt;span class="c"&gt;# anyone runs this&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;cairn verify&lt;/code&gt; re-downloads the recording, re-hashes the bytes it gets back, rebuilds the receipt from scratch and compares the result against what's on chain. It doesn't ask my server to be honest. With &lt;code&gt;cairn hash&lt;/code&gt; it doesn't need my server to exist.&lt;/p&gt;

&lt;p&gt;A cairn is a stack of stones travellers raise along a route. Each person who passes adds one. It proves somebody came through, it guides whoever comes next, and it works with nobody owning it and nobody supervising it. Every clause maps onto something the software does, which is the only reason the name stuck.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/yashksaini-coder/Cairn/blob/main/docs/COMMANDS.md" rel="noopener noreferrer"&gt;docs/COMMANDS.md&lt;/a&gt;&lt;/strong&gt; walks the whole loop with real captured terminal output at every step — funding an escrow, recording a receipt, releasing, and verifying.&lt;/p&gt;

&lt;p&gt;The command that matters:&lt;/p&gt;

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

&lt;p&gt;Those two hashes were produced by different code paths, minutes apart. The same command against a recording whose bytes have been altered:&lt;/p&gt;

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

&lt;p&gt;Two visibly different hashes and a non-zero exit code, so it can gate a script.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;
        yashksaini-coder
      &lt;/a&gt; / &lt;a href="https://github.com/yashksaini-coder/Cairn" rel="noopener noreferrer"&gt;
        Cairn
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/yashksaini-coder/Cairn/assets/Banner.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fyashksaini-coder%2FCairn%2FHEAD%2Fassets%2FBanner.png" alt="Cairn — proof of arrival for charitable giving" width="100%"&gt;&lt;/a&gt;
&lt;/div&gt;



&lt;p&gt;A weekend build. Lock some SOL for someone; they only get it by signing a
transaction that carries the hash of a voice recording saying what they
received.&lt;/p&gt;
&lt;p&gt;Devnet. Not audited. About 4,500 lines of Rust, of which 400 are the test
suite, and no frontend at all.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;cairn give     --to &lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;them&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; --amount 0.05 --for &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;one term of school fees&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
cairn receive  --escrow &lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;pubkey&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; --audio receipt.wav     &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; they run this&lt;/span&gt;
cairn verify   &lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;signature&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;                               &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; anyone runs this&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/yashksaini-coder/Cairn/assets/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fyashksaini-coder%2FCairn%2FHEAD%2Fassets%2Fdemo.gif" alt="A terminal session: funds are locked against a description, the recipient records a receipt and releases them, verification passes, the stored recording is altered, and verification then fails." width="100%"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;A real session against a local validator — the escrow is created on chain, the
receipt is signed by the recipient's key, and the failure at the end is a
genuine hash mismatch after the stored recording is altered. Also available as
&lt;a href="https://github.com/yashksaini-coder/Cairn/assets/demo.mp4" rel="noopener noreferrer"&gt;mp4&lt;/a&gt;, or replay it yourself with
&lt;code&gt;asciinema play assets/demo.cast&lt;/code&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The itch&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Donation platforms verify that money left you. They don't verify…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yashksaini-coder/Cairn" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;A weekend build: roughly 4,500 lines of Rust, 400 of them the test suite. MIT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cairn/
├── programs/cairn/      custody, authorization, state machine
├── crates/
│   ├── cairn-core/      canonical receipt, hashing, seeds, account layout
│   ├── cairn-api/       blob store, index, verifier. Holds no keys.
│   └── cairn-cli/       the client. All of it.
└── docs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The axis everything turns on
&lt;/h3&gt;

&lt;p&gt;Two things here look similar and are not, and the entire design is the consequence of separating them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authorization&lt;/strong&gt; is an ed25519 signature from the recipient key the donor names at creation. The program enforces it. No server key, operator key or admin override substitutes for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testimony&lt;/strong&gt; is the recording. Human evidence, hashed and committed on chain so it's tamper-evident and bound to exactly one transaction signature.&lt;/p&gt;

&lt;p&gt;The recording authorizes nothing. Audio used as an authentication factor is walked through by replay or a decent TTS model, so Cairn treats it as attached evidence with guaranteed integrity instead. That's a much smaller claim, and it's one the software can actually keep.&lt;/p&gt;

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

&lt;p&gt;Note the asymmetry. The recipient's way out is a signature; the donor's way back is a deadline. There's no third arrow and nobody standing in the middle.&lt;/p&gt;

&lt;h3&gt;
  
  
  One hash, and everything checks against it
&lt;/h3&gt;

&lt;p&gt;Strip Cairn down and a single sentence is left: two independently computed hashes agree. Everything below exists to make that sentence hard to fake and easy to check.&lt;/p&gt;

&lt;p&gt;It starts with the encoding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receipt_hash = sha256(
    b"cairn:receipt:v1" ‖ escrow(32) ‖ audio_sha256(32) ‖ transcript ‖ locale(8) ‖ recorded_at(8)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;locale&lt;/code&gt; is a fixed 8 bytes rather than a string, and that's the load-bearing choice. Every field except the transcript is fixed width, so for a preimage of length &lt;code&gt;L&lt;/code&gt; the transcript is always exactly &lt;code&gt;L - 96&lt;/code&gt; bytes. The encoding is injective with no length prefixes anywhere: no two distinct receipts concatenate to identical bytes. Add a second variable-length field and that evaporates — &lt;code&gt;("ab", "c")&lt;/code&gt; and &lt;code&gt;("a", "bc")&lt;/code&gt; land on the same preimage, and two different receipts share one hash. A test named &lt;code&gt;transcript_and_locale_do_not_smear&lt;/code&gt; exists purely to fail if someone later decides &lt;code&gt;locale&lt;/code&gt; should be a &lt;code&gt;String&lt;/code&gt;. Transcripts are NFC-normalised, trimmed and whitespace-collapsed first, so the same recording hashes the same regardless of which machine composed the receipt.&lt;/p&gt;

&lt;p&gt;Then there's the question of how many implementations of that function exist, and the answer is one. &lt;code&gt;cairn-core&lt;/code&gt; compiles to both &lt;code&gt;sbf-solana-solana&lt;/code&gt; and the host, so the on-chain program, the CLI and the API server all call the same &lt;code&gt;receipt_hash()&lt;/code&gt;. Not kept in sync, not mirrored, not generated from a shared schema — the same function, compiled twice. The account layout works the same way: &lt;code&gt;Escrow&lt;/code&gt; is defined once in &lt;code&gt;cairn-core&lt;/code&gt; and the program wraps it in a newtype that Anchor's traits hang off, so the bytes the program writes are the bytes the indexer reads, with a test asserting that Anchor's generated discriminator matches the constant the decoder uses.&lt;/p&gt;

&lt;p&gt;That count is also why the client is a CLI. A browser would need its own &lt;code&gt;receipt_hash()&lt;/code&gt;, which means hand-porting the normalisation into TypeScript, and JavaScript's &lt;code&gt;\s&lt;/code&gt; and Rust's &lt;code&gt;char::is_whitespace&lt;/code&gt; disagree about U+FEFF — a byte-order mark in a transcript gives you a browser reporting a valid receipt and a chain reporting a fake one. That character is patchable; the shape of it isn't. Any second implementation is a second answer, and an interface whose job is to display "these match" while running different code from the thing doing the matching argues against itself.&lt;/p&gt;

&lt;p&gt;With one function to check against, three commands do the checking rather than taking anything on faith. &lt;code&gt;cairn show&lt;/code&gt; re-hashes the description client-side against the on-chain &lt;code&gt;need_hash&lt;/code&gt;, so the API can serve whatever text it likes and it doesn't matter. &lt;code&gt;cairn receive&lt;/code&gt; rebuilds the receipt from the audio on the recipient's own disk and refuses to sign if the server's hash doesn't cover it. &lt;code&gt;cairn hash&lt;/code&gt; touches no network, needs no key and reads no database — compute a hash with the wifi off and compare it against a block explorer.&lt;/p&gt;

&lt;p&gt;Which leaves the server holding nothing worth stealing: no keypair, no signing path, no instruction it could call to move a lamport.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speech, and why the transcript is a convenience
&lt;/h3&gt;

&lt;p&gt;Transcription runs through &lt;a href="https://elevenlabs.io/" rel="noopener noreferrer"&gt;ElevenLabs Scribe&lt;/a&gt;. The recipient speaks whatever language they actually speak, and the transcript joins the canonical receipt alongside the locale tag.&lt;/p&gt;

&lt;p&gt;It's wired as best-effort enrichment rather than a dependency. The hash covers the audio bytes either way, so a receipt is valid with or without a transcript — the audio is the evidence, and the transcript is a convenience for whoever reads it later. Depending on a third party that lightly felt like the right call inside something whose whole argument is about not needing to trust third parties.&lt;/p&gt;

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

&lt;p&gt;One sentence, precisely: that a specific recording, unaltered, was committed by the holder of a specific key in the same instruction that moved specific funds. There's no window in which the money has moved without a receipt, or a receipt exists without the money.&lt;/p&gt;

&lt;p&gt;Every design decision above exists to make that sentence checkable by a stranger who has no access to me, my server, or my database. Every state transition has a happy-path test in LiteSVM, and every guard has a negative test asserting its specific error code. The one that carries the trust model is &lt;code&gt;only_the_named_recipient_can_release&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Solana.&lt;/strong&gt; The escrow program isn't a payment rail bolted onto the side of the idea — it's the only reason the idea works. A receipt that anyone with database access can remove is a receipt back under the control of the party you were trying not to have to trust, which is the exact problem the project starts from. Putting it on Solana moves custody into a program-owned vault PDA with deterministic seeds and no operator key, makes the recipient's ed25519 signature the sole authorization primitive, and makes the receipt append-only by construction rather than by policy. The program is the trust model. Everything off-chain is a convenience wrapped around it, and it says so by holding no keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of ElevenLabs.&lt;/strong&gt; &lt;a href="https://elevenlabs.io/" rel="noopener noreferrer"&gt;Scribe&lt;/a&gt; closes the language gap that sits between a donor and a recipient by default. The audio is the evidence, but audio in a language you don't read is evidence you can't check, and a transcript makes it legible to someone on the other side of that gap. The locale tag travels inside the hash preimage alongside the transcript, so the language a receipt was given in is part of what gets committed — not metadata bolted on afterwards, but a field the signature covers.&lt;/p&gt;

&lt;p&gt;It was a good challenge that stretched me to make use of solana and AI together in a interesting manner. Interesting part was the idea of getting the scribe using eleven labs. Overall a good exercise to learn something new. &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;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>ai</category>
      <category>rust</category>
    </item>
    <item>
      <title>Dev log #20 Deleting 180k lines and chasing socket leaks: A week in the OSS trenches</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:46:16 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-deleting-180k-lines-and-chasing-socket-leaks-a-week-in-the-oss-trenches-4f9b</link>
      <guid>https://dev.to/yashksaini/dev-log-deleting-180k-lines-and-chasing-socket-leaks-a-week-in-the-oss-trenches-4f9b</guid>
      <description>&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;blockquote&gt;
&lt;p&gt;A massive week of cleanup and deep technical dives. From deleting 180k lines in my vault to fixing socket leaks in libp2p and contributing to the Rust compiler, I hit 102 commits and a perfect 7-day streak.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;There is something incredibly therapeutic about a net-negative week. I pushed 102 commits across a dozen repos, but the headline stat isn't the additions—it’s the 188,000 lines of code I deleted. Between chasing down residual socket leaks in &lt;code&gt;py-libp2p&lt;/code&gt;, building out a resumable progress log for &lt;code&gt;Reachable&lt;/code&gt;, and even opening a PR on the Rust compiler, it’s been a high-velocity seven days.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Great Purge and Portfolio Polish
&lt;/h2&gt;

&lt;p&gt;I started the week with a "scorched earth" approach to my &lt;code&gt;obsidian-vault&lt;/code&gt;. I realized I was tracking way too much of Obsidian's internal UI state and plugin noise, which made the git history a mess. I ripped out about 179,000 lines by stopping the tracking of that ephemeral state. Now, my syncs are clean, and the vault actually represents my thoughts rather than my editor's window positions. It’s a good reminder that just because you &lt;em&gt;can&lt;/em&gt; version control something doesn't mean you &lt;em&gt;should&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;On the &lt;a href="https://github.com/yashksaini-coder/portfolio" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; front, I spent a good chunk of time in &lt;code&gt;app/projects&lt;/code&gt; and &lt;code&gt;components/common&lt;/code&gt;. I finally stripped out some old "catalogue-numbering" chrome that was cluttering the UI and gave each project row its own distinct card surface. I also fixed a nagging SEO issue by moving the canonical origin to &lt;code&gt;yashksaini.vercel.app&lt;/code&gt;. The big feature update here was the new "spotlight" section, where I’m now featuring &lt;a href="https://github.com/yashksaini-coder/GitBanner" rel="noopener noreferrer"&gt;GitBanner&lt;/a&gt; and &lt;a href="https://github.com/yashksaini-coder/Reachable" rel="noopener noreferrer"&gt;Reachable&lt;/a&gt; right at the top. If you’re going to build things, you might as well show them off properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep in the Networking Guts
&lt;/h2&gt;

&lt;p&gt;A lot of my mental energy this week went into &lt;a href="https://github.com/libp2p/py-libp2p" rel="noopener noreferrer"&gt;py-libp2p&lt;/a&gt;. Networking bugs are the ultimate test of patience. I opened two pretty critical issues regarding socket leaks. In &lt;a href="https://github.com/libp2p/py-libp2p/issues/1485" rel="noopener noreferrer"&gt;Issue #1485&lt;/a&gt;, I found that the Swarm was leaking dialed connection sockets on shutdown because the service-manager stop path wasn't cleaning up properly. I followed that up with &lt;a href="https://github.com/libp2p/py-libp2p/issues/1487" rel="noopener noreferrer"&gt;Issue #1487&lt;/a&gt;, which identified residual leaks in the muxer connection test fixtures.&lt;/p&gt;

&lt;p&gt;I didn't just report them, though. I’ve been working on &lt;a href="https://github.com/libp2p/py-libp2p/pull/1486" rel="noopener noreferrer"&gt;PR #1486&lt;/a&gt; to ensure connections are explicitly closed on shutdown so their sockets are released back to the OS. I also pushed forward on &lt;a href="https://github.com/libp2p/py-libp2p/pull/1471" rel="noopener noreferrer"&gt;WebRTC-Direct interop tests&lt;/a&gt;. Getting Python and Go to agree on WebRTC-Direct is... a journey. We had a case where the ICE handshake succeeded, but then the connection was immediately torn down. It’s these kinds of edge cases that make p2p work both frustrating and deeply rewarding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building and Fixing at Scale
&lt;/h2&gt;

&lt;p&gt;I also spent some time on &lt;a href="https://github.com/xevrion/breakscale" rel="noopener noreferrer"&gt;breakscale&lt;/a&gt;, which is a project that really challenges how I think about failure states. I found a bug (Issue #51) where a node losing 25% of its requests to timeouts was still reporting a 0% failure rate. That’s a dangerous blind spot. I merged &lt;a href="https://github.com/xevrion/breakscale/pull/57" rel="noopener noreferrer"&gt;PR #57&lt;/a&gt; to ensure timeouts are counted correctly so the failure rate actually reflects reality. I also fixed a nasty data loss bug in &lt;a href="https://github.com/xevrion/breakscale/pull/54" rel="noopener noreferrer"&gt;PR #54&lt;/a&gt;—previously, if the validator rejected a saved design, the next save or delete would just erase it from storage. Now, we keep the design even if the current build can't "open" it.&lt;/p&gt;

&lt;p&gt;Over at &lt;a href="https://github.com/yashksaini-coder/Reachable" rel="noopener noreferrer"&gt;Reachable&lt;/a&gt;, the focus was on the demo experience. I built out a resumable progress log, a sound score mixer, and a frame renderer. This project is becoming a really cool playground for TypeScript-based media tools. I’m particularly happy with the "paste-ready" voiceover script block—it’s one of those small DX (developer experience) features that makes the tool feel much more polished.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tooling Life: Neovim and DevNotion
&lt;/h2&gt;

&lt;p&gt;You can’t be a dev without constantly tweaking your tools. I did some work on my &lt;a href="https://github.com/yashksaini-coder/nvim" rel="noopener noreferrer"&gt;nvim&lt;/a&gt; config, specifically rebuilding the keymap reference around a prefix tree. I also fixed a frustrating bug where &lt;code&gt;&amp;lt;leader&amp;gt;bd&lt;/code&gt; would leave a blank window open. Now it closes the window properly, and I can open a terminal from any window type, not just "normal" ones. It’s the little things that keep the flow state alive.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;, I focused on the documentation and the "blog log." I reworked the masthead and made the ledger header sticky while scrolling. I also made a conscious decision to drop the sequential numbering from posts. Now they’re just titled "Dev log: ." It feels less like a chore and more like a journal. I also had to escape some pipes in the headlines to prevent the markdown tables in the logs from breaking—classic string manipulation fun.&lt;/p&gt;

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

&lt;p&gt;Beyond the ones I've mentioned, a few others stood out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Rust Compiler&lt;/strong&gt;: I opened &lt;a href="https://github.com/rust-lang/rust/pull/162123" rel="noopener noreferrer"&gt;PR #162123&lt;/a&gt; to report unsatisfied opaque item bounds instead of a generic type mismatch under the &lt;code&gt;-Znext-solver&lt;/code&gt;. Contributing to the Rust compiler is always a "hold your breath" moment, but it's the best way to learn how the magic happens.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Agent Orchestrator&lt;/strong&gt;: Merged a fix in &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/4717" rel="noopener noreferrer"&gt;PR #4717&lt;/a&gt; to carry the resolved model through metadata merging. This fixed a bug where &lt;code&gt;ao spawn&lt;/code&gt; would record an empty model.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Sixb&lt;/strong&gt;: Merged &lt;a href="https://github.com/sixb-ai/sixb/pull/478" rel="noopener noreferrer"&gt;PR #478&lt;/a&gt; which adds the project model catalog to both the core and server.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This was a true polyglot week. TypeScript dominated the frontend and CLI work, Python was the heavy hitter for the libp2p networking logic, and I dipped into Rust for the compiler work. I even wrote a bit of C++ for some basic alphabet-printing programs in my &lt;a href="https://github.com/yashksaini-coder/Learning-2026" rel="noopener noreferrer"&gt;Learning-2026&lt;/a&gt; repo—sometimes you just need to get back to the basics to clear your head.&lt;/p&gt;

&lt;p&gt;The 7-day streak feels good. It wasn't a forced grind; it was just one of those weeks where every time I sat down, there was a clear problem to solve. Whether it was a socket leak in Python or a CSS alignment issue in my portfolio, the momentum stayed high.&lt;/p&gt;

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

&lt;p&gt;Next week is going to be about closing the loop on those &lt;code&gt;py-libp2p&lt;/code&gt; leaks. I want to get the connection-closing logic merged and see if I can figure out why the Go and Python WebRTC-Direct implementations are talking past each other. I’ll also be spending more time on the &lt;code&gt;Reachable&lt;/code&gt; frame renderer—there’s some performance optimization I want to try out.&lt;/p&gt;

&lt;p&gt;Catch you in the next push.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>rust</category>
      <category>networking</category>
    </item>
    <item>
      <title>Dev log #19 WebRTC v2 flows, agentic orchestration, and a perfectly synced vault</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Mon, 31 Aug 2026 06:52:21 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-19-webrtc-v2-flows-agentic-orchestration-and-a-perfectly-synced-vault-5egd</link>
      <guid>https://dev.to/yashksaini/dev-log-19-webrtc-v2-flows-agentic-orchestration-and-a-perfectly-synced-vault-5egd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This week was a high-output sprint across the stack—from low-level p2p networking in Python to refining agentic workflows in TypeScript. I pushed 53 commits and opened 13 PRs, maintaining a perfect 7-day streak while balancing deep protocol work with personal knowledge management.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;I didn't really intend for this to be a "build everything" week, but that’s exactly where the momentum took me. Between hardening WebRTC implementations in &lt;code&gt;py-libp2p&lt;/code&gt; and chasing down edge cases in agent orchestration, I managed to ship 53 commits and keep my daily streak alive for the full seven days. The stats show a heavy tilt toward new code—over 12,000 additions—as I laid the groundwork for better telemetry and more robust p2p networking.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Deep in the Networking weeds: py-libp2p
&lt;/h3&gt;

&lt;p&gt;Most of my "deep work" hours went into &lt;code&gt;py-libp2p&lt;/code&gt;. Networking code is unforgiving, but incredibly satisfying when it clicks. I spent a significant chunk of time in &lt;code&gt;libp2p/kad_dht&lt;/code&gt; implementing configurable subnet-diversity limits and table-wide IP-group caps. If you've ever dealt with Sybil attacks or just messy peer distributions, you know why this matters—it’s about making the DHT resilient, not just functional.&lt;/p&gt;

&lt;p&gt;On the transport side, I was neck-deep in &lt;code&gt;libp2p/transport&lt;/code&gt; fixing WebRTC issues. I had to guard private-slot writes and ensure we’re using &lt;code&gt;close_peer_connection&lt;/code&gt; at every production site to avoid hanging resources. I also spent time on a tricky Windows-specific bug where we needed to listen on a concrete non-loopback interface for tests to actually pass. &lt;/p&gt;

&lt;h3&gt;
  
  
  Hardening Reachable &amp;amp; Breakscale
&lt;/h3&gt;

&lt;p&gt;I’ve been refining &lt;strong&gt;Reachable&lt;/strong&gt;, specifically making the "Ask" feature behave more like a natural conversation. I had to harden the store behind it to ensure state doesn't drift when the UI gets complex. &lt;/p&gt;

&lt;p&gt;Over at &lt;strong&gt;breakscale&lt;/strong&gt;, I hit a weird one: Vitest failing on Node 26 because of &lt;code&gt;jsdom&lt;/code&gt;'s &lt;code&gt;localStorage&lt;/code&gt; implementation. I opened an issue, tracked it down, and pushed a fix to put &lt;code&gt;localStorage&lt;/code&gt; back where it belongs. I also shipped a feature to improve design descriptions—focusing on what a design is &lt;em&gt;made of&lt;/em&gt; rather than just its dimensions. It’s a small UX shift, but it makes the tool feel much more intuitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Digital Brain: obsidian-vault
&lt;/h3&gt;

&lt;p&gt;I treat my &lt;code&gt;obsidian-vault&lt;/code&gt; like a production repo, and this week it saw 35 commits. I was mostly syncing UI states and fixing templates. I noticed a bug in my "Concept" template where the parent pointer was recursively pointing at the template itself (classic meta-mess). I also logged three separate stream sessions, which has been a great way to document my thought process while I code.&lt;/p&gt;

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

&lt;p&gt;I opened 13 PRs this week, and while some are still in flight, they represent a lot of the "glue" work I do across the ecosystem. &lt;/p&gt;

&lt;p&gt;A lot of my focus was on &lt;code&gt;agent-orchestrator&lt;/code&gt;. I’m working on carrying request IDs into daemon-emitted events to make telemetry actually useful. There’s nothing worse than seeing an error in a log and having no way to trace it back to the original trigger. I also fixed a dev-server proxy issue where it was pointing at the wrong daemon—one of those "how did this ever work?" bugs that felt great to squash.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;corsair&lt;/code&gt; repo, I’ve been on a mission to improve reliability and performance. I opened PRs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Cover &lt;code&gt;verifyHmacSignature&lt;/code&gt; with proper unit tests (security code without tests is just a disaster waiting to happen).&lt;/li&gt;
&lt;li&gt;  Add database indexes on &lt;code&gt;corsair_entities&lt;/code&gt; and &lt;code&gt;corsair_accounts&lt;/code&gt; query paths. As the data grows, those sequential scans were starting to hurt.&lt;/li&gt;
&lt;li&gt;  Clean up CLI error handling so async command failures don't just dump unhandled promise rejections into the terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Community
&lt;/h2&gt;

&lt;p&gt;I opened 7 issues this week, mostly as a way to "work in the open." When I find a bug in &lt;code&gt;agent-orchestrator&lt;/code&gt; (like the Connect Mobile tailnet proxy stranding processes on ungraceful exits) or a performance bottleneck in &lt;code&gt;corsair&lt;/code&gt;, I document it before I fix it. It helps me stay organized and gives the maintainers a heads-up on what I’m poking at.&lt;/p&gt;

&lt;p&gt;One specific bug I’m still tracking is in &lt;code&gt;omarchy&lt;/code&gt;, where selecting the default agent somehow triggers a version downgrade because of a &lt;code&gt;mise&lt;/code&gt; release cooldown. It’s a weird interaction between the version manager and the agent logic that I’m hoping to resolve next week.&lt;/p&gt;

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

&lt;p&gt;This was a polyglot week, but Python and TypeScript were the clear winners.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Python:&lt;/strong&gt; 84MB of code touched (mostly libp2p internals).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;TypeScript:&lt;/strong&gt; 58MB of code across frontend tools and CLI utilities.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Mix:&lt;/strong&gt; A bit of Lua for my &lt;code&gt;nvim&lt;/code&gt; config updates and some Shell/Makefile glue to keep the CI pipelines happy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With 12,205 additions against 1,500 deletions, this was definitely a "growth" week. I wasn't just cleaning up; I was shipping new features and expanding test suites. The 7-day streak wasn't forced—when you're bouncing between p2p protocols and AI agents, there's always one more thing you want to tweak before calling it a day.&lt;/p&gt;

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

&lt;p&gt;Next week, I’m planning to close out those WebRTC v2 PRs in &lt;code&gt;py-libp2p&lt;/code&gt;. The specs are solid, but the implementation details around data-channel framing as a byte stream are still a bit finicky. I also want to get those &lt;code&gt;corsair&lt;/code&gt; database indexes merged and see the real-world performance delta.&lt;/p&gt;

&lt;p&gt;And of course, more streaming. I’ve found that logging my sessions in Obsidian has made me much more intentional about the code I write. If I have to explain it to a "future me" in a log, I'm less likely to take the lazy shortcut.&lt;/p&gt;

&lt;p&gt;Catch you in the next push. Keep hacking.&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>typescript</category>
      <category>ai</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Dev log #18 From Redis Brokers to Knowledge Graphs: A 177-Commit Sprint</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sun, 23 Aug 2026 16:26:50 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-18-from-redis-brokers-to-knowledge-graphs-a-177-commit-sprint-1mni</link>
      <guid>https://dev.to/yashksaini/dev-log-18-from-redis-brokers-to-knowledge-graphs-a-177-commit-sprint-1mni</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Hit a perfect 7-day streak this week, shipping 177 commits across systems architecture and personal tooling. Between hardening Redis brokers and fixing terminal hijacking in tmux, I managed to push nearly 7k lines of code across eight different projects.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;There’s a specific kind of satisfaction that comes from a perfect 7-day streak where the flow state just doesn't quit. This week was a massive 177-commit sprint, largely split between hardening the infrastructure for Reachable and refining the data pipelines in my opportunity-radar. I spent a lot of time in the "maintenance and hardening" phase—fixing memory leaks, bounding caches, and making sure my CLI tools don't hijack my terminal. With nearly 7,000 lines added and 2,700 removed, the codebase feels significantly more robust than it did last Monday.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Reachable: Security and Performance
&lt;/h3&gt;

&lt;p&gt;I spent the lion's share of my time (84 commits!) on &lt;strong&gt;Reachable&lt;/strong&gt;. Most of the work was centered around the &lt;code&gt;worker/reachable&lt;/code&gt; and &lt;code&gt;web/components&lt;/code&gt; areas. One of the more critical changes was in the ingest layer: I bounded the packument cache by size. If you've ever dealt with massive metadata ingestion, you know how quickly an unbounded cache can eat your RAM, so getting that logic in place was a huge win for stability.&lt;/p&gt;

&lt;p&gt;On the security side, I reworked how we handle keys. I updated the system to show minted keys as masked values instead of printing them whole (basic security hygiene, but easy to overlook early on) and added the ability for testers to revoke keys immediately. Waiting out a TTL just to test a revocation flow is a special kind of developer hell, so I’m glad that’s behind me. I also did some housekeeping by stripping out internal working notes from the docs and pointing everything to the proper references.&lt;/p&gt;

&lt;h3&gt;
  
  
  Opportunity-Radar: Scraping and UI
&lt;/h3&gt;

&lt;p&gt;My Python-based &lt;strong&gt;opportunity-radar&lt;/strong&gt; saw 40 commits this week. Scraping is always a game of whack-a-mole with edge cases. I spent a good chunk of Tuesday fixing a bug where neighboring listings were getting "glued" onto titles—one of those classic regex-adjacent headaches. &lt;/p&gt;

&lt;p&gt;To make the data actually usable, I implemented paging for the opportunities table and derived category filters directly from the data instead of hardcoding them. I also hooked up the dashboard to supply run tokens directly, which makes the manual trigger flow much smoother. It’s starting to feel like a real product rather than just a collection of scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Personal Stack: Obsidian and Portfolio
&lt;/h3&gt;

&lt;p&gt;I’m a big believer in "sharpening the saw," so I put 27 commits into my &lt;strong&gt;obsidian-vault&lt;/strong&gt;. I’ve been working on syncing the graph zoom and UI state across devices. There’s something deeply nerdy but satisfying about getting your &lt;code&gt;#stream&lt;/code&gt; and &lt;code&gt;#reference&lt;/code&gt; tags to color-coordinate perfectly across your knowledge base.&lt;/p&gt;

&lt;p&gt;I also gave my &lt;strong&gt;portfolio&lt;/strong&gt; some love. I’m a bit of a stickler for UX, so I spent time enlarging the ⌘K hint chips in the header so they actually look like controls. I also fixed a gnarly bug in the hero section where scrub audio would stay locked after a page reload. If you’re going to have audio on a dev portfolio, it has to work perfectly, or it’s just noise.&lt;/p&gt;

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

&lt;p&gt;I opened 6 PRs this week, and the results were a mix of "shipped it" and "back to the drawing board."&lt;/p&gt;

&lt;p&gt;Over at &lt;strong&gt;sixb&lt;/strong&gt;, I had two successful merges. One was a critical fix for the broker: I bounded a Redis command so a single stuck reply couldn't hang the entire publication pipeline. The other involved releasing event subscriptions for action runs that settled late—essentially plugging a memory leak I’d been hunting for a while. I did close one massive PR (+2,030 lines) regarding connector stores and sealed credentials without merging it. It was a big architectural pivot, and after looking at the diff, I realized I wanted to approach the credential sealing differently. Sometimes the best thing you can do for a codebase is realize a 2,000-line change isn't the right one.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;agent-orchestrator&lt;/strong&gt; repo, I’ve got two PRs currently open. They both deal with tmux integration. One fixes a bug where destroying a session would accidentally hand the orchestrator's terminal over to another session, and the other handles resuming agents when using a non-default tmux base-index. If you’ve ever had your terminal focus stolen by a background process, you know why these are high priority for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Discussions
&lt;/h2&gt;

&lt;p&gt;I was pretty active on the issue tracker this week, opening 5 issues across various projects. &lt;/p&gt;

&lt;p&gt;The most interesting one was in &lt;strong&gt;hydradb&lt;/strong&gt;, where I noticed an inconsistency in how HTTP and Bolt map &lt;code&gt;CLASS_FRESHNESS&lt;/code&gt; errors. One treats them as retryable, the other doesn't. That’s the kind of subtle bug that causes "it works on my machine" tickets, so I’m hoping to dive into the fix for that soon.&lt;/p&gt;

&lt;p&gt;I also tracked down a CPU spike in &lt;strong&gt;nezuko&lt;/strong&gt; where the runtime was spinning at 100% while idle and leaking file descriptors. There’s no faster way to lose my trust in a tool than having it cook my CPU while it’s doing literally nothing, so getting that issue documented (and subsequently closed) was a relief.&lt;/p&gt;

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

&lt;p&gt;This was a true polyglot week. &lt;strong&gt;TypeScript&lt;/strong&gt; was my primary driver for the web and worker logic in Reachable and sixb, while &lt;strong&gt;Python&lt;/strong&gt; handled the heavy lifting for the data scraping in opportunity-radar. I also dipped into &lt;strong&gt;Go&lt;/strong&gt; to add a &lt;code&gt;AO_DISABLE_GPU&lt;/code&gt; escape hatch for the agent-orchestrator—sometimes you just need a way to bypass broken drivers when you're working on desktop tools.&lt;/p&gt;

&lt;p&gt;The stats tell the story: 177 commits over 7 days. My add/delete ratio was about 2.5:1. While I love a good refactoring week where I delete more than I add, this week was clearly about expansion. Building out the paging systems, the credential stores (even the ones I pivoted on), and the dashboard features meant a lot of new logic hitting the disk.&lt;/p&gt;

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

&lt;p&gt;Next week, I’m focusing on the tmux fixes in agent-orchestrator. I want to get those PRs merged so the terminal handling is rock solid. I’m also planning to revisit the credential sealing logic I backed out of in sixb—I have a better idea now of how to handle the "sealed" part without making the storage layer too brittle.&lt;/p&gt;

&lt;p&gt;Consistency is key, and hitting a 7-day streak feels like I've got the momentum I need heading into September. See you on 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>opensource</category>
      <category>contentwriting</category>
      <category>productivity</category>
      <category>cli</category>
    </item>
    <item>
      <title>One rented /24 could eclipse a Kademlia node. Now it takes ten.</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Thu, 20 Aug 2026 18:56:59 +0000</pubDate>
      <link>https://dev.to/yashksaini/one-rented-24-could-eclipse-a-kademlia-node-now-it-takes-ten-121p</link>
      <guid>https://dev.to/yashksaini/one-rented-24-could-eclipse-a-kademlia-node-now-it-takes-ten-121p</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The setting
&lt;/h2&gt;

&lt;p&gt;py-libp2p speaks Kademlia — a distributed hash table where every node keeps a routing table of &lt;em&gt;other&lt;/em&gt; nodes, bucketed by how far their IDs sit from its own. You find a peer or a record by asking the nodes closest to the target, who point you closer, and closer, until you arrive. It's elegant, and it works because of one quiet assumption: &lt;strong&gt;the peers in your routing table are a fair sample of the network.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An eclipse attack breaks exactly that assumption. If an attacker can get &lt;em&gt;their&lt;/em&gt; nodes into enough of your routing buckets — specifically the closest-K slots for a target key — they don't need to break any crypto. They just surround you. Every lookup you make gets answered by them. They can hide records, feed you stale routing, or silently partition you from the real DHT. You're still online. You're still "connected." You're just connected to a lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signing authenticates the record, not the routing table
&lt;/h2&gt;

&lt;p&gt;The reason this one nags at me is that the DHT already has an integrity story: records are &lt;strong&gt;signed&lt;/strong&gt;. So my instinct — and I don't think I'm alone — was that the surface was mostly covered. If a malicious peer can't forge a record, how much damage can it do?&lt;/p&gt;

&lt;p&gt;A lot, it turns out, because signing and eclipse answer different questions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wg3k1oowvnql6fwstpj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wg3k1oowvnql6fwstpj.png" alt="Two different questions — signing only answers one" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Record signing&lt;/strong&gt; authenticates &lt;em&gt;content&lt;/em&gt;. Hand me a DHT record and I can verify the owning key produced it. I can't be fed forged values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eclipse&lt;/strong&gt; attacks &lt;em&gt;membership&lt;/em&gt;. The attacker never forges anything. They make sure the only peers you ever &lt;em&gt;ask&lt;/em&gt; are theirs, then answer with perfectly valid, perfectly signed records — just never the &lt;em&gt;whole&lt;/em&gt; set. Withholding has no signature. Silence has no signature.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the real surface isn't the record. It's &lt;strong&gt;how a peer earns a slot in a k-bucket.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the attack to measure it
&lt;/h2&gt;

&lt;p&gt;I didn't want to argue this from a whiteboard, so I built an eclipse-attack simulation inside py-libp2p — it lives at &lt;code&gt;tests/examples/attack_simulation/eclipse_attack/&lt;/code&gt;. It spins up a network of honest &lt;code&gt;KadDHT&lt;/code&gt; nodes, introduces malicious peers that &lt;strong&gt;flood the honest nodes' routing tables and poison DHT entries&lt;/strong&gt;, and then measures what actually degrades: a &lt;code&gt;RealAttackMetrics&lt;/code&gt; collector runs real lookups against the network and records the success rate as the attack takes hold.&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/examples/attack_simulation/eclipse_attack/attack_scenarios.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;execute&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;trio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open_nursery&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nursery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;mp&lt;/span&gt; &lt;span class="ow"&gt;in&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;malicious_peers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="ow"&gt;in&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;honest_peers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;nursery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_soon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;poison_dht_entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;nursery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_soon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flood_peer_table&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;honest_peer_tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point of the harness is to turn "eclipse is possible" into a number: &lt;strong&gt;what fraction of lookups can an attacker capture, and how does that change once the routing table stops admitting them so cheaply?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcrbt1e8pk54a0x6fvc16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcrbt1e8pk54a0x6fvc16.png" alt="How an eclipse fills a k-bucket — diverse subnets vs one attacker subnet" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The point of the harness is to turn "eclipse is possible" into a number. I drove the fork's real &lt;code&gt;KBucket.add_peer&lt;/code&gt; (&lt;code&gt;k = 20&lt;/code&gt;) with Sybil peers from attacker-controlled &lt;code&gt;/24&lt;/code&gt;s, running it with &lt;code&gt;MAX_PEERS_PER_SUBNET&lt;/code&gt; set to &lt;code&gt;0&lt;/code&gt; (the pre-#1399 behaviour) versus its default of &lt;code&gt;2&lt;/code&gt;. This is a component-level measurement — it isolates exactly what the admission rule changes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attacker capability&lt;/th&gt;
&lt;th&gt;Pre-#1399 (no filter)&lt;/th&gt;
&lt;th&gt;With #1399 (max 2 / subnet)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bucket slots captured — flood from a single &lt;code&gt;/24&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20 / 20 (100%)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 / 20 (10%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distinct &lt;code&gt;/24&lt;/code&gt;s needed to fully own a 20-slot bucket&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Share of slots taken when the bucket already holds 10 honest, subnet-diverse peers&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Before the fix, one rented &lt;code&gt;/24&lt;/code&gt; owns the whole bucket. After it, an attacker needs &lt;strong&gt;ten&lt;/strong&gt; genuinely distinct networks to do the same — the cost moved from "grind cheap IDs" to "rent diverse address space," which is the entire point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root cause
&lt;/h2&gt;

&lt;p&gt;In vanilla Kademlia, a peer earns a bucket slot mostly by being live and having an ID that lands in range. Node IDs are cheap — you can grind as many as you want. So an attacker spins up a Sybil fleet and, crucially, parks them at &lt;strong&gt;IP addresses in a subnet they control&lt;/strong&gt;. Their cheap resource is node IDs; the resource they'd actually have to &lt;em&gt;spend&lt;/em&gt; is distinct network positions — and nothing in the base admission rule was pricing that.&lt;/p&gt;

&lt;p&gt;A rented cloud block is typically a &lt;strong&gt;/24&lt;/strong&gt;, not a scattering of unrelated addresses. That's the lever: if admission is priced in &lt;em&gt;distinct subnets&lt;/em&gt; rather than &lt;em&gt;distinct IDs&lt;/em&gt;, surrounding a node suddenly costs real, diverse address space the attacker can't cheaply conjure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix — #1399: enforce IP subnet diversity in k-buckets
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/libp2p/py-libp2p/pull/1399" rel="noopener noreferrer"&gt;libp2p/py-libp2p#1399&lt;/a&gt; (closes &lt;a href="https://github.com/libp2p/py-libp2p/issues/1383" rel="noopener noreferrer"&gt;#1383&lt;/a&gt;) makes &lt;code&gt;KBucket.add_peer&lt;/code&gt; reject a new peer when its globally-routable &lt;strong&gt;/24 (IPv4)&lt;/strong&gt; or &lt;strong&gt;/48 (IPv6)&lt;/strong&gt; subnet already holds &lt;code&gt;MAX_PEERS_PER_SUBNET&lt;/code&gt; (default &lt;strong&gt;2&lt;/strong&gt;) peers in that bucket:&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;# IP/subnet diversity limits for k-buckets (issue #1383). Within one bucket,
# at most MAX_PEERS_PER_SUBNET peers may share the same globally-routable subnet.
# /24 matches realistic attacker economics — a rented cloud block is typically
# a /24, not a /16 — and avoids bundling an ASN dataset.
&lt;/span&gt;&lt;span class="n"&gt;MAX_PEERS_PER_SUBNET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_subnet_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer_info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PeerInfo&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="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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;peer_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p2p-circuit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;      &lt;span class="c1"&gt;# relayed addr = relay's IP, not the peer's
&lt;/span&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix_len&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ip4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SUBNET_PREFIX_LEN_V4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                                  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ip6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SUBNET_PREFIX_LEN_V6&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="bp"&gt;...&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_global&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;            &lt;span class="c1"&gt;# loopback/private/CGNAT/link-local → exempt
&lt;/span&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ip_network&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;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix_len&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The details are where the real engineering is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only globally-routable addresses are grouped&lt;/strong&gt; (&lt;code&gt;ip.is_global&lt;/code&gt;) — loopback, RFC1918/ULA private, CGNAT (&lt;code&gt;100.64.0.0/10&lt;/code&gt;), link-local, and documentation ranges are exempt, so CI and local testnets are unaffected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS-named and relayed (&lt;code&gt;p2p-circuit&lt;/code&gt;) peers are exempt&lt;/strong&gt; — a circuit address exposes the &lt;em&gt;relay's&lt;/em&gt; IP, not the peer's, so grouping on it would wrongly bundle distinct peers behind a shared relay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-homed peers&lt;/strong&gt; are grouped by their first globally-routable address (a deliberate divergence from go-libp2p, which checks &lt;em&gt;every&lt;/em&gt; address — simpler, and it avoids false rejections of legitimately multi-homed peers).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reject-only, no eviction&lt;/strong&gt; — matching go-libp2p's &lt;code&gt;TryAddPeer&lt;/code&gt;, which avoids opening a churn/DoS vector where an attacker forces evictions.&lt;/li&gt;
&lt;li&gt;Opt-out via &lt;code&gt;MAX_PEERS_PER_SUBNET &amp;lt;= 0&lt;/code&gt;, and a guard so a subnet rejection on a &lt;em&gt;non-full&lt;/em&gt; bucket can't trigger a spurious bucket split.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No protocol or wire change — just a stricter admission rule at exactly the layer the eclipse attacks.&lt;/p&gt;

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

&lt;p&gt;The trap wasn't a bug in the code — it was a bug in the &lt;em&gt;question&lt;/em&gt;. "Records are signed, so the DHT is safe" quietly swaps the question you need answered ("is this set of peers a fair sample?") for one you've already answered ("is this record authentic?"). Authentication and membership-diversity are orthogonal: signing is a real defense against forgery and &lt;strong&gt;no defense at all&lt;/strong&gt; against being surrounded by valid liars.&lt;/p&gt;

&lt;p&gt;The broader habit I'm keeping: when someone says "that's already handled," ask &lt;em&gt;which property&lt;/em&gt; is handled. The most dangerous vulnerabilities live in the gap between two defenses that each look complete on their own.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Fix: &lt;a href="https://github.com/libp2p/py-libp2p/pull/1399" rel="noopener noreferrer"&gt;libp2p/py-libp2p#1399 — feat(kad_dht): enforce IP subnet diversity in k-buckets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Issue: &lt;a href="https://github.com/libp2p/py-libp2p/issues/1383" rel="noopener noreferrer"&gt;#1383&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Eclipse-attack simulation: &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; in py-libp2p&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>python</category>
      <category>security</category>
    </item>
    <item>
      <title>The /sdp endpoint that trusted its callers: fixing a memory-amplification DoS in py-libp2p</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Thu, 20 Aug 2026 18:56:05 +0000</pubDate>
      <link>https://dev.to/yashksaini/the-sdp-endpoint-that-trusted-its-callers-fixing-a-memory-amplification-dos-in-py-libp2p-3g2e</link>
      <guid>https://dev.to/yashksaini/the-sdp-endpoint-that-trusted-its-callers-fixing-a-memory-amplification-dos-in-py-libp2p-3g2e</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/libp2p/py-libp2p" rel="noopener noreferrer"&gt;py-libp2p&lt;/a&gt; is the Python implementation of libp2p — the peer-to-peer networking stack that underpins IPFS, Filecoin, and Ethereum-class nodes. I've been working on its &lt;strong&gt;WebRTC-Direct&lt;/strong&gt; transport, which lets two peers connect without a certificate authority: the peer's multiaddr carries a hash of its TLS cert, and the DTLS handshake is verified against it.&lt;/p&gt;

&lt;p&gt;Before that encrypted transport exists, the two sides have to swap SDP offer/answer blobs. Until the STUN-based listener lands (&lt;a href="https://github.com/libp2p/py-libp2p/issues/1352" rel="noopener noreferrer"&gt;#1352&lt;/a&gt;), py-libp2p ships a minimal dev harness for this: a tiny hand-rolled HTTP server (no aiohttp dependency) that accepts an SDP offer over &lt;code&gt;POST /sdp&lt;/code&gt; and hands the body to an offer handler. This post is about a memory-amplification DoS I found and fixed in that harness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;POST /sdp&lt;/code&gt; handler read the caller's &lt;code&gt;Content-Length&lt;/code&gt; and buffered exactly that many bytes — &lt;strong&gt;with no upper bound&lt;/strong&gt; — before the handshake ever happened. Here's the pre-fix read path in &lt;code&gt;_aiortc_helpers.py&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;# Read HTTP request line (consumed but not used) + headers
&lt;/span&gt;&lt;span class="k"&gt;await&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;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_SDP_HTTP_TIMEOUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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="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="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_SDP_HTTP_TIMEOUT&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&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;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;(&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="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;content_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content-length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;content_length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readexactly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_length&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_SDP_HTTP_TIMEOUT&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;Two things are attacker-controlled and unbounded:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The body.&lt;/strong&gt; &lt;code&gt;content_length&lt;/code&gt; comes straight from the request. &lt;code&gt;reader.readexactly(content_length)&lt;/code&gt; accumulates that many bytes into memory — and &lt;code&gt;readexactly&lt;/code&gt; bypasses the &lt;code&gt;StreamReader&lt;/code&gt;'s default 64 KiB flow-control limit, so nothing throttles it. &lt;code&gt;body.decode()&lt;/code&gt; makes a second copy, the handler string a third. Net amplification ≈ &lt;strong&gt;2× the bytes on the wire, with no ceiling.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The headers.&lt;/strong&gt; &lt;code&gt;while True&lt;/code&gt; with only a &lt;em&gt;per-line&lt;/em&gt; timeout: an attacker can keep sending header lines forever — the loop never counts them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is pre-handshake, unauthenticated input. Anyone who can reach the port can trigger it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3ktz26wodvajo5cwxov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3ktz26wodvajo5cwxov.png" alt="Anatomy of the /sdp memory-amplification DoS — before vs after" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where I found it:&lt;/strong&gt; a reviewer flagged the &lt;em&gt;shape&lt;/em&gt; of this inside my own WebRTC PR (&lt;a href="https://github.com/libp2p/py-libp2p/pull/1309" rel="noopener noreferrer"&gt;#1309&lt;/a&gt;) — a one-line "reads the whole body into memory with no cap, availability risk" note. Easy to read as a style nit; it wasn't. Pre-handshake input is attacker-controlled by definition, and "reads the whole body with no cap" is the entire exploit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The numbers.&lt;/strong&gt; I built a reproduction harness that imports the &lt;em&gt;real&lt;/em&gt; &lt;code&gt;run_signaling_server&lt;/code&gt; at the pre-fix parent (&lt;code&gt;9506041&lt;/code&gt;) and the merged fix (&lt;code&gt;759c75b&lt;/code&gt;), fires the malicious request, and samples RSS on a 20 ms timer:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before (pre-fix)&lt;/th&gt;
&lt;th&gt;After (#1396)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Peak RSS, 512 MiB malicious body&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1,113 MB&lt;/strong&gt; (+1,052)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;61 MB&lt;/strong&gt; (+0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak RSS, 1 GiB malicious body&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2,137 MB&lt;/strong&gt; (+2,076)&lt;/td&gt;
&lt;td&gt;rejected, idle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak RSS, 4 concurrent × 300 MiB&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1,575 MB&lt;/strong&gt; (+1,514)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;88 MB&lt;/strong&gt; (+27)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to refuse oversized request&lt;/td&gt;
&lt;td&gt;never (buffers to OOM)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0.3 ms&lt;/strong&gt; (&lt;code&gt;413&lt;/code&gt; on headers alone)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Body size accepted&lt;/td&gt;
&lt;td&gt;unbounded&lt;/td&gt;
&lt;td&gt;32 KiB (&lt;code&gt;413&lt;/code&gt; above)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo5jcjech9v0gnnouoqs6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo5jcjech9v0gnnouoqs6.png" alt="Peak RSS under a malicious POST, before vs after #1396" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Measured in a Python 3.11 / aiortc 1.15 sandbox on a single asyncio loop — re-run the harness on your own hardware for local figures; the ~2× ratio holds.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PR: &lt;a href="https://github.com/libp2p/py-libp2p/pull/1396" rel="noopener noreferrer"&gt;libp2p/py-libp2p#1396 — fix(webrtc): harden /sdp HTTP server against memory-amplification DoS&lt;/a&gt;&lt;/strong&gt; · merged &lt;strong&gt;Aug 14, 2026&lt;/strong&gt; · closes &lt;strong&gt;&lt;a href="https://github.com/libp2p/py-libp2p/issues/1354" rel="noopener noreferrer"&gt;#1354&lt;/a&gt;&lt;/strong&gt; · commit &lt;a href="https://github.com/libp2p/py-libp2p/commit/759c75b" rel="noopener noreferrer"&gt;&lt;code&gt;759c75b&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The fix adds three bounded constants and rejects &lt;em&gt;before&lt;/em&gt; touching any body buffer:&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;# Bounds for the HTTP /sdp dev harness — defend against memory-amplification
# DoS while the harness exists (until the STUN-based listener lands, #1352).
&lt;/span&gt;&lt;span class="n"&gt;_MAX_SDP_BODY_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;  &lt;span class="c1"&gt;# 32 KiB; SDP offers are typically 1–4 KiB
&lt;/span&gt;&lt;span class="n"&gt;_MAX_HEADER_LINES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
&lt;span class="n"&gt;_MAX_HEADER_BYTES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;    &lt;span class="c1"&gt;# 8 KiB total across all header lines
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headers are bounded by line count and cumulative bytes (the &lt;code&gt;for/else&lt;/code&gt; fires a &lt;code&gt;400&lt;/code&gt; if the terminator never arrives):&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;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&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="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="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;header_bytes&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;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_MAX_HEADER_LINES&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_SDP_HTTP_TIMEOUT&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;header_bytes&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;line&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;header_bytes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_MAX_HEADER_BYTES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 400 Bad Request&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 0&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drain&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&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;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;(&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="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 400 Bad Request&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 0&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drain&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;Content-Length&lt;/code&gt; is validated before any body buffer exists — malformed/negative → &lt;code&gt;400&lt;/code&gt;, oversized → &lt;code&gt;413&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="n"&gt;raw_cl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content-length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;content_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_cl&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;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 400 Bad Request&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 0&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drain&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;content_length&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="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 400 Bad Request&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 0&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drain&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;content_length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_MAX_SDP_BODY_SIZE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP/1.1 413 Payload Too Large&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 0&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&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;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drain&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PR also ships a 228-line regression test (&lt;a href="https://github.com/libp2p/py-libp2p/pull/1396/files" rel="noopener noreferrer"&gt;&lt;code&gt;test_aiortc_helpers.py&lt;/code&gt;&lt;/a&gt;) whose key assertion checks that RSS &lt;em&gt;didn't grow&lt;/em&gt; after a &lt;code&gt;413&lt;/code&gt; — not just the status 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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_too_large&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;rss_before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getrusage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUSAGE_SELF&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;ru_maxrss&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST /sdp HTTP/1.1&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Content-Length: 999999999&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;413&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
    &lt;span class="n"&gt;rss_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getrusage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUSAGE_SELF&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;ru_maxrss&lt;/span&gt;
    &lt;span class="nf"&gt;assert &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rss_after&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rss_before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;one_mib&lt;/span&gt;   &lt;span class="c1"&gt;# body buffer was never allocated
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;I deliberately chose a &lt;strong&gt;hard cap&lt;/strong&gt; over a streaming-with-ceiling read. SDP offers are 1–4 KiB in practice; no legitimate caller needs to POST megabytes here, so a fixed 32 KiB ceiling with early rejection is simpler &lt;em&gt;and&lt;/em&gt; safer than draining a bounded stream. Rejecting on the &lt;code&gt;Content-Length&lt;/code&gt; header alone means the oversized request never allocates a buffer at all — which is why the "after" column of the table plateaus at the idle baseline and the refusal lands in 0.3 ms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg3fgm3ow3kt4vt2u4355.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg3fgm3ow3kt4vt2u4355.png" alt="The fix: three gates before a byte of body is buffered" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impact in context:&lt;/strong&gt; py-libp2p underpins Python IPFS/Filecoin-class nodes. A signaling endpoint that OOM-kills the whole process on unauthenticated, pre-handshake input is a cheap remote crash — no credentials, no handshake, one connection. Bounding it to 32 KiB turns "attacker picks my memory ceiling" into "attacker gets a 413."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I learned:&lt;/strong&gt; the reviewer's one-line "availability risk" aside was a working exploit, and I almost filed it under style. Pre-handshake input is attacker-controlled by definition — "trust the &lt;code&gt;Content-Length&lt;/code&gt;" is never a safe default. And the &lt;em&gt;test&lt;/em&gt; matters as much as the fix: asserting &lt;code&gt;413&lt;/code&gt; proves the status code; asserting RSS-didn't-grow proves the property — that no buffer was allocated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reproduce it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/libp2p/py-libp2p &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;py-libp2p
pip &lt;span class="nb"&gt;install &lt;/span&gt;aiortc
git checkout 9506041   &lt;span class="c"&gt;# PRE-FIX: RSS climbs to ~1.1 GB, 200 OK&lt;/span&gt;
python3 exploit.py PREFIX body 512
git checkout 759c75b   &lt;span class="c"&gt;# POST-FIX (#1396): 413 in 0.3 ms, RSS flat&lt;/span&gt;
python3 exploit.py POSTFIX bodyreject 512
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Use of Sentry
&lt;/h2&gt;

&lt;p&gt;There's no first-class trio integration for Sentry (its Python integrations are asyncio / Django / Flask / FastAPI / AIOHTTP / etc.), so I attached manually — and it fit cleanly because the pieces genuinely apply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;/sdp&lt;/code&gt; server is plain asyncio&lt;/strong&gt;, so wrapping the handler in &lt;code&gt;sentry_sdk.start_transaction(op="sdp.post", name="/sdp handler")&lt;/code&gt; gave me a real trace waterfall — the span sitting on &lt;code&gt;reader.readexactly()&lt;/code&gt; is visibly the culprit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AsyncioIntegration&lt;/code&gt; actually applies&lt;/strong&gt; — the WebRTC bridge from #1309 runs a real asyncio event loop in a daemon thread, so the integration isn't decorative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SocketIntegration&lt;/code&gt;&lt;/strong&gt; adds DNS/connect spans for free, on-topic for a P2P lib.&lt;/li&gt;
&lt;li&gt;I emitted RSS as a &lt;strong&gt;custom measurement&lt;/strong&gt; so the before/after shows up inside Sentry:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk.integrations.asyncio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncioIntegration&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk.integrations.socket&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SocketIntegration&lt;/span&gt;

&lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="p"&gt;.&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;dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_DSN&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traces_sample_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profiles_sample_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;integrations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AsyncioIntegration&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;SocketIntegration&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;rss_mb&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;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getrusage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUSAGE_SELF&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;ru_maxrss&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;  &lt;span class="c1"&gt;# Linux: KB→MB
&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sentry_sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sdp.post&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/sdp handler&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_measurement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rss_before_mb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;rss_mb&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;megabyte&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ... handle request ...
&lt;/span&gt;    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_measurement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rss_after_mb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;rss_mb&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;megabyte&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the captured trace — the &lt;code&gt;sdp.read_body&lt;/code&gt; span running &lt;code&gt;reader.readexactly(157286400)&lt;/code&gt; is the whole story, and the &lt;code&gt;rss_before_mb&lt;/code&gt; / &lt;code&gt;rss_after_mb&lt;/code&gt; measurements ride along on the transaction:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo0t4md6a081hwvzypz0j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo0t4md6a081hwvzypz0j.png" alt="Sentry trace waterfall — the readexactly span is the culprit" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seer's root-cause analysis&lt;/strong&gt; — this was the standout. I ran Seer on the captured event and, with no knowledge of the fix I'd already shipped, it pinned the exact cause and proposed essentially the same patch:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Focj1p8hwyczj4x0yrs8n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Focj1p8hwyczj4x0yrs8n.png" alt="Sentry Seer RCA — independently proposes a body cap plus header caps" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quoting it: Seer flagged a &lt;strong&gt;"+196 MB in one request"&lt;/strong&gt; spike, found that &lt;code&gt;handle()&lt;/code&gt; "reads &lt;code&gt;Content-Length&lt;/code&gt; directly from the attacker-controlled HTTP header and passes it verbatim to &lt;code&gt;reader.readexactly(content_length)&lt;/code&gt; … with no upper-bound check," &lt;em&gt;and&lt;/em&gt; separately caught the header loop as an "unbounded while-loop." Its recommended fix — a &lt;code&gt;MAX_SDP_BODY_SIZE&lt;/code&gt; cap that returns &lt;code&gt;413 Payload Too Large&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; the read, plus per-line/total header caps — is the same shape as &lt;a href="https://github.com/libp2p/py-libp2p/pull/1396" rel="noopener noreferrer"&gt;#1396&lt;/a&gt;. The only real difference: Seer suggested a 64 KiB cap; I shipped 32 KiB. Honest verdict: &lt;strong&gt;right, and specific enough to act on&lt;/strong&gt; — the uncommon case where the AI root-cause analysis lands the actual fix rather than a vague "add validation."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Session Replay:&lt;/strong&gt; not applicable — there's no browser in this path, so I didn't fake a screenshot. The honest "N/A" is more useful to a maintainer than a contrived one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Use of Google AI
&lt;/h2&gt;

&lt;p&gt;I pasted the pre-fix read path into &lt;strong&gt;Google AI Studio (Gemini)&lt;/strong&gt; and asked for three concrete hypotheses for the unbounded memory growth, ranked by likelihood, with a one-line fix for each.&lt;/p&gt;

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

&lt;p&gt;Its &lt;strong&gt;#1, ranked "Most Likely," was exactly right&lt;/strong&gt;: &lt;em&gt;"the server trusts the client-provided &lt;code&gt;content-length&lt;/code&gt; header and attempts to buffer the full amount into RAM … an attacker can send a large value (e.g. &lt;code&gt;10^9&lt;/code&gt;) and slowly stream data, forcing the server to grow its internal buffer to match."&lt;/em&gt; It even independently named the ~2× amplification I'd measured — &lt;em&gt;"Python's object-copying behaviour is acting as a memory multiplier."&lt;/em&gt; The guard it proposed (&lt;code&gt;if content_length &amp;gt; 1_048_576: raise …&lt;/code&gt;) is the same shape as the fix I shipped in &lt;a href="https://github.com/libp2p/py-libp2p/pull/1396" rel="noopener noreferrer"&gt;#1396&lt;/a&gt; — the only difference is the cap size (Gemini picked 1 MiB; I used 32 KiB, since real SDP offers are 1–4 KiB). Honest read: for a tightly-scoped prompt, Gemini's top-ranked hypothesis landed the real bug on the first try.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>webrtc</category>
      <category>security</category>
    </item>
    <item>
      <title>The test was green. Every real connection would have failed.</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Thu, 20 Aug 2026 18:54:53 +0000</pubDate>
      <link>https://dev.to/yashksaini/the-test-was-green-every-real-connection-would-have-failed-3e37</link>
      <guid>https://dev.to/yashksaini/the-test-was-green-every-real-connection-would-have-failed-3e37</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The setting
&lt;/h2&gt;

&lt;p&gt;WebRTC-Direct in libp2p has a neat trick for connecting without a certificate authority: the peer's multiaddr &lt;em&gt;contains&lt;/em&gt; a hash of its TLS certificate — &lt;code&gt;/webrtc-direct/certhash/&amp;lt;...&amp;gt;&lt;/code&gt;. When you dial, the DTLS handshake presents a cert, you hash it, and you check it against the &lt;code&gt;certhash&lt;/code&gt; in the address. No CA, no trust store — the address &lt;em&gt;is&lt;/em&gt; the pin.&lt;/p&gt;

&lt;p&gt;For that to work, py-libp2p has to make aiortc use &lt;em&gt;our&lt;/em&gt; libp2p-generated certificate, not the one aiortc auto-generates for itself. So the code pins it:&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;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;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="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean. Obvious. And, as of aiortc ≥ 1.5, completely wrong — in two different ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first sign
&lt;/h2&gt;

&lt;p&gt;The first way was loud: aiortc ≥ 1.5 &lt;strong&gt;dropped &lt;code&gt;certificates=&lt;/code&gt; from &lt;code&gt;RTCConfiguration&lt;/code&gt;&lt;/strong&gt;. Passing it raises &lt;code&gt;TypeError&lt;/code&gt;. Easy to spot, easy to "fix" — just move the cert onto the object after construction:&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;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="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;rtc_cert&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# looks right. isn't.
&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;The tests went green. The loopback echo test — open a data channel, send a payload, get it back — passed. I could have shipped it right there.&lt;/p&gt;

&lt;p&gt;The quiet way is the one that would have burned every user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The investigation
&lt;/h2&gt;

&lt;p&gt;Here's the thing that made this a &lt;em&gt;story&lt;/em&gt; and not a one-liner. &lt;code&gt;pc._certificates = [...]&lt;/code&gt; is a &lt;strong&gt;silent no-op.&lt;/strong&gt; aiortc never reads &lt;code&gt;_certificates&lt;/code&gt;. Inside the class, it stores and reads the cert as &lt;code&gt;self.__certificates&lt;/code&gt; — a &lt;em&gt;double-underscore&lt;/em&gt; attribute. And Python does something specific with double-underscore names inside a class body: it &lt;strong&gt;mangles&lt;/strong&gt; them. &lt;code&gt;self.__certificates&lt;/code&gt; inside &lt;code&gt;class RTCPeerConnection&lt;/code&gt; is compiled to &lt;code&gt;self._RTCPeerConnection__certificates&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So from the outside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pc._certificates&lt;/code&gt; — a brand-new attribute I invented. aiortc reads it never.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pc._RTCPeerConnection__certificates&lt;/code&gt; — the &lt;em&gt;actual&lt;/em&gt; slot aiortc reads at &lt;code&gt;createOffer&lt;/code&gt;/&lt;code&gt;createAnswer&lt;/code&gt; time to write the SDP &lt;code&gt;a=fingerprint&lt;/code&gt; line (see &lt;code&gt;aiortc/rtcpeerconnection.py:295&lt;/code&gt; and &lt;code&gt;:1129&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was setting the first one. aiortc was reading the second one — still holding its own auto-generated cert. The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;/webrtc-direct/certhash/&amp;lt;...&amp;gt;&lt;/code&gt; multiaddr advertised &lt;strong&gt;our&lt;/strong&gt; libp2p cert hash.&lt;/li&gt;
&lt;li&gt;The DTLS handshake used &lt;strong&gt;aiortc's&lt;/strong&gt; auto-generated cert.&lt;/li&gt;
&lt;li&gt;Every real dial would fail peer verification with &lt;code&gt;Remote DTLS fingerprint does not match certhash&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the loopback echo test &lt;em&gt;still passed&lt;/em&gt; — because loopback doesn't validate the DTLS fingerprint against a multiaddr. It just opens a channel and echoes bytes. The one property that was broken was the one property the happy-path test never checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root cause
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9cstrtlc4fo392gw0f7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9cstrtlc4fo392gw0f7p.png" alt="Why pc._certificates is a silent no-op — Python name mangling" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a bug that lives in the seam between two systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python's language rule:&lt;/strong&gt; name mangling rewrites &lt;code&gt;__name&lt;/code&gt; references &lt;em&gt;inside a class body&lt;/em&gt; so subclasses can't accidentally clobber a base class's "private" attributes. It's a language feature doing exactly what it's designed to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;aiortc's library internals:&lt;/strong&gt; it stores the cert under &lt;code&gt;__certificates&lt;/code&gt;, i.e. it &lt;em&gt;relies&lt;/em&gt; on that mangling for its own encapsulation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither is wrong on its own. But when you reach into a library's private state from outside the class, the mangled name is the &lt;em&gt;only&lt;/em&gt; name that works — and the un-mangled one you'd naturally type is a no-op that fails silently. There's no &lt;code&gt;AttributeError&lt;/code&gt;, no &lt;code&gt;TypeError&lt;/code&gt;, no warning. You just create a junk attribute and move on, and everything downstream keeps working &lt;em&gt;except the security property.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Set the mangled attribute directly, after construction, before any SDP operation triggers the handshake:&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;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;# Replace aiortc's auto-generated cert. Must use the mangled name —
# aiortc reads only `self.__certificates`, which mangles to this.
&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;It's an ugly line, and the docstring says so out loud — reaching into &lt;code&gt;_RTCPeerConnection__certificates&lt;/code&gt; is exactly the kind of private-state coupling that breaks on a library upgrade. But it's &lt;em&gt;correct&lt;/em&gt;, and it's &lt;em&gt;annotated&lt;/em&gt; so the next person knows why it can't just be &lt;code&gt;pc._certificates&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The canary that would have caught it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8nlrat0yla811q1zayr6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8nlrat0yla811q1zayr6.png" alt="Two tests, one bug — only the invariant test sees it" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The real fix wasn't the one-liner — it was the test that makes the silent failure loud. The happy-path echo test can't see this bug, so I added one that asserts the &lt;em&gt;invariant&lt;/em&gt;: the pinned cert's fingerprint must actually appear in the SDP.&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="nd"&gt;@pytest.mark.trio&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;test_cert_pinning_lands_in_sdp_fingerprint&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Guards against a class of cert-pin bug: if the pin is a no-op (e.g.
    written to a public attribute aiortc never reads because it actually
    stores the cert under a name-mangled private slot), the SDP a=fingerprint
    line reflects aiortc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s auto-generated cert instead of ours.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;cert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebRTCCertificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_aiortc&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_coro&lt;/span&gt;&lt;span class="p"&gt;(&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;cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_rtc_certificate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ice_servers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]))&lt;/span&gt;

    &lt;span class="c1"&gt;# Need at least one data channel for the SCTP m-line, otherwise
&lt;/span&gt;    &lt;span class="c1"&gt;# createOffer omits the DTLS fingerprint entirely.
&lt;/span&gt;    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_coro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_create_dummy_channel&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;sdp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_coro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_offer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sdp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SDP fingerprint does not match pinned cert.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the small landmine inside the fix for the &lt;em&gt;test&lt;/em&gt;: &lt;code&gt;createOffer&lt;/code&gt; omits the DTLS fingerprint entirely unless there's at least one SCTP m-line, so the canary has to create a dummy data channel first or it asserts against an empty offer. The bug had layers even in the reproduction.&lt;/p&gt;

&lt;p&gt;Revert the mangled-name fix and this test goes red immediately with a fingerprint mismatch. Keep the old &lt;code&gt;pc._certificates = [...]&lt;/code&gt; no-op and it's red. That's the whole point: it fails &lt;em&gt;closed&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;Two lessons, and they're the same lesson from two directions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test the invariant, not the happy path.&lt;/strong&gt; The echo test proved bytes moved. It said nothing about &lt;em&gt;which certificate&lt;/em&gt; moved them — and that was the only thing that mattered for security. A green test is only as good as the property it asserts, and "it worked end to end" quietly excludes every property your end-to-end path doesn't check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A silent no-op is worse than a crash.&lt;/strong&gt; If &lt;code&gt;pc._certificates = [...]&lt;/code&gt; had raised, I'd have fixed it in thirty seconds. Because it &lt;em&gt;succeeded&lt;/em&gt; — created a real attribute, threw no error — it sailed through review and tests and would have shipped a transport where every authenticated dial fails. When you reach across an encapsulation boundary into a library's private state, assume the language is doing something clever with the name, and write the test that proves your write actually landed where the library reads.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Commit: &lt;a href="https://github.com/libp2p/py-libp2p/commit/ca8331ff" rel="noopener noreferrer"&gt;&lt;code&gt;ca8331ff&lt;/code&gt; — fix(webrtc): pin DTLS certificate via aiortc's mangled private slot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Canary test: &lt;a href="https://github.com/libp2p/py-libp2p/blob/main/tests/core/transport/webrtc/test_multiplexing_loopback.py" rel="noopener noreferrer"&gt;&lt;code&gt;test_cert_pinning_lands_in_sdp_fingerprint&lt;/code&gt;&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Context: &lt;a href="https://github.com/libp2p/py-libp2p/issues/546" rel="noopener noreferrer"&gt;#546&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;aiortc internals referenced: &lt;code&gt;aiortc/rtcpeerconnection.py:295, 1129&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>Dev log #17 From 16-bit TUIs to p2p Hardening: A 227-Commit Week</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sun, 16 Aug 2026 13:14:30 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-17-from-16-bit-tuis-to-p2p-hardening-a-227-commit-week-1cbg</link>
      <guid>https://dev.to/yashksaini/dev-log-17-from-16-bit-tuis-to-p2p-hardening-a-227-commit-week-1cbg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A high-output week focused on shipping GitBanner 1.3.0, hardening WebRTC and DHT layers in py-libp2p, and archiving kad-monitor with a proper demo harness. 227 commits, 16 PRs, and a perfect 7-day streak.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;This week was a massive exercise in context switching, but in the best way possible. I logged 227 commits across a dozen repos, ranging from low-level p2p networking fixes in Python to aesthetic TUI refinements in TypeScript. The highlights? Shipping a 16-bit type system for GitBanner, hardening py-libp2p against DoS attacks, and finally archiving my kad-monitor project with the dignity of a full demo harness.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  GitBanner: The Aesthetic CLI
&lt;/h3&gt;

&lt;p&gt;I spent a huge chunk of my time (95 commits!) on &lt;a href="https://github.com/yashksaini-coder/GitBanner" rel="noopener noreferrer"&gt;GitBanner&lt;/a&gt;. I’m prepping the 1.3.0 release, and I went down a rabbit hole with the TUI type system. I implemented a 16-bit CLI/TUI aesthetic—using Silkscreen for titles and VT323 for basically everything else. It gives the tool this retro-terminal vibe that I’m obsessed with. &lt;/p&gt;

&lt;p&gt;Beyond the visuals, I did a lot of "invisible" work: rebuilding &lt;code&gt;dist&lt;/code&gt;, regenerating cards, and cleaning up the caption logic so the 'total commits' count doesn't feel redundant next to the 52-week scope. It’s about that polish.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obsidian &amp;amp; Personal Knowledge
&lt;/h3&gt;

&lt;p&gt;My &lt;a href="https://github.com/yashksaini-coder/obsidian-vault" rel="noopener noreferrer"&gt;obsidian-vault&lt;/a&gt; saw 58 commits this week. I know, "commits to a vault?" but I treat my knowledge base like a codebase. I overhauled the graph palette to ensure every category has a vibrant, distinct family based on perceptual distance. I also built a &lt;code&gt;calendar-sync&lt;/code&gt; tool to repair frontmatter and backfill my August 2026 daily notes. If it isn't automated, I won't do it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kad-Monitor: The Final Chapter
&lt;/h3&gt;

&lt;p&gt;I decided to officially archive &lt;a href="https://github.com/yashksaini-coder/kad-monitor" rel="noopener noreferrer"&gt;kad-monitor&lt;/a&gt;. But I didn't want to just flip the bit and walk away. I spent 37 commits building a proper "Demo Harness." I merged &lt;a href="https://github.com/yashksaini-coder/kad-monitor/pull/1" rel="noopener noreferrer"&gt;PR #1&lt;/a&gt;, which includes a runnable simulation, a full dashboard, and A/B experiment results. I also documented its limitations honestly in the README. There's something satisfying about parking a project with a clean deployment guide and CI badges rather than letting it rot.&lt;/p&gt;

&lt;h2&gt;
  
  
  P2P &amp;amp; Backend Hardening
&lt;/h2&gt;

&lt;p&gt;Most of my "serious" engineering time went into &lt;a href="https://github.com/libp2p/py-libp2p" rel="noopener noreferrer"&gt;py-libp2p&lt;/a&gt;. P2P networking is unforgiving, and this week was all about resilience.&lt;/p&gt;

&lt;p&gt;I opened &lt;a href="https://github.com/libp2p/py-libp2p/pull/1442" rel="noopener noreferrer"&gt;PR #1442&lt;/a&gt; to enforce IP subnet diversity in Kademlia DHT k-buckets. In a decentralized network, you don't want all your peers coming from the same subnet—that's a centralization risk. I also tackled a security concern in &lt;a href="https://github.com/libp2p/py-libp2p/pull/1396" rel="noopener noreferrer"&gt;PR #1396&lt;/a&gt;, hardening the &lt;code&gt;/sdp&lt;/code&gt; HTTP server against memory-amplification DoS attacks.&lt;/p&gt;

&lt;p&gt;One of the more satisfying fixes was &lt;a href="https://github.com/libp2p/py-libp2p/pull/1441" rel="noopener noreferrer"&gt;PR #1441&lt;/a&gt;. I noticed our varint decoders were accepting truncated or over-long inputs. It’s a classic edge case that can lead to all sorts of weirdness down the line, so I tightened that up to reject malformed data immediately.&lt;/p&gt;

&lt;p&gt;I also took a swing at &lt;a href="https://github.com/hydra-db/hydradb" rel="noopener noreferrer"&gt;hydradb&lt;/a&gt;, opening &lt;a href="https://github.com/hydra-db/hydradb/pull/80" rel="noopener noreferrer"&gt;PR #80&lt;/a&gt; to optimize cursor performance. The old logic was rescanning the cursor tail on every page drain while holding a global mutex—not exactly great for concurrency. I swapped it to subtract drained bytes instead, which should feel much snappier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Discussions
&lt;/h2&gt;

&lt;p&gt;I opened 13 issues this week, mostly as I was dogfooding my own tools or digging into upstream dependencies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;HydraDB:&lt;/strong&gt; Found a nasty bug where &lt;code&gt;merge_distributed_inner_join&lt;/code&gt; treats &lt;code&gt;null&lt;/code&gt; keys as equal (&lt;a href="https://github.com/hydra-db/hydradb/issues/96" rel="noopener noreferrer"&gt;Issue #96&lt;/a&gt;). That results in a massive cross-product of null rows that can blow up your memory.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Agent-Orchestrator:&lt;/strong&gt; Had some trouble with the Linux AppImage on Arch (&lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/issues/4006" rel="noopener noreferrer"&gt;Issue #4006&lt;/a&gt;). It turns out it was built with a legacy FUSE2 runtime. I opened &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/4007" rel="noopener noreferrer"&gt;PR #4007&lt;/a&gt; to move it to a modern runtime so it actually starts on clean installs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Corsair:&lt;/strong&gt; Did a quick accessibility audit and found some missing labels in the Studio ChatPage (&lt;a href="https://github.com/corsairdev/corsair/issues/746" rel="noopener noreferrer"&gt;Issue #746&lt;/a&gt;). I fixed that and also implemented a constant-time comparison for GitLab tokens to prevent timing attacks (&lt;a href="https://github.com/corsairdev/corsair/pull/757" rel="noopener noreferrer"&gt;PR #757&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This was a heavy &lt;strong&gt;Python&lt;/strong&gt; and &lt;strong&gt;TypeScript&lt;/strong&gt; week, but I also touched quite a bit of &lt;strong&gt;Lua&lt;/strong&gt; while tweaking my Neovim config. &lt;/p&gt;

&lt;p&gt;I actually simplified my &lt;a href="https://github.com/yashksaini-coder/nvim" rel="noopener noreferrer"&gt;nvim&lt;/a&gt; setup this week. I removed &lt;code&gt;toggleterm&lt;/code&gt; and all its associated complexity in favor of a leaner workflow. I also restored my &lt;code&gt;&amp;lt;leader&amp;gt;gg&lt;/code&gt; binding to trigger &lt;code&gt;snacks.lazygit&lt;/code&gt;. Sometimes the best code is the code you delete—I ended the week with a net negative line count on my config, which is always the goal.&lt;/p&gt;

&lt;p&gt;The stats tell a story of massive growth: +29,890 lines and -12,478 lines. A lot of that addition is documentation and the Obsidian backfill, but the 12k deletions represent a lot of refactoring in GitBanner and py-libp2p.&lt;/p&gt;

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

&lt;p&gt;Next week is all about the GitBanner 1.3.0 launch. I want to make sure the new TUI type system is rock solid. I’m also planning to dive deeper into the HydraDB join issues I uncovered—fixing that cross-join on null keys is going to be a fun challenge in distributed logic. &lt;/p&gt;

&lt;p&gt;Seven days of commits in a row. The streak is alive, the coffee is strong, and the code is getting cleaner. See you 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>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Gravy Theory: three chickens, one base</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sat, 15 Aug 2026 09:59:55 +0000</pubDate>
      <link>https://dev.to/yashksaini/gravy-theory-three-chickens-one-base-25h8</link>
      <guid>https://dev.to/yashksaini/gravy-theory-three-chickens-one-base-25h8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2026-07-29"&gt;Frontend Challenge - Comfort Food Edition Perfect Landing&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Gravy Theory&lt;/strong&gt; is an interactive field guide to North Indian gravy, built around a single claim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Butter chicken and chicken curry are not different recipes. They are the &lt;strong&gt;same onion–tomato masala&lt;/strong&gt;, stopped at different moments and finished in different directions. Chilli chicken never touches it at all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Recipes give you minutes. Cooks give you colour. So the page is organised around the thing a recipe can't hand you — the ability to &lt;em&gt;look&lt;/em&gt; at a pan and know where you are.&lt;/p&gt;

&lt;p&gt;It's best understood by dragging the dial rather than reading about it: &lt;strong&gt;&lt;a href="https://yashksaini-coder.github.io/comfort-food-challenge/" rel="noopener noreferrer"&gt;open the live demo&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://github.com/yashksaini-coder/comfort-food-challenge" rel="noopener noreferrer"&gt;source&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Seven sections, each answering one question:&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;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The base&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A dial that cooks the masala from raw to done. Six ingredients light up as you pass their stage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The split&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Three dishes, tabbed, each showing what it adds on top of the shared base.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The fork&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One pan divides into two along drawn curves while a wok slides in from the right and &lt;em&gt;stops short&lt;/em&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The clock&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Where the time actually goes. Butter chicken is the only one needing two burners.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Green chilli&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Heat plotted against aroma on a log scale. The Indian green chilli sits where nothing else does.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The dabba&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Seven tins, seven jobs — and what breaks when each one is missing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tonight&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Three questions. It's a constraint problem, not a taste problem.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The constraint
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;One HTML file. Zero image requests.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every pan, thali, katori, roti, chilli, spice tin and ingredient is drawn in CSS gradients and inline SVG. No build step, no framework, no bundler, no icon font, no CDN, no tracking. The only external request is Google Fonts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;index.html    ~147 KB    everything
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open it in a browser. That's the whole setup.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://yashksaini-coder.github.io/comfort-food-challenge/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Cook the base →&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Best on a real screen — the dial, the tabs and the fork scene all want a pointer or a thumb. It works from 320px up, and with JavaScript switched off.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;
        yashksaini-coder
      &lt;/a&gt; / &lt;a href="https://github.com/yashksaini-coder/comfort-food-challenge" rel="noopener noreferrer"&gt;
        comfort-food-challenge
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An interactive field guide to North Indian gravy. One HTML file, zero image requests — every pan, thali and chilli drawn in CSS and inline SVG. DEV Frontend Challenge, Comfort Food Edition.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Gravy Theory — three chickens, one base&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;An interactive field guide to North Indian gravy, arguing one thing: &lt;strong&gt;butter chicken and chicken
curry are not different recipes.&lt;/strong&gt; They are the same onion–tomato masala, stopped at different
moments and finished in different directions. Chilli chicken never touches it at all.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/frontend-2026-07-29" rel="nofollow"&gt;DEV Frontend Challenge — Comfort Food Edition&lt;/a&gt;
(&lt;em&gt;Perfect Landing&lt;/em&gt; prompt).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://yashksaini-coder.github.io/comfort-food-challenge/" rel="nofollow noopener noreferrer"&gt;→ Live demo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The constraint&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;One HTML file. &lt;strong&gt;Zero image requests.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every pan, thali, katori, roti, chilli, spice tin and ingredient on the page is drawn in CSS
gradients and inline SVG. There is no build step, no framework, no bundler, no icon font, no CDN
and no tracking. The only external request is Google Fonts.&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;index.html    ~146 KB    everything
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Open it in a browser. That's the whole setup.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What's in it&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The base&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A dial that cooks the masala from raw to&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yashksaini-coder/comfort-food-challenge" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Plain links, in case the embeds above don't render for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo&lt;/strong&gt; — &lt;a href="https://yashksaini-coder.github.io/comfort-food-challenge/" rel="noopener noreferrer"&gt;https://yashksaini-coder.github.io/comfort-food-challenge/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt; — &lt;a href="https://github.com/yashksaini-coder/comfort-food-challenge" rel="noopener noreferrer"&gt;https://github.com/yashksaini-coder/comfort-food-challenge&lt;/a&gt; (MIT)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The thing that took three attempts
&lt;/h3&gt;

&lt;p&gt;My first pass at the butter chicken looked like a &lt;strong&gt;donut chart&lt;/strong&gt;. Orange disc, gradient, a white wedge for the cream. Technically food-coloured; emotionally a pie chart.&lt;/p&gt;

&lt;p&gt;What fixed it wasn't more detail. It was realising that real food has &lt;strong&gt;three separable optical layers&lt;/strong&gt;, and I had collapsed two of them into one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;an opaque &lt;strong&gt;pigmented base&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;subsurface glow&lt;/strong&gt; — light entering the material, scattering, coming back warmer&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;specular glint&lt;/strong&gt; — a hard reflection off the fat on top&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The glow is &lt;em&gt;big, blurry and coloured.&lt;/em&gt; The glint is &lt;em&gt;small, sharp and near-white.&lt;/em&gt; Merge them into one soft highlight and you get plastic every single time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* subsurface — wide, offset toward the light, blurred, screened */&lt;/span&gt;
&lt;span class="nc"&gt;.kadai__pool&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="m"&gt;58%&lt;/span&gt; &lt;span class="m"&gt;44%&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;66%&lt;/span&gt; &lt;span class="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="m"&gt;#f0a03c7&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="m"&gt;#d6431f2&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="m"&gt;54%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;74%&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;radial-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;34%&lt;/span&gt; &lt;span class="m"&gt;26%&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="m"&gt;33%&lt;/span&gt; &lt;span class="m"&gt;68%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#f0a03c54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="m"&gt;72%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;9px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;mix-blend-mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* specular — tiny, hard, warm-white. This one line is the difference. */&lt;/span&gt;
&lt;span class="nc"&gt;.kadai__lit&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#fffdf4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ffe9be&lt;/span&gt; &lt;span class="m"&gt;62%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ffd79200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-22deg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.1px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;mix-blend-mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;screen&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;The other half of it: &lt;strong&gt;commit to one light source and never break it.&lt;/strong&gt; Everything on the page is lit from the upper right (&lt;code&gt;--lx: 78%; --ly: 18%&lt;/code&gt;). Mismatched light direction is the fastest way to make something read as drawn rather than lit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four filters, all of them static
&lt;/h3&gt;

&lt;p&gt;Inline SVG filters do the work CSS can't: a &lt;code&gt;feTurbulence&lt;/code&gt; + &lt;code&gt;feDisplacementMap&lt;/code&gt; pair gives gravy a ragged edge instead of a perfect circle, a gooey &lt;code&gt;feColorMatrix&lt;/code&gt; makes cream dollops &lt;em&gt;fuse&lt;/em&gt; with surface tension rather than stack like stickers, and thresholded noise paints tandoor char onto the chicken.&lt;/p&gt;

&lt;p&gt;The rule I set myself: &lt;strong&gt;no filter animates its own parameters.&lt;/strong&gt; Animating &lt;code&gt;baseFrequency&lt;/code&gt; or &lt;code&gt;seed&lt;/code&gt; regenerates Perlin noise on the CPU every frame and drops a phone to single-digit FPS. The steam filter is completely static — only the source element translates.&lt;/p&gt;

&lt;p&gt;That char filter also taught me something I didn't know. My first version rendered &lt;em&gt;nothing&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- broken: thresholds at 6/7 ≈ 0.857 --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;feFuncA&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"discrete"&lt;/span&gt; &lt;span class="na"&gt;tableValues=&lt;/span&gt;&lt;span class="s"&gt;"0 0 0 0 0 0 1"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fractalNoise&lt;/code&gt; alpha clusters tightly around 0.5 and essentially never reaches 0.857, so the soot layer was empty. A linear stretch puts the threshold where the data actually lives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;feFuncA&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"linear"&lt;/span&gt; &lt;span class="na"&gt;slope=&lt;/span&gt;&lt;span class="s"&gt;"11"&lt;/span&gt; &lt;span class="na"&gt;intercept=&lt;/span&gt;&lt;span class="s"&gt;"-6.6"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Two CSS traps I fell into twice each
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Percentage padding on an absolutely positioned element resolves against the positioned ancestor, not the element.&lt;/strong&gt; &lt;code&gt;padding: 7.5%&lt;/code&gt; on a pan inside a 1000px stage became 75px per side on a 190px pan — the gravy collapsed to a dot. Same bug later stretched my spice tins into ovals. The fix both times is &lt;code&gt;inset: 7.5%&lt;/code&gt; on an absolutely positioned child, which &lt;em&gt;does&lt;/em&gt; resolve against the parent's own box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inline &lt;code&gt;style&lt;/code&gt; beats every media query.&lt;/strong&gt; My entire mobile layout for the fork scene was silently dead because the positions lived in &lt;code&gt;style="--x:27%"&lt;/code&gt;. Custom properties don't escape specificity — anything responsive has to live in a class.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making the colour actually interpolate
&lt;/h3&gt;

&lt;p&gt;The dial interpolates the gravy in OKLCH so the ramp doesn't go grey through the middle of a warm palette. But as a plain custom property, &lt;code&gt;--f&lt;/code&gt; is a &lt;em&gt;string&lt;/em&gt; — so the colour stepped instead of transitioning. Registering it fixes that in three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@property&lt;/span&gt; &lt;span class="n"&gt;--f&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"&amp;lt;percentage&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inherits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;initial-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@property&lt;/span&gt; &lt;span class="n"&gt;--gravy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;syntax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"&amp;lt;color&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inherits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;initial-value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#b94a17&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The part I'm most proud of
&lt;/h3&gt;

&lt;p&gt;Not the food — the &lt;strong&gt;accessibility&lt;/strong&gt;, because it forced better decisions everywhere.&lt;/p&gt;

&lt;p&gt;The chilli scatter and the masala dabba were originally sets of &lt;code&gt;aria-pressed&lt;/code&gt; toggle buttons. But &lt;code&gt;aria-pressed&lt;/code&gt; models &lt;em&gt;N independent toggles&lt;/em&gt;, and a set where exactly one member is active is the APG &lt;strong&gt;radio group&lt;/strong&gt; pattern. Converting them collapsed 13 tab stops into 2, added arrow-key navigation, and made screen readers announce "3 of 6" instead of "pressed."&lt;/p&gt;

&lt;p&gt;A few more that I'd have shipped broken without checking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A live region inside a &lt;code&gt;display: none&lt;/code&gt; subtree announces nothing.&lt;/strong&gt; My quiz verdict wrote all its text while hidden, then revealed it — silent both ways. The region now lives outside the collapsible body.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hiding a button's label with &lt;code&gt;display: none&lt;/code&gt; destroys its accessible name.&lt;/strong&gt; Below 30rem my motion toggle fell back to a stale &lt;code&gt;title&lt;/code&gt; and announced the &lt;em&gt;opposite&lt;/em&gt; of what it would do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;role="img"&lt;/code&gt; makes every descendant presentational.&lt;/strong&gt; My fork scene had four visible captions that screen readers never saw.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt; blocks usually forget delays.&lt;/strong&gt; Zeroing &lt;code&gt;animation-duration&lt;/code&gt; but not &lt;code&gt;animation-delay&lt;/code&gt; leaves the hero at &lt;code&gt;opacity: 0&lt;/code&gt; for half a second.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a &lt;strong&gt;Pause motion&lt;/strong&gt; control in the header that stops every looping animation (WCAG 2.2.2 — a &lt;code&gt;:hover&lt;/code&gt; pause is reachable by neither keyboard nor touch), contrast is verified AA across every text/background pair in &lt;em&gt;both&lt;/em&gt; palettes, and the whole page is readable with &lt;strong&gt;JavaScript switched off&lt;/strong&gt; — tabs become stacked panels, charts fall back to static lists.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bug that embarrassed me most
&lt;/h3&gt;

&lt;p&gt;Late on, a review pass caught that two of my three timing bars &lt;strong&gt;ran past their own labels&lt;/strong&gt;. Chicken curry's bar reached 45 minutes against a "42 min" caption; butter chicken hit 26.1 against "24". On a page whose entire middle section is a timing comparison, I had drawn the timings wrong. There's now a script that asserts every bar ends where its label says it does.&lt;/p&gt;

&lt;p&gt;Lesson filed: the thing you're &lt;em&gt;arguing&lt;/em&gt; is the thing to write a check for.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next
&lt;/h3&gt;

&lt;p&gt;A cook-along mode. The rotating stamp advertises eighteen minutes and then does nothing with it — start/pause/reset driving the dial in real time against the stage timings would make the page usable at the stove, and the pause control it needs is one I've already built.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://yashksaini-coder.github.io/comfort-food-challenge/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Watch the oil separate →&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built with vanilla HTML, CSS and JavaScript. No frameworks were harmed. It's one file and it's MIT — &lt;a href="https://github.com/yashksaini-coder/comfort-food-challenge" rel="noopener noreferrer"&gt;take it apart&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Dev log #16 Typographic Hierarchy and the Great Obsidian Purge</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Tue, 11 Aug 2026 06:18:06 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-16-typographic-hierarchy-and-the-great-obsidian-purge-3h5e</link>
      <guid>https://dev.to/yashksaini/dev-log-16-typographic-hierarchy-and-the-great-obsidian-purge-3h5e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Spent the week redesigning my portfolio’s blog layout and nuking thousands of stale notes in my Obsidian vault. Between the UI polish and some deep dives into libp2p DHT de-flaking, I pushed 36 commits and managed to delete almost 16,000 lines of clutter.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;I’ve always believed that your digital space needs a good pruning every now and then to stay healthy. This week was the embodiment of that philosophy. I pushed 36 commits across four primary projects, resulting in over 23,000 additions and nearly 16,000 deletions. Most of that churn came from a massive redesign of my portfolio's blog and a long-overdue "fresh start" for my Obsidian vault. On the open-source side, I spent some quality time in the weeds of &lt;code&gt;py-libp2p&lt;/code&gt;, chasing down flaky DHT tests and proposing better subnet diversity limits.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Portfolio Redesign: The Typography Pivot
&lt;/h3&gt;

&lt;p&gt;My main focus this week was my portfolio. I’ve been feeling like the blog layout was getting a bit cluttered, so I opened and merged PR #15, which was all about "typographic hierarchy instead of decoration." I’m moving away from unnecessary borders and boxes and letting the type do the heavy lifting.&lt;/p&gt;

&lt;p&gt;I spent a lot of time in &lt;code&gt;components/blog&lt;/code&gt; and &lt;code&gt;app/blog&lt;/code&gt; refining the layout. I implemented borderless filter pills and full-width rows to give the content more room to breathe. One of the bigger technical shifts was moving the blog list to be fully server-rendered. It feels snappier, and it allowed me to implement more "honest" dates and better hover states on the rows. I also added a real focus ring for accessibility (because we’ve all been frustrated by keyboard navigation that feels like a guessing game). By the time I was done, I’d touched over 200 files in that repo alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Obsidian Purge
&lt;/h3&gt;

&lt;p&gt;I also took a metaphorical chainsaw to my &lt;code&gt;obsidian-vault&lt;/code&gt;. I nuked nearly 10,000 lines of stale content. I removed entire directories for "Projects," "Rust," and "Backend" notes that were just gathering digital dust. &lt;/p&gt;

&lt;p&gt;It’s easy to let a vault become a graveyard of half-finished thoughts. I renamed my local audit to "Persona-Audit-August-2026" and cleared out a bunch of orphaned assets and stale index notes. I even repointed some old PDF links, like the Rust book, and dropped "Practical C" from my active reading list. It feels good to have a clean slate for the second half of the year.&lt;/p&gt;

&lt;h3&gt;
  
  
  Neovim Tinkering (As Always)
&lt;/h3&gt;

&lt;p&gt;My &lt;code&gt;nvim&lt;/code&gt; config saw some action too. I finally made the jump from &lt;code&gt;bufferline&lt;/code&gt; to &lt;code&gt;barbar&lt;/code&gt; for my tabline. I also swapped out &lt;code&gt;aerial&lt;/code&gt; for &lt;code&gt;outline.nvim&lt;/code&gt; to handle my symbols and code structure. &lt;/p&gt;

&lt;p&gt;Beyond the editor itself, I did some work on my config’s documentation site. I redesigned the card layout to use a masonry/bento style CSS-column grid. I reordered the cards "tallest-first" so the columns balance out better visually. I also widened the max width to &lt;code&gt;5xl&lt;/code&gt; and added some hairline section dividers. It’s a much tighter rhythm now.&lt;/p&gt;

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

&lt;p&gt;I had two main PRs on the go this week:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio PR #15 (Merged):&lt;/strong&gt; As I mentioned, this was the big typography overhaul. It was a massive diff (+10,034 / -1,811) because I was essentially re-skinning the entire blog experience. It feels much more professional now—less like a template and more like a custom-built home for my thoughts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;py-libp2p PR #1424 (Open):&lt;/strong&gt; This one was a bit more technical. I’ve been hitting some flakiness in the &lt;code&gt;kad_dht&lt;/code&gt; tests, specifically where the &lt;code&gt;dht_pair&lt;/code&gt; fixture races on routing-table population. Sometimes Node A just doesn't know about Node B in time for the test to run. I introduced a bounded poll to give the nodes time to find each other without making the tests feel sluggish.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Discussions
&lt;/h2&gt;

&lt;p&gt;Most of my community-facing work happened in the &lt;code&gt;py-libp2p&lt;/code&gt; repo. I opened three issues that I’m pretty passionate about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Issue #1423:&lt;/strong&gt; This was the bug report for the flaky &lt;code&gt;dht_pair&lt;/code&gt; fixture I mentioned above. It’s one of those "it works on my machine but fails in CI" problems that makes everyone's life harder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue #1422 &amp;amp; #1421:&lt;/strong&gt; These are about hardening the Kademlia DHT. I want to make subnet-diversity limits runtime-configurable and add a table-wide IP-group cap (&lt;code&gt;maxForTable&lt;/code&gt;). When you're dealing with p2p networking, you really don't want your routing table to be dominated by a single ISP or subnet—it’s a massive reliability and security risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also did a tiny bit of housekeeping on &lt;code&gt;Pair-Pal&lt;/code&gt; by creating a &lt;code&gt;FUNDING.yml&lt;/code&gt; file. It’s a small step, but it’s part of taking my open-source projects more seriously.&lt;/p&gt;

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

&lt;p&gt;This was a heavily polyglot week, though the numbers are skewed by the sheer amount of content in the portfolio and vault:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript:&lt;/strong&gt; The backbone of the portfolio redesign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lua:&lt;/strong&gt; Keeping the Neovim config evolving.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python:&lt;/strong&gt; The language of choice for my libp2p contributions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Markdown/MDX:&lt;/strong&gt; This was the "cleanup" language this week.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The add/delete ratio was interesting. While I added over 23k lines, a huge chunk of that was the new portfolio structure. Deleting 15k lines—mostly from the Obsidian vault and the old portfolio layout—felt just as productive. Refactoring isn't always about the new stuff; it's about making sure the old stuff isn't in the way.&lt;/p&gt;

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

&lt;p&gt;Next week, I’m hoping to get that libp2p PR merged once the maintainers have a look at my polling logic. I also want to dive deeper into those subnet diversity configurations I proposed. On the personal side, now that the portfolio layout is stable, I need to actually sit down and write some of the long-form content I’ve been planning. &lt;/p&gt;

&lt;p&gt;The editor is clean, the vault is purged, and the site is fast. No more excuses—time to build.&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>design</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
      <category>webdev</category>
    </item>
    <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>
  </channel>
</rss>
