<?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: Sakhavat</title>
    <description>The latest articles on DEV Community by Sakhavat (@ssakhavat).</description>
    <link>https://dev.to/ssakhavat</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%2F4133421%2Ffc70ca19-a251-4543-a567-9cde070d2d50.png</url>
      <title>DEV Community: Sakhavat</title>
      <link>https://dev.to/ssakhavat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ssakhavat"/>
    <language>en</language>
    <item>
      <title>How I built errno.help - and what broke along the way</title>
      <dc:creator>Sakhavat</dc:creator>
      <pubDate>Sat, 19 Sep 2026 21:26:54 +0000</pubDate>
      <link>https://dev.to/ssakhavat/how-i-built-errnohelp-and-what-broke-along-the-way-ofl</link>
      <guid>https://dev.to/ssakhavat/how-i-built-errnohelp-and-what-broke-along-the-way-ofl</guid>
      <description>&lt;p&gt;I kept doing the same thing every few days: googling a Windows error code, pasting a JWT into some random site to decode it, or working out a CIDR range by hand because I couldn't remember the shortcut. Dozens of tabs, dozens of sites, none of them quite trustworthy with what I was pasting in. So I built one place for the tools I actually reach for: errno.help - CIDR/IPv4 calculator, JWT decoder, hash generator, UUID/password generator, YAML⇄JSON, cron parser, DNS/WHOIS/ASN/GeoIP lookups, a Windows error/event ID reference, and a few command builders (Robocopy, chmod, kubectl).&lt;/p&gt;

&lt;p&gt;It's open source &lt;strong&gt;(MIT)&lt;/strong&gt;: &lt;a href="//github.com/ssakhavat/errno-help"&gt;github.com/ssakhavat/errno-help&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few decisions and mistakes along the way felt worth writing down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client-side first, server only when it can't be avoided&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most of these tools don't need a server at all. CIDR math, JWT decoding, hashing, UUID generation - all of it runs in the browser with native APIs (&lt;code&gt;crypto.subtle.digest&lt;/code&gt;, &lt;code&gt;crypto.getRandomValues&lt;/code&gt;, &lt;code&gt;crypto.randomUUID&lt;/code&gt;). Nothing you paste into the JWT decoder ever leaves your machine, which matters more than it sounds like it should when the tool's whole job is decoding tokens.&lt;/p&gt;

&lt;p&gt;A handful of tools genuinely need a server - DNS, WHOIS, ASN, GeoIP, and a port checker. Those go through Next.js API routes that talk to an upstream service, so no API key or raw socket access ever touches the client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The GeoIP provider swap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I originally wired GeoIP lookups to MaxMind's GeoLite2 web service - free, well-documented, industry-standard data. I set up Basic Auth with an account ID and license key, deployed it, and got: "&lt;em&gt;The GeoIP service rejected our credentials.&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;Locally, the exact same credentials worked fine, curl included. On Vercel, rejected, consistently, across multiple redeploys and freshly re-pasted environment variables. I never fully root-caused it - possibly an account-level web-service permission that hadn't propagated, possibly something in how the credentials moved through Vercel's env var UI. Rather than keep debugging a black box, I switched to &lt;a href="//www.ipinfo.io"&gt;ipinfo.io&lt;/a&gt;, which uses a single URL-embedded token instead of Basic Auth. Same result, less to get wrong, and - as a bonus - its free tier's terms are commercial-use friendly, whereas the always-free &lt;code&gt;ip-api.com&lt;/code&gt; option I'd also considered is explicitly non-commercial only. Worth reading the fine print before you build on top of a free tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rate limiter I thought I'd already built&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every network-facing route had rate limiting from early on - capped per IP, tighter on the port checker since it's the closest thing to a port scanner I'm willing to expose. Then I asked for a general security pass, and it turned up something I'd missed entirely: &lt;strong&gt;rate limiting keyed on&lt;/strong&gt; &lt;code&gt;X-Forwarded-For&lt;/code&gt; &lt;strong&gt;can be trivially bypassed by just sending a different fake value on every request&lt;/strong&gt;. Twelve spoofed headers, twelve requests that should've been blocked at ten.&lt;/p&gt;

&lt;p&gt;The fix ended up being environment-aware: on Vercel, the platform's own edge network sets &lt;code&gt;x-real-ip&lt;/code&gt; in a way the client can't override, so that's authoritative there. Everywhere else (local dev, unknown environments), each client gets a random ID in an httpOnly cookie and its own isolated bucket, so spoofing gains nothing and legitimate users don't share a quota with strangers. It's the kind of bug that's invisible until someone actually tries to break it - and a good reminder to occasionally treat your own tool as adversarially as a stranger would.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One screen, no scroll, no logo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The design brief I gave myself was strict: single viewport, no logo, plain typography, nothing that looks templated. That constraint turned out to be harder to satisfy structurally than visually. A shared &lt;code&gt;overflow: hidden; height: 100vh&lt;/code&gt; on &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; - meant only for the landing page - quietly clipped every other page in the app, including tool results that overflowed a single screen. A pre-existing media query meant to relax that on short viewports never fired, because a bare-element CSS selector can't out-specify a Tailwind utility class. The fix was moving the viewport lock off  and onto the landing page's own root element, so it's opt-in per page instead of a global default nobody remembers is there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The tool list will probably keep growing - Base64, regex testing, a few more command builders are on the list. If you build or maintain IT/dev tools yourself, I'd be curious what you reach for that isn't here yet.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>nextjs</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
