<?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: Sathvic Kollu</title>
    <description>The latest articles on DEV Community by Sathvic Kollu (@sathvic_kollu).</description>
    <link>https://dev.to/sathvic_kollu</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%2F3987375%2F4883e905-8dbb-4d72-a279-dec200d13732.jpg</url>
      <title>DEV Community: Sathvic Kollu</title>
      <link>https://dev.to/sathvic_kollu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sathvic_kollu"/>
    <language>en</language>
    <item>
      <title>How I Audit JavaScript Regexes for Catastrophic Backtracking</title>
      <dc:creator>Sathvic Kollu</dc:creator>
      <pubDate>Sun, 30 Aug 2026 05:48:11 +0000</pubDate>
      <link>https://dev.to/sathvic_kollu/how-i-audit-javascript-regexes-for-catastrophic-backtracking-1aj9</link>
      <guid>https://dev.to/sathvic_kollu/how-i-audit-javascript-regexes-for-catastrophic-backtracking-1aj9</guid>
      <description>&lt;p&gt;I maintain &lt;a href="https://codeswap.net/" rel="noopener noreferrer"&gt;CodeSwap&lt;/a&gt;, a developer-tools site with browser-based utilities and technical guides. While reviewing its regular-expression tools, I wanted a repeatable answer to a deceptively simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How can I tell whether a regex is merely slow or capable of pinning a CPU with a tiny hostile input?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer was not another list of patterns labeled “safe” or “unsafe.” It was a small audit process: identify ambiguous repetition, construct a failing input, measure growth at several lengths, rewrite the pattern, and measure the same input again.&lt;/p&gt;

&lt;p&gt;This article documents that process. The exact timings come from a Node.js 22 test run used for the original CodeSwap guide. Hardware and engine versions change absolute numbers, but the growth curve is the useful signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode: many valid paths, followed by one failure
&lt;/h2&gt;

&lt;p&gt;Most JavaScript regular expressions use backtracking. The engine makes a greedy choice, and if something later fails, it revisits earlier choices.&lt;/p&gt;

&lt;p&gt;That is normally harmless. The dangerous case appears when the same characters can be divided or matched in many different ways.&lt;/p&gt;

&lt;p&gt;Consider this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner &lt;code&gt;a+&lt;/code&gt; can consume one or many &lt;code&gt;a&lt;/code&gt; characters. The outer &lt;code&gt;+&lt;/code&gt; can repeat that group one or many times. For an all-&lt;code&gt;a&lt;/code&gt; string, the first path succeeds quickly. Add one final character that cannot match, however, and the engine must explore a rapidly growing number of partitions before returning &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;OWASP uses this same nested-quantifier shape when explaining Regular Expression Denial of Service, or ReDoS. Its warning signs include repetition inside a repeated group and overlapping alternatives inside repetition.&lt;/p&gt;

&lt;h2&gt;
  
  
  A measurement that exposes the curve
&lt;/h2&gt;

&lt;p&gt;The useful test input is not a random long string. It is a string that lets the risky portion match repeatedly and then fails at the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vulnerable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;a+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vulnerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;milliseconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;started&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;length&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 recorded run produced this curve:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repeated &lt;code&gt;a&lt;/code&gt; characters&lt;/th&gt;
&lt;th&gt;Time before rejecting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;6 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;25 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;98 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;402 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;1,494 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;6,060 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every two added characters multiplied the time by roughly four. That is the evidence I look for: not one slow result, but growth that accelerates as the input grows.&lt;/p&gt;

&lt;p&gt;Do not run an unknown pattern against unbounded input on a production request thread. Use short, controlled samples in an isolated test process or worker and stop before the duration becomes disruptive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two shapes I check first
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A quantified group containing another quantifier
&lt;/h3&gt;

&lt;p&gt;Examples include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(a+)+
(\d+)*
([a-z]*)+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesting alone is not a proof of exploitation, but it is a strong review signal. I ask whether the inner and outer repetitions create multiple ways to consume the same text.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Overlapping alternatives under repetition
&lt;/h3&gt;

&lt;p&gt;This pattern also creates ambiguity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At many positions, either branch can consume the next characters. Real patterns hide the overlap inside character classes, such as &lt;code&gt;\w|\d&lt;/code&gt;, where every digit can match both alternatives.&lt;/p&gt;

&lt;p&gt;The question is always the same: can more than one path consume the same input, and can a later failure force the engine to revisit those paths?&lt;/p&gt;

&lt;h2&gt;
  
  
  Rewrite first, then compare the same hostile input
&lt;/h2&gt;

&lt;p&gt;For the minimal example, the outer group adds no useful behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vulnerable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;a+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repaired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^a+$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both accept one or more &lt;code&gt;a&lt;/code&gt; characters and reject everything else. The repaired version removes the ambiguity.&lt;/p&gt;

&lt;p&gt;In the same recorded test:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;30-character hostile input&lt;/th&gt;
&lt;th&gt;100,000-character hostile input&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;^(a+)+$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6,060 ms&lt;/td&gt;
&lt;td&gt;stopped rather than attempted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;^a+$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.02 ms&lt;/td&gt;
&lt;td&gt;0.09 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I do not treat those exact numbers as a universal benchmark. I treat the change in growth behavior as the acceptance criterion: the repaired pattern remains approximately linear on the same family of inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  My practical review checklist
&lt;/h2&gt;

&lt;p&gt;Before a JavaScript regex processes user-controlled input, I now check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Nested repetition:&lt;/strong&gt; Does a &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, or open-ended range wrap a group that contains another quantifier?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overlapping alternatives:&lt;/strong&gt; Can two branches under repetition match the same character or substring?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A forced failure:&lt;/strong&gt; Is there an anchor or required literal after the ambiguous part that can trigger exhaustive backtracking?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input bounds:&lt;/strong&gt; Is there a maximum accepted length before the regex runs?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measured growth:&lt;/strong&gt; Does duration remain stable or roughly linear as a hostile input is lengthened?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime isolation:&lt;/strong&gt; If patterns or inputs are untrusted, can evaluation happen in a worker, subprocess, or engine with an enforceable timeout?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Length limits matter even after a rewrite. OWASP's input-validation guidance recommends explicit minimum and maximum lengths, and those bounds reduce the damage from mistakes that survive review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static checks are useful, but measurement closes the loop
&lt;/h2&gt;

&lt;p&gt;I built a &lt;a href="https://codeswap.net/regex/redos-checker/" rel="noopener noreferrer"&gt;browser-based ReDoS checker&lt;/a&gt; around this workflow. It looks for ambiguous structures and can run a bounded timing probe locally in the browser. It does not upload the pattern or test input to a server.&lt;/p&gt;

&lt;p&gt;The tool is a review aid, not a security certification. Static detection can produce false positives, while one timing sample can miss an engine-specific worst case. I use both signals, inspect the pattern manually, and compare the repaired version against the same adversarial input.&lt;/p&gt;

&lt;p&gt;The longer &lt;a href="https://codeswap.net/guides/regex-catastrophic-backtracking-redos-explained/" rel="noopener noreferrer"&gt;catastrophic backtracking and ReDoS guide&lt;/a&gt; contains the full measurement table and more examples. This DEV version declares that original URL as canonical.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in my maintenance process
&lt;/h2&gt;

&lt;p&gt;The main lesson was to treat regex complexity like ordinary algorithmic complexity. A pattern can be functionally correct on every normal test and still be unsafe under a carefully chosen non-match.&lt;/p&gt;

&lt;p&gt;So “the regex returns the right answer” is no longer enough for patterns on untrusted input. I want three pieces of evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a structural review for ambiguity;&lt;/li&gt;
&lt;li&gt;a bounded test showing how runtime changes with input length; and&lt;/li&gt;
&lt;li&gt;a repaired pattern measured against the same hostile case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a small amount of work compared with diagnosing a production thread that appears frozen while doing exactly what its regex engine was asked to do.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I maintain CodeSwap and the links above point to that project. This article was prepared with AI assistance from the site's documented implementation and test records. Its technical claims and measurements were checked against the live guide, live tool and dated maintenance evidence before publication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt; &lt;a href="https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS" rel="noopener noreferrer"&gt;OWASP ReDoS explanation&lt;/a&gt;, &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP input-validation guidance&lt;/a&gt;, and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions" rel="noopener noreferrer"&gt;MDN's JavaScript regular-expression reference&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>performance</category>
      <category>security</category>
    </item>
    <item>
      <title>Simple probe title</title>
      <dc:creator>Sathvic Kollu</dc:creator>
      <pubDate>Fri, 17 Jul 2026 03:54:11 +0000</pubDate>
      <link>https://dev.to/sathvic_kollu/simple-probe-title-h0g</link>
      <guid>https://dev.to/sathvic_kollu/simple-probe-title-h0g</guid>
      <description>&lt;p&gt;Just a body.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>UUID-Tools API - Live Now</title>
      <dc:creator>Sathvic Kollu</dc:creator>
      <pubDate>Fri, 17 Jul 2026 03:53:25 +0000</pubDate>
      <link>https://dev.to/sathvic_kollu/uuid-tools-api-live-now-4hd8</link>
      <guid>https://dev.to/sathvic_kollu/uuid-tools-api-live-now-4hd8</guid>
      <description>&lt;h1&gt;
  
  
  UUID Tools
&lt;/h1&gt;

&lt;p&gt;Free UUID API.&lt;/p&gt;

&lt;p&gt;Live: &lt;a href="https://uuid-tools.techtenstein.com" rel="noopener noreferrer"&gt;https://uuid-tools.techtenstein.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>probe-published-9928</title>
      <dc:creator>Sathvic Kollu</dc:creator>
      <pubDate>Fri, 17 Jul 2026 03:47:08 +0000</pubDate>
      <link>https://dev.to/sathvic_kollu/probe-published-9928-5bkf</link>
      <guid>https://dev.to/sathvic_kollu/probe-published-9928-5bkf</guid>
      <description>&lt;h1&gt;
  
  
  hi
&lt;/h1&gt;

&lt;p&gt;body&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing Password Strength API — score, crack-time, and generation</title>
      <dc:creator>Sathvic Kollu</dc:creator>
      <pubDate>Thu, 16 Jul 2026 06:54:11 +0000</pubDate>
      <link>https://dev.to/sathvic_kollu/introducing-password-strength-api-score-crack-time-and-generation-7nf</link>
      <guid>https://dev.to/sathvic_kollu/introducing-password-strength-api-score-crack-time-and-generation-7nf</guid>
      <description>&lt;p&gt;Just shipped &lt;strong&gt;Password Strength API&lt;/strong&gt; — score passwords, estimate crack time, and generate strong passwords or passphrases. Passwords are processed at the edge and never logged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endpoints
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /check?password=hunter2&lt;/code&gt; → score 0-4, entropy, crack time, feedback&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /generate?length=24&amp;amp;symbols=true&amp;amp;count=3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /passphrase?words=5&lt;/code&gt; (diceware-style)&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://password-strength.techtenstein.com/check?password=CorrectHorseBatteryStaple"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Live at &lt;a href="https://password-strength.techtenstein.com" rel="noopener noreferrer"&gt;https://password-strength.techtenstein.com&lt;/a&gt; — OpenAPI 3.1 spec at &lt;code&gt;/openapi.json&lt;/code&gt;. MIT.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Introducing Timezone Convert API — DST-aware IANA conversion at the edge</title>
      <dc:creator>Sathvic Kollu</dc:creator>
      <pubDate>Thu, 16 Jul 2026 06:53:29 +0000</pubDate>
      <link>https://dev.to/sathvic_kollu/introducing-timezone-convert-api-dst-aware-iana-conversion-at-the-edge-35hc</link>
      <guid>https://dev.to/sathvic_kollu/introducing-timezone-convert-api-dst-aware-iana-conversion-at-the-edge-35hc</guid>
      <description>&lt;p&gt;Just shipped &lt;strong&gt;Timezone Convert API&lt;/strong&gt; — DST-aware IANA timezone conversion. Free, no key, CORS-enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endpoints
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /convert?time=2026-07-16T09:00&amp;amp;from=Asia/Kolkata&amp;amp;to=America/New_York&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /now?zone=Europe/London&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /offset?zone=Pacific/Auckland&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /diff?a=Asia/Tokyo&amp;amp;b=Asia/Kolkata&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /zones&lt;/code&gt; (400+ IANA zones)&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://timezone-convert.techtenstein.com/now?zone=Europe/London"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Live at &lt;a href="https://timezone-convert.techtenstein.com" rel="noopener noreferrer"&gt;https://timezone-convert.techtenstein.com&lt;/a&gt; — OpenAPI 3.1 spec at &lt;code&gt;/openapi.json&lt;/code&gt;. MIT.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Introducing Color Palette API — zero-key palettes and WCAG contrast at the edge</title>
      <dc:creator>Sathvic Kollu</dc:creator>
      <pubDate>Thu, 16 Jul 2026 06:53:28 +0000</pubDate>
      <link>https://dev.to/sathvic_kollu/introducing-color-palette-api-zero-key-palettes-and-wcag-contrast-at-the-edge-547p</link>
      <guid>https://dev.to/sathvic_kollu/introducing-color-palette-api-zero-key-palettes-and-wcag-contrast-at-the-edge-547p</guid>
      <description>&lt;p&gt;Just shipped &lt;strong&gt;Color Palette API&lt;/strong&gt; — a free, no-key, CORS-enabled API for generating color palettes and checking WCAG contrast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endpoints
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /palette?color=3498db&amp;amp;scheme=triadic&amp;amp;count=5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /random?scheme=analogous&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /convert?color=%233498db&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /contrast?fg=000&amp;amp;bg=fff&lt;/code&gt; (WCAG AA/AAA pass/fail)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Schemes supported
&lt;/h2&gt;

&lt;p&gt;complementary, triadic, analogous, tetradic, split-complementary, monochromatic, shades, tints&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://color-palette.techtenstein.com/palette?color=3498db&amp;amp;scheme=triadic"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Live at &lt;a href="https://color-palette.techtenstein.com" rel="noopener noreferrer"&gt;https://color-palette.techtenstein.com&lt;/a&gt; — OpenAPI 3.1 spec at &lt;code&gt;/openapi.json&lt;/code&gt;. MIT.&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Introducing Slug API — Unicode URL slugs in one call, on the Cloudflare edge</title>
      <dc:creator>Sathvic Kollu</dc:creator>
      <pubDate>Tue, 14 Jul 2026 04:32:17 +0000</pubDate>
      <link>https://dev.to/sathvic_kollu/introducing-slug-api-unicode-url-slugs-in-one-call-on-the-cloudflare-edge-pgh</link>
      <guid>https://dev.to/sathvic_kollu/introducing-slug-api-unicode-url-slugs-in-one-call-on-the-cloudflare-edge-pgh</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Every CMS, blog engine, and e-commerce site needs URL slugs. Every one of them re-implements slugify. And most implementations break the moment someone types &lt;strong&gt;München&lt;/strong&gt; or &lt;strong&gt;café&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API
&lt;/h2&gt;

&lt;p&gt;One endpoint. Handles Unicode properly. Sub-100ms globally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'https://slug-api.techtenstein.workers.dev/?text=Hello%20World%202026'&lt;/span&gt;
&lt;span class="c"&gt;# {"slug":"hello-world-2026","length":16,"processing_ms":0}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Unicode is the differentiator
&lt;/h2&gt;

&lt;p&gt;Most slugify libraries strip accented characters. Ours transliterates them properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'https://slug-api.techtenstein.workers.dev/?text=M%C3%BCnchen%20Stra%C3%9Fe%20caf%C3%A9%20se%C3%B1or'&lt;/span&gt;
&lt;span class="c"&gt;# {"slug":"muenchen-strasse-cafe-senor"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice: &lt;code&gt;München&lt;/code&gt; → &lt;code&gt;muenchen&lt;/code&gt; (not &lt;code&gt;mnchen&lt;/code&gt;), &lt;code&gt;Straße&lt;/code&gt; → &lt;code&gt;strasse&lt;/code&gt; (not &lt;code&gt;strae&lt;/code&gt;), &lt;code&gt;café&lt;/code&gt; → &lt;code&gt;cafe&lt;/code&gt;, &lt;code&gt;señor&lt;/code&gt; → &lt;code&gt;senor&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configurable
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'https://slug-api.techtenstein.workers.dev/?text=The+quick+brown+fox+jumps&amp;amp;max=20&amp;amp;sep=_'&lt;/span&gt;
&lt;span class="c"&gt;# {"slug":"the_quick_brown_fox"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max&lt;/code&gt; — length cap, cleanly truncated at word boundary&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sep&lt;/code&gt; — separator character&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lower&lt;/code&gt; — case control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  POST support for larger inputs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://slug-api.techtenstein.workers.dev/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"text":"Blog post with emoji ✨ and \"quotes\"", "max":40}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploy target:&lt;/strong&gt; Cloudflare Workers (single-file JS, module syntax)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total code:&lt;/strong&gt; ~120 lines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold start:&lt;/strong&gt; none — Workers keep JS warm at every edge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency:&lt;/strong&gt; 60-120ms globally (p50 68ms in test)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; free 100K req/day, then $5/mo for 10M&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Free vs. paid
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free:&lt;/strong&gt; 1000 req/day per IP, no key needed. Great for prototypes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paid ($2/mo unlimited):&lt;/strong&gt; &lt;a href="https://techtenstein.gumroad.com" rel="noopener noreferrer"&gt;https://techtenstein.gumroad.com&lt;/a&gt; — API key for production apps.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;👉 &lt;a href="https://slug-api.techtenstein.workers.dev" rel="noopener noreferrer"&gt;slug-api.techtenstein.workers.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docs live at that URL. Zero signup for the free tier.&lt;/p&gt;

</description>
      <category>api</category>
      <category>cloudflare</category>
      <category>typescript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How much VRAM do you actually need to run Llama 3 or Gemma locally?</title>
      <dc:creator>Sathvic Kollu</dc:creator>
      <pubDate>Wed, 17 Jun 2026 03:56:47 +0000</pubDate>
      <link>https://dev.to/sathvic_kollu/how-much-vram-do-you-actually-need-to-run-llama-3-or-gemma-locally-3heg</link>
      <guid>https://dev.to/sathvic_kollu/how-much-vram-do-you-actually-need-to-run-llama-3-or-gemma-locally-3heg</guid>
      <description>&lt;p&gt;Every few days someone in a local LLM thread asks the same question: "will this run on my 3060?" And the answers are almost always vibes. "Should be fine." "Probably need to quantize." Nobody shows the math, so you download 16GB, load it up, and find out the hard way.&lt;/p&gt;

&lt;p&gt;I did exactly that a while back. Grabbed an 8B model, it loaded fine on a 12GB card, I felt clever, and then it OOM'd about 20,000 tokens into a long document. The weights fit. The KV cache didn't. That gap is the whole reason for this post.&lt;/p&gt;

&lt;p&gt;So here is the actual math, with real numbers for Llama 3 and Gemma, including the part that surprised me, where two models that look identical on paper need very different amounts of memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things eat your VRAM
&lt;/h2&gt;

&lt;p&gt;When you run a model locally, your GPU memory goes to three places:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The model weights&lt;/li&gt;
&lt;li&gt;The KV cache&lt;/li&gt;
&lt;li&gt;A bit of overhead (CUDA context, activations, fragmentation)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most "how much VRAM" answers only talk about the first one. That is the mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Weights: the number everyone quotes
&lt;/h2&gt;

&lt;p&gt;This one is simple. The weights take up &lt;code&gt;parameters × bytes per weight&lt;/code&gt;. Full precision (FP16) is 2 bytes per weight, and quantization shrinks that:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Bytes/weight&lt;/th&gt;
&lt;th&gt;Llama 3 8B weights&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FP16&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;~15 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q8_0&lt;/td&gt;
&lt;td&gt;~1.06&lt;/td&gt;
&lt;td&gt;~8 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q5_K_M&lt;/td&gt;
&lt;td&gt;~0.73&lt;/td&gt;
&lt;td&gt;~5.5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q4_K_M&lt;/td&gt;
&lt;td&gt;~0.58&lt;/td&gt;
&lt;td&gt;~4.3 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Q3_K_M&lt;/td&gt;
&lt;td&gt;~0.46&lt;/td&gt;
&lt;td&gt;~3.5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Q4_K_M is the one I reach for. It is the usual sweet spot: roughly a quarter of the FP16 size, with quality that is hard to tell apart for most tasks. So an 8B model is about 4.3GB of weights. Easy. Fits anything.&lt;/p&gt;

&lt;p&gt;And that is the number that lies to you, because it is only part of the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. KV cache: the part that scales with your prompt
&lt;/h2&gt;

&lt;p&gt;When a model generates text, it caches the key and value vectors for every token it has already seen, so it does not recompute them on every new token. That cache is the KV cache, and it grows linearly with context length. Long prompt, big cache.&lt;/p&gt;

&lt;p&gt;The formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KV bytes = 2 × layers × kv_dim × context_length × bytes_per_element
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The leading 2 is one slot for keys and one for values. For Llama 3 8B that is 32 layers, a KV dimension of 1024 (it uses grouped-query attention, so the KV heads are smaller than the attention heads), and 2 bytes per element for an FP16 cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 × 32 × 1024 × 8192 × 2  ≈  1 GB at 8K context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far so good, 1GB is nothing. But watch what happens as the context grows, because the weights stay put and the cache does not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8K context: ~1 GB&lt;/li&gt;
&lt;li&gt;32K context: ~4 GB&lt;/li&gt;
&lt;li&gt;128K context: ~16 GB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sixteen gigabytes of KV cache for a model whose weights are four. That is why your model loads fine and then dies halfway through a long document. You did not run out of room for the model. You ran out of room for its memory of the conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Overhead
&lt;/h2&gt;

&lt;p&gt;CUDA reserves some memory, activations need scratch space, and allocators leave gaps. I budget about 10% on top of weights plus cache. It is a rule of thumb, not a law, but it keeps you from cutting it too fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together: Llama 3 8B
&lt;/h2&gt;

&lt;p&gt;Q4_K_M weights (about 4.3GB) plus 1GB of KV at 8K plus 10% overhead lands around 5.8GB total. That fits a 12GB card with plenty of headroom, and even an 8GB card with a little room to spare. Push the context to 32K and you are at about 9GB, still fine on 12GB. Go to a 128K context and the KV cache alone is bigger than the weights, and now you need a 24GB card.&lt;/p&gt;

&lt;p&gt;Same model, same quant. The only thing that changed was how much text you fed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that surprised me: Gemma 2 9B
&lt;/h2&gt;

&lt;p&gt;Gemma 2 9B and Llama 3 8B look like the same weight class. A billion parameters apart, both run on a normal gaming GPU, so you would assume they need about the same VRAM.&lt;/p&gt;

&lt;p&gt;Run the math. The weights are close, a touch over 4GB for Llama and about 5GB for Gemma at Q4_K_M. But the KV cache at 8K is roughly 2.6GB for Gemma, not 1GB. Gemma uses a larger head dimension and more layers, so its kv_dim is double Llama's and it has ten more layers to cache. Total comes out around 8.4GB, versus Llama's 5.8GB.&lt;/p&gt;

&lt;p&gt;A billion more parameters, but about 2.5GB more VRAM, almost all of it hiding in the KV cache. You would never guess that from the parameter count, and it is exactly the kind of thing that turns "should fit" into an OOM at the worst moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I stopped doing this by hand
&lt;/h2&gt;

&lt;p&gt;Working this out per model, per quant, per context length got old, so I built a calculator that does it: &lt;a href="https://codeswap.net/llm/llm-vram-calculator/" rel="noopener noreferrer"&gt;LLM VRAM Calculator&lt;/a&gt;. Pick a model (or punch in your own params, layers, and KV dim), choose a quant and a context length, and it breaks out weights, KV cache, and overhead, then tells you which GPUs it fits on. It runs in the browser, and nothing gets uploaded.&lt;/p&gt;

&lt;p&gt;A few things worth knowing once you can see the breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you OOM at long context, it is almost always the KV cache, not the weights. Drop the context window, or set the KV cache to FP8, which halves it.&lt;/li&gt;
&lt;li&gt;Concurrent requests multiply the cache. Serving four users at 8K is roughly four times the KV memory of one.&lt;/li&gt;
&lt;li&gt;Apple Silicon cheats this a little, since unified memory is one shared pool, so the usual VRAM ceilings do not apply the same way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb I actually use: take the weight size from your quant, add about 1GB of KV per 8K of context for a 7 to 8B model (more for Gemma-style architectures), then 10% on top. Or skip the arithmetic and check the calculator before you download 16 gigabytes.&lt;/p&gt;

&lt;p&gt;If you run something with a wildly different memory profile than the parameter count suggests, I would genuinely like to hear it. Those are the ones worth knowing about before you hit buy on a GPU.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
