<?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: Tyler Francis</title>
    <description>The latest articles on DEV Community by Tyler Francis (@just-a-guy360).</description>
    <link>https://dev.to/just-a-guy360</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%2F3959086%2Fa653f307-4384-491e-9cd1-11d0d37d5454.png</url>
      <title>DEV Community: Tyler Francis</title>
      <link>https://dev.to/just-a-guy360</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/just-a-guy360"/>
    <language>en</language>
    <item>
      <title>The Regex Bug That Passes Code Review, Passes Tests, and Takes Down Prod</title>
      <dc:creator>Tyler Francis</dc:creator>
      <pubDate>Thu, 30 Jul 2026 05:50:18 +0000</pubDate>
      <link>https://dev.to/just-a-guy360/the-regex-bug-that-passes-code-review-passes-tests-and-takes-down-prod-4gf1</link>
      <guid>https://dev.to/just-a-guy360/the-regex-bug-that-passes-code-review-passes-tests-and-takes-down-prod-4gf1</guid>
      <description>&lt;p&gt;Here's a regex. It validates that a string is a list of comma-separated words. Twelve characters. Take a second and decide whether you'd approve it in a PR.&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;RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(\w&lt;/span&gt;&lt;span class="sr"&gt;+,&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd have approved it. It reads correctly, it does what it says, and it passes every test you'd naturally write:&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="nx"&gt;RE&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alpha&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;// true,  instant&lt;/span&gt;
&lt;span class="nx"&gt;RE&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alpha,beta,gamma&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// true,  instant&lt;/span&gt;
&lt;span class="nx"&gt;RE&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alpha,,beta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;// true,  instant&lt;/span&gt;
&lt;span class="nx"&gt;RE&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="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                       &lt;span class="c1"&gt;// false, instant&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now hand it a string that is 30 characters long and doesn't match:&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="nx"&gt;RE&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my machine that call doesn't return. Not "returns slowly" — the event loop is gone. Node is pinned at 100% of one core doing regex backtracking, and because JavaScript's regex engine has no timeout and no interrupt, nothing you can do from JS will stop it. No &lt;code&gt;AbortController&lt;/code&gt;, no &lt;code&gt;Promise.race&lt;/code&gt;, no &lt;code&gt;setTimeout&lt;/code&gt;. The only exit is killing the process.&lt;/p&gt;

&lt;p&gt;If that regex is validating a request body, you have just shipped a single-request denial of service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it explodes
&lt;/h2&gt;

&lt;p&gt;The problem is &lt;code&gt;(\w+,?)+&lt;/code&gt;. There's an unbounded quantifier &lt;code&gt;+&lt;/code&gt; inside a group that is itself repeated with &lt;code&gt;+&lt;/code&gt;. That means there are multiple distinct ways for the engine to divide the same input between iterations.&lt;/p&gt;

&lt;p&gt;Given &lt;code&gt;aaa&lt;/code&gt;, the group can match:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aaa&lt;/code&gt; in one iteration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aa&lt;/code&gt; then &lt;code&gt;a&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;a&lt;/code&gt; then &lt;code&gt;aa&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;a&lt;/code&gt; then &lt;code&gt;a&lt;/code&gt; then &lt;code&gt;a&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four ways to match three characters. The engine has to try all of them before it can conclude the string doesn't match, because the failing character is at the &lt;em&gt;end&lt;/em&gt;. That's 2^(n-1) paths. At n=30 you're around 500 million. At n=40 you're past 500 billion and the heat death of the universe is a more realistic completion estimate than your request timeout.&lt;/p&gt;

&lt;p&gt;The technical name is catastrophic backtracking. The security name is ReDoS — Regular Expression Denial of Service, CWE-1333.&lt;/p&gt;

&lt;p&gt;The reason it survives review is that &lt;strong&gt;every failure mode is invisible from the code and from the happy path.&lt;/strong&gt; The regex is short. It's readable. It has no obvious "hot spot." And it is instantaneous on every input that matches. You only see it on inputs that &lt;em&gt;nearly&lt;/em&gt; match and then fail late — which is exactly the input an attacker sends and almost never the input a test suite contains.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three shapes to look for
&lt;/h2&gt;

&lt;p&gt;After staring at a lot of these, they mostly reduce to three patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Nested unbounded quantifiers.&lt;/strong&gt; A quantified group whose body can also repeat.&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;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(\w&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\s?)&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&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="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&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;span class="c1"&gt;// this one is fine, actually — see below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third is safe because &lt;code&gt;\d+,&lt;/code&gt; ends in a mandatory literal &lt;code&gt;,&lt;/code&gt;. That comma anchors each iteration, so there's exactly one way to split the input. The presence of nesting isn't sufficient on its own — &lt;strong&gt;ambiguity&lt;/strong&gt; is what matters. Nesting is just the most common way to get it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Ambiguous alternation inside a repeat.&lt;/strong&gt; Branches that can match the same text.&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="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&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;&lt;code&gt;\d&lt;/code&gt; is a subset of &lt;code&gt;\w&lt;/code&gt;. Every digit can be matched by either branch, so an n-digit string has 2^n paths through the group. This one hides better than nested quantifiers because there's no visible nesting at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Overlapping adjacent quantifiers.&lt;/strong&gt;&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="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&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;span class="sr"&gt;/^.*.*=.*$/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two adjacent quantifiers over intersecting character classes. Any character matched by the first could equally have been matched by the second.&lt;/p&gt;

&lt;h2&gt;
  
  
  The advice you'll find online is wrong for JavaScript
&lt;/h2&gt;

&lt;p&gt;Search for how to fix catastrophic backtracking and the top answer is almost always: wrap the inner group in an atomic group, &lt;code&gt;(?&amp;gt;...)&lt;/code&gt;, or use a possessive quantifier, &lt;code&gt;a++&lt;/code&gt;. Atomic groups tell the engine "once you've matched this, never give the characters back," which kills the backtracking outright.&lt;/p&gt;

&lt;p&gt;This is correct advice. It is also &lt;strong&gt;not available in JavaScript.&lt;/strong&gt; JS regex has no atomic groups and no possessive quantifiers. &lt;code&gt;/(?&amp;gt;a+)/&lt;/code&gt; is a syntax error. &lt;code&gt;/a++/&lt;/code&gt; is a syntax error — &lt;code&gt;+&lt;/code&gt; is not a valid quantifier target.&lt;/p&gt;

&lt;p&gt;I found this out the annoying way: an early version of a rewrite suggester I wrote emitted &lt;code&gt;a++&lt;/code&gt; as its fix. It looked right, it matched the advice everyone gives, and it was unrunnable JavaScript. The corrected version has to separate the base atom from its own quantifier before recombining, because &lt;code&gt;\w+&lt;/code&gt; and &lt;code&gt;\w&lt;/code&gt; are different things and you can't blindly append to the end of an arbitrary token.&lt;/p&gt;

&lt;p&gt;The closest JS gets is an atomic-group &lt;em&gt;emulation&lt;/em&gt; using a lookahead plus a backreference:&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="c1"&gt;// atomic (?&amp;gt;\w+) emulated&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="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works, but it is unreadable, and in most real cases you don't need it. The two fixes that actually apply:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make the ambiguity impossible.&lt;/strong&gt; Rewrite so there's exactly one way to split the input.&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="c1"&gt;// before: /^(\w+,?)+$/&lt;/span&gt;
&lt;span class="c1"&gt;// after:&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;(?:,&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;w&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="p"&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;Each iteration now starts with a mandatory &lt;code&gt;,&lt;/code&gt;. No ambiguity, linear time, same accepted language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Or stop using a regex.&lt;/strong&gt; For the comma-separated-words case:&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;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&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="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\w&lt;/span&gt;&lt;span class="sr"&gt;*$/&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;p&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uglier. Also unconditionally linear, and obviously correct to the next person who reads it. A lot of ReDoS bugs exist because a regex was reached for on reflex where a &lt;code&gt;split&lt;/code&gt; would have done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And regardless: bound your inputs.&lt;/strong&gt; &lt;code&gt;if (input.length &amp;gt; 256) return false;&lt;/code&gt; before the test caps the worst case no matter what the pattern does. It is not a fix — the exponent is still there, you've just chosen an exponent you can survive. Do it anyway. It's one line and it turns a total outage into a slow request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing for this is trickier than it looks
&lt;/h2&gt;

&lt;p&gt;The obvious approach is to run the regex against a generated attack string and time it. Don't do that in the process you care about — that's the same single-request DoS, self-inflicted. If you're going to time it, do it in a worker thread or a child process you can kill:&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="c1"&gt;// crude but honest&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Worker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:worker_threads&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;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
  const { workerData, parentPort } = require("node:worker_threads");
  const t = Date.now();
  new RegExp(workerData.src).test(workerData.input);
  parentPort.postMessage(Date.now() - t);
`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;workerData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^(&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="s2"&gt;w+,?)+$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&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="mi"&gt;30&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="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminate&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VULNERABLE: timed out&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ms&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better still is to not execute the pattern at all. You can determine ambiguity by parsing the regex into an AST and checking the structural properties — whether a quantified node's body is nullable or self-overlapping, whether alternation branches have intersecting first-character sets, whether adjacent quantifiers overlap. That's static analysis, it terminates, and it never runs untrusted input through an engine with no timeout.&lt;/p&gt;

&lt;p&gt;That's the approach I ended up taking. I wrote a small MCP server that does exactly this, because I wanted my own agent to be able to check a regex before writing it into a codebase rather than after: &lt;a href="https://regex-safety-audit-mcp.mcpize.run" rel="noopener noreferrer"&gt;regex-safety-audit-mcp&lt;/a&gt;. It parses the pattern into a real hand-written AST — not string heuristics — flags nested unbounded quantifiers, ambiguous alternation inside repeated groups, and backreferences, and generates an attack string for you to test &lt;strong&gt;in your own sandboxed process&lt;/strong&gt;. It never executes the pattern itself. A regex-safety tool that runs untrusted regexes to see if they're slow would be a fairly funny way to get owned.&lt;/p&gt;

&lt;p&gt;Three tools: &lt;code&gt;analyze_redos_risk&lt;/code&gt;, &lt;code&gt;generate_attack_string&lt;/code&gt;, &lt;code&gt;suggest_safe_rewrite&lt;/code&gt;. Free tier if you just want to throw a few patterns at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part worth remembering
&lt;/h2&gt;

&lt;p&gt;Grep your codebase for &lt;code&gt;)+&lt;/code&gt;, &lt;code&gt;)*&lt;/code&gt;, &lt;code&gt;){2,}&lt;/code&gt; — quantifiers applied to groups. Most of them are fine. The ones where the group's body ends in another unbounded quantifier are worth thirty seconds each.&lt;/p&gt;

&lt;p&gt;And check the regexes you didn't write. Copy-pasted email validators are a famously rich source; the "RFC 5322 compliant" one that circulates on Stack Overflow has bitten multiple production systems. So has the default URL-matching regex in more than one popular library. The bug doesn't care that you got the pattern from a trustworthy-looking place.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you've hit one of these in production, I'd genuinely like to hear which pattern did it — the catalog of real-world offenders is more useful than any general rule.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>regex</category>
      <category>node</category>
    </item>
    <item>
      <title>Modeling Prop Firm Trailing Drawdown in Pine Script (So Your Backtest Stops Lying)</title>
      <dc:creator>Tyler Francis</dc:creator>
      <pubDate>Fri, 12 Jun 2026 20:03:47 +0000</pubDate>
      <link>https://dev.to/just-a-guy360/modeling-prop-firm-trailing-drawdown-in-pine-script-so-your-backtest-stops-lying-2n7e</link>
      <guid>https://dev.to/just-a-guy360/modeling-prop-firm-trailing-drawdown-in-pine-script-so-your-backtest-stops-lying-2n7e</guid>
      <description>&lt;p&gt;If you write TradingView strategies for futures prop firm accounts (Apex, Topstep, MyFundedFutures), there's a structural problem with your backtest: Pine Script's &lt;code&gt;strategy()&lt;/code&gt; framework evaluates terminal equity, but prop firms evaluate a path-dependent constraint called trailing drawdown. A strategy can be profitable in the Strategy Tester and still fail evals consistently. Here's how to model the constraint correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What trailing drawdown actually does
&lt;/h3&gt;

&lt;p&gt;On most futures firms, your drawdown floor ratchets up with your account's high-water mark — and on many firms, that high-water mark includes &lt;em&gt;unrealized&lt;/em&gt; profit. If you're up $600 open on a trade and it stops out at breakeven, your closed P&amp;amp;L is unchanged but your drawdown floor moved up $600 permanently. The backtest sees a scratch. The eval sees lost cushion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Track the unrealized high-water mark
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//@version=6
strategy("Eval-Aware Backtest", overlay=true,
     initial_capital=50000, commission_type=strategy.commission.cash_per_contract,
     commission_value=0.67, slippage=1)

float liveEquity = strategy.equity

var float hwm = strategy.initial_capital
hwm := math.max(hwm, liveEquity)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;strategy.equity&lt;/code&gt; in Pine already includes open profit, which is what we want for the high-water mark approximation. Run this on 1-minute bars for the closest approximation to tick-level ratcheting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Compute the trailing floor and breach condition
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;float trailDD = 2500.0  // 50k account, typical
float floorLevel = hwm - trailDD

float floorCapped = math.min(floorLevel, strategy.initial_capital + 100)

bool breached = liveEquity &amp;lt;= floorCapped
var bool evalFailed = false
if breached
    evalFailed := true
    strategy.close_all(comment="DD BREACH")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Gate entries on eval state
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bool profitTargetHit = strategy.equity &amp;gt;= strategy.initial_capital + 3000
bool canTrade = not evalFailed and not profitTargetHit

if canTrade and longSignal
    strategy.entry("L", strategy.long)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;evalFailed&lt;/code&gt; flips, the account is dead — letting the backtest keep trading after a breach is the single most common way Pine backtests overstate eval performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Report what matters
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var table t = table.new(position.top_right, 1, 3)
if barstate.islast
    table.cell(t, 0, 0, evalFailed ? "EVAL FAILED" : profitTargetHit ? "EVAL PASSED" : "IN PROGRESS")
    table.cell(t, 0, 1, "Floor: " + str.tostring(floorCapped, "#.00"))
    table.cell(t, 0, 2, "Cushion: " + str.tostring(liveEquity - floorCapped, "#.00"))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Net profit is now a secondary metric. The number you optimize is whether the path ever touched the floor.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changes when you do this
&lt;/h3&gt;

&lt;p&gt;When I retrofitted this onto a library of strategies that all looked fine in the standard tester, about two-thirds failed simulated evals. The failures clustered: strategies with high MAE relative to target, and strategies with win rates under 40%.&lt;/p&gt;

&lt;p&gt;The survivors — low MAE, 40-60% win rate, hard daily-loss gating — are a genuinely different population than "most profitable backtest." That's the core insight: the eval is a different objective function, and you have to encode it.&lt;/p&gt;

&lt;p&gt;Run your own strategies through this harness before paying for another evaluation. If you'd rather start from strategies already built and validated against these exact constraints — with full source code so you can verify everything — that's what I sell at &lt;a href="https://propfirmpinescripts.com" rel="noopener noreferrer"&gt;propfirmpinescripts.com&lt;/a&gt;, alongside free tools (drawdown calculator, eval simulator) that run this math in the browser.&lt;/p&gt;

</description>
      <category>pinescript</category>
      <category>trading</category>
      <category>tutorial</category>
      <category>fintech</category>
    </item>
    <item>
      <title>I Built a Side Project Selling Pine Script Strategies for Prop Traders</title>
      <dc:creator>Tyler Francis</dc:creator>
      <pubDate>Fri, 29 May 2026 21:23:33 +0000</pubDate>
      <link>https://dev.to/just-a-guy360/i-built-a-side-project-selling-pine-script-strategies-for-prop-traders-55bj</link>
      <guid>https://dev.to/just-a-guy360/i-built-a-side-project-selling-pine-script-strategies-for-prop-traders-55bj</guid>
      <description>&lt;p&gt;Started &lt;a href="https://propfirmpinescripts.com" rel="noopener noreferrer"&gt;propfirmpinescripts.com&lt;/a&gt; a while back selling pine script strategies for futures prop firm traders. Figured I would share some of what worked and what has not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem I was solving
&lt;/h2&gt;

&lt;p&gt;I was actually trading on Apex myself and kept running into the same thing. Every pine script strategy I found online was not built for prop firm rules. Daily loss limits not enforced in code. No end of day flatten. Blew an evaluation partly because of it.&lt;/p&gt;

&lt;p&gt;Figured other traders had the same problem. Coded the rules myself, then decided to sell the scripts.&lt;/p&gt;

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

&lt;p&gt;Pre-built pine scripts for 4 instruments: GC (gold futures), MES (micro S&amp;amp;P), MNQ (micro Nasdaq), CL (crude oil). Each one has daily loss lock, EOD flatten, win lock coded in. You can configure the limits for different firms without touching the core logic.&lt;/p&gt;

&lt;p&gt;Priced at $50 for a single script or $150 for all 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually moved conversions
&lt;/h2&gt;

&lt;p&gt;Adding real payout screenshots. Like actual Apex payout certificates from traders who passed using the strategies. Before I did that — traffic but weak conversions. After — noticeably better.&lt;/p&gt;

&lt;p&gt;Prop firm traders do not trust backtest results at all anymore. Too many people have gamed them. A real funded account payout is the only thing that actually means something to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where things are at
&lt;/h2&gt;

&lt;p&gt;Still early. Revenue is real but small. Building more SEO content, getting into prop firm communities, and eventually a subscription tier for updates when firms change their rules.&lt;/p&gt;

&lt;p&gt;If you are a dev with trading knowledge this space is underbuilt.&lt;/p&gt;

</description>
      <category>indiehacker</category>
      <category>saas</category>
      <category>trading</category>
      <category>pinescript</category>
    </item>
    <item>
      <title>Apex Trader Funding vs Topstep: Best Prop Firm for Algo Traders in 2025</title>
      <dc:creator>Tyler Francis</dc:creator>
      <pubDate>Fri, 29 May 2026 21:23:31 +0000</pubDate>
      <link>https://dev.to/just-a-guy360/apex-trader-funding-vs-topstep-best-prop-firm-for-algo-traders-in-2025-1hfh</link>
      <guid>https://dev.to/just-a-guy360/apex-trader-funding-vs-topstep-best-prop-firm-for-algo-traders-in-2025-1hfh</guid>
      <description>&lt;p&gt;I have tried a few prop firms at this point and the rules are genuinely different in ways that matter if you are running automated strategies. Here is my take after actually trading through evaluations on each.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing that changes everything: consistency rules
&lt;/h2&gt;

&lt;p&gt;Apex Trader Funding has no consistency rule. I cannot overstate how much better this is for algo trading. Your system can have a huge trending day and it does not hurt you at all.&lt;/p&gt;

&lt;p&gt;Topstep has a 30% rule. Your single best day cannot be more than 30% of your total evaluation profit. So if your algo catches a 200 tick CL move that represents 35% of your week profit, you are potentially failing even though you were profitable. This is not theoretical — it happened to me.&lt;/p&gt;

&lt;p&gt;MyFundedFutures has no consistency rule, trailing drawdown of $2,200 on $50k. Good option.&lt;/p&gt;

&lt;p&gt;FTMO has no consistency rule, 5% daily loss limit, good for automated strategies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The instrument selection matters too
&lt;/h2&gt;

&lt;p&gt;Apex has GC, CL, MES, MNQ, ES, NQ, M2K, ZB, ZN and more. Topstep is more limited — mainly the equity index micros. If you are running a gold or crude oil strategy you basically have to go with Apex or FTMO.&lt;/p&gt;

&lt;h2&gt;
  
  
  My recommendation
&lt;/h2&gt;

&lt;p&gt;Apex if you are automating. Better rules, way more instruments, and the trailing drawdown model actually makes sense from a risk management perspective.&lt;/p&gt;

&lt;p&gt;I sell pre-built scripts specifically for Apex at &lt;a href="https://propfirmpinescripts.com" rel="noopener noreferrer"&gt;propfirmpinescripts.com&lt;/a&gt; — 4 strategies across GC, MES, MNQ, and CL with all the rules coded in. Tested on actual evaluations.&lt;/p&gt;

</description>
      <category>trading</category>
      <category>futures</category>
      <category>finance</category>
      <category>algotrading</category>
    </item>
    <item>
      <title>How to Build a Pine Script Strategy That Passes Prop Firm Evaluation</title>
      <dc:creator>Tyler Francis</dc:creator>
      <pubDate>Fri, 29 May 2026 21:23:28 +0000</pubDate>
      <link>https://dev.to/just-a-guy360/how-to-build-a-pine-script-strategy-that-passes-prop-firm-evaluation-3j18</link>
      <guid>https://dev.to/just-a-guy360/how-to-build-a-pine-script-strategy-that-passes-prop-firm-evaluation-3j18</guid>
      <description>&lt;p&gt;So I spent a while getting burned on prop firm evaluations before I figured this out. The issue was that my Pine Script was not built with prop firm rules in mind. Most scripts you find online are not. Here is what actually needs to be in your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Daily Loss Lock
&lt;/h2&gt;

&lt;p&gt;This is the big one. Your script has to stop trading automatically if you hit the firm daily drawdown. Do not rely on yourself to close manually — you will miss it eventually.&lt;/p&gt;

&lt;p&gt;On Apex that is $2,500 on a $50k account. Topstep is only $1,000. Code it in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var float dayStart = na
if dayofweek != dayofweek[1] or na(dayStart)
    dayStart := strategy.equity
float dailyPnL = strategy.equity - dayStart
if dailyPnL &amp;lt;= -2500
    strategy.close_all("daily loss limit hit")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  End of Day Flatten
&lt;/h2&gt;

&lt;p&gt;Every prop firm requires you to be flat before session close. 3:55pm CT for CME futures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if hour == 15 and minute &amp;gt;= 55 and strategy.position_size != 0
    strategy.close_all("EOD flatten")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Apex vs Topstep for algos
&lt;/h2&gt;

&lt;p&gt;Apex has no consistency rule at all. Topstep caps your best single day at 30% of your total eval profit. If your algo has a great trending day it can actually hurt you on Topstep. I break this down at &lt;a href="https://propfirmpinescripts.com" rel="noopener noreferrer"&gt;propfirmpinescripts.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-built compliant scripts
&lt;/h2&gt;

&lt;p&gt;If you want scripts that already have all this coded in, they are at &lt;a href="https://propfirmpinescripts.com" rel="noopener noreferrer"&gt;propfirmpinescripts.com&lt;/a&gt;. The bundle includes 4 strategies for GC, MES, MNQ, and CL — all with EOD flatten, daily loss locks, and win locks built in.&lt;/p&gt;

</description>
      <category>trading</category>
      <category>pinescript</category>
      <category>algotrading</category>
      <category>futures</category>
    </item>
  </channel>
</rss>
