<?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: COOLOSJ STUDIOS</title>
    <description>The latest articles on DEV Community by COOLOSJ STUDIOS (@coolosj_studios).</description>
    <link>https://dev.to/coolosj_studios</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%2F4159162%2Fc961e0ce-b1da-4ae8-a196-00e1f7f8d977.png</url>
      <title>DEV Community: COOLOSJ STUDIOS</title>
      <link>https://dev.to/coolosj_studios</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/coolosj_studios"/>
    <language>en</language>
    <item>
      <title>COOLOSJ SHIELD</title>
      <dc:creator>COOLOSJ STUDIOS</dc:creator>
      <pubDate>Sat, 03 Oct 2026 07:06:00 +0000</pubDate>
      <link>https://dev.to/coolosj_studios/coolosj-shield-58eh</link>
      <guid>https://dev.to/coolosj_studios/coolosj-shield-58eh</guid>
      <description>&lt;p&gt;I spent a few weeks building a CAPTCHA engine. The detection logic was the fun part. The part that actually taught me something was watching my own tests confidently tell me the opposite of the truth.&lt;/p&gt;

&lt;p&gt;Here are three failures worth stealing from.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A single outlier destroyed my best signal
Human arm movement follows a minimum-jerk trajectory. You accelerate, hit peak speed around the midpoint, then decelerate into the target. It's one of the more reliable things about us — a scripted page.mouse.move() loop runs at constant speed instead.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I measured peak speed divided by mean speed:&lt;/p&gt;

&lt;p&gt;JavaScript&lt;/p&gt;

&lt;p&gt;function velocityProfile(points) {&lt;br&gt;
  const speeds = [];&lt;br&gt;
  for (let i = 1; i &amp;lt; points.length; i++) {&lt;br&gt;
    const dt = Math.max(1, points[i][2] - points[i-1][2]);&lt;br&gt;
    const dist = Math.hypot(&lt;br&gt;
      points[i][0] - points[i-1][0],&lt;br&gt;
      points[i][1] - points[i-1][1]&lt;br&gt;
    );&lt;br&gt;
    speeds.push(dist / dt);&lt;br&gt;
  }&lt;br&gt;
  const mean = speeds.reduce((a, b) =&amp;gt; a + b, 0) / speeds.length;&lt;br&gt;
  return Math.max(...speeds) / mean;&lt;br&gt;
}&lt;br&gt;
In my Node simulation: humans ~1.9, scripts ~1.1. Clean separation. Shipped it.&lt;/p&gt;

&lt;p&gt;Then I ran it against a real browser, and the straight-line bot scored 2.32 — higher than my human baseline. My detector was handing out a positive signal for being a bot.&lt;/p&gt;

&lt;p&gt;Why&lt;br&gt;
When Playwright clicks a button, the cursor doesn't travel there. It teleports.&lt;/p&gt;

&lt;p&gt;text&lt;/p&gt;

&lt;p&gt;pure straight line, even timing : 1.00&lt;br&gt;
same + one click teleport       : 3.62&lt;br&gt;
four segments + teleport        : 9.62&lt;br&gt;
One jump of a few hundred pixels in a single millisecond is an enormous speed value. Math.max() grabs it, and the whole ratio becomes meaningless.&lt;/p&gt;

&lt;p&gt;Peak-based statistics are destroyed by a single outlier — and automation generates outliers constantly.&lt;/p&gt;

&lt;p&gt;The fix&lt;br&gt;
Stop using the maximum. Use a measure of spread that ignores extremes:&lt;/p&gt;

&lt;p&gt;JavaScript&lt;/p&gt;

&lt;p&gt;function speedDispersion(points) {&lt;br&gt;
  const speeds = [];&lt;br&gt;
  for (let i = 1; i &amp;lt; points.length; i++) {&lt;br&gt;
    const dt = Math.max(1, points[i][2] - points[i-1][2]);&lt;br&gt;
    speeds.push(Math.hypot(&lt;br&gt;
      points[i][0] - points[i-1][0],&lt;br&gt;
      points[i][1] - points[i-1][1]&lt;br&gt;
    ) / dt);&lt;br&gt;
  }&lt;br&gt;
  speeds.sort((a, b) =&amp;gt; a - b);&lt;br&gt;
  const median = speeds[Math.floor(speeds.length / 2)];&lt;br&gt;
  if (!(median &amp;gt; 0)) return undefined;&lt;br&gt;
  const q1 = speeds[Math.floor(speeds.length * 0.25)];&lt;br&gt;
  const q3 = speeds[Math.floor(speeds.length * 0.75)];&lt;br&gt;
  return (q3 - q1) / median;   // interquartile range over median&lt;br&gt;
}&lt;br&gt;
Results, with a teleport appended to each path:&lt;/p&gt;

&lt;p&gt;path    IQR/median&lt;br&gt;
straight line bot   0.45&lt;br&gt;
bezier + jitter bot 0.78&lt;br&gt;
real human  1.13&lt;br&gt;
The teleport changes none of them. Same separation, no fragility.&lt;/p&gt;

&lt;p&gt;Then I tracked teleports as their own signal rather than letting them corrupt a different one — a pointer that jumps instead of travelling is itself interesting.&lt;/p&gt;

&lt;p&gt;The actual lesson&lt;br&gt;
I validated the idea in a simulation that contained no teleports. The bug lived entirely in the gap between my model of a browser and a real one.&lt;/p&gt;

&lt;p&gt;If you're building any kind of detection, run it against the real thing before you trust the separation you measured.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"success: true" for a bot scoring 0.0
This one's a design trap, and it's industry-wide.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;reCAPTCHA v3 returns success: true for any well-formed token — including one that scored 0.0. In their API, success means "this token parsed and hasn't expired", not "this is a human". The score is a separate field you're expected to check yourself.&lt;/p&gt;

&lt;p&gt;So people write this:&lt;/p&gt;

&lt;p&gt;JavaScript&lt;/p&gt;

&lt;p&gt;const { success } = await verifyCaptcha(token);&lt;br&gt;
if (success) {&lt;br&gt;
  await createAccount(req.body);   // every bot walks straight through&lt;br&gt;
}&lt;br&gt;
I had copied that semantic for drop-in compatibility. It seemed like the responsible thing to do.&lt;/p&gt;

&lt;p&gt;Then someone looked at my demo output and asked a very simple question: "did it actually let the bot create an account?"&lt;/p&gt;

&lt;p&gt;It had. My engine detected the bot perfectly — score: 0, band: block, every signal firing — and then told the website everything was fine.&lt;/p&gt;

&lt;p&gt;Now a bot that legitimately solves the proof-of-work still gets:&lt;/p&gt;

&lt;p&gt;JSON&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "success": false,&lt;br&gt;
  "error-codes": ["score-below-threshold"],&lt;br&gt;
  "verdict": "deny",&lt;br&gt;
  "score": 0,&lt;br&gt;
  "signals": ["webdriverFlag", "zeroScreen", "noInteraction", "instantSubmit(60ms)"]&lt;br&gt;
}&lt;br&gt;
Solving a proof-of-work proves you spent CPU. It does not prove you are human. Those are different claims and the API should say which one it's making.&lt;/p&gt;

&lt;p&gt;If you're designing an API, make the obvious usage the safe usage. Compatibility with a known footgun is not a feature.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;My test harness lied to me three times
I built a red-team harness: ten HTTP attacks, then four levels of real browser automation from plain Playwright up to Bézier paths with synthetic tremor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It reported "4 attacks blocked" while I watched four accounts get created on screen.&lt;/p&gt;

&lt;p&gt;Three separate bugs, each a different flavour of wrong:&lt;/p&gt;

&lt;p&gt;Swallowed exception defaulting to safe.&lt;/p&gt;

&lt;p&gt;JavaScript&lt;/p&gt;

&lt;p&gt;try { apiResult = await res.json(); } catch {}&lt;br&gt;
// capture fails -&amp;gt; apiResult stays null&lt;br&gt;
// -&amp;gt; result.success stays undefined&lt;br&gt;
// -&amp;gt; print logic treats "not success" as "blocked"&lt;br&gt;
A security tool must never default to the reassuring answer. There's now an explicit UNKNOWN state that's loudly not a pass.&lt;/p&gt;

&lt;p&gt;Config applied too broadly. I passed --disable-blink-features=AutomationControlled to every attack level — including the one meant to represent an undefended bot. So "plain Playwright" was silently stealthed, and all four levels were effectively level 4.&lt;/p&gt;

&lt;p&gt;A substring match. The blocked banner reads "BLOCKED — no account created". My parser checked for "account created". Every blocked run reported as a pass.&lt;/p&gt;

&lt;p&gt;All three were caught the same way: by watching the browser window instead of believing the terminal.&lt;/p&gt;

&lt;p&gt;A false alarm wastes an afternoon. A false all-clear ships a vulnerability.&lt;/p&gt;

&lt;p&gt;If you write tooling that tells you whether something is safe, that tooling needs testing as carefully as the thing it's checking.&lt;/p&gt;

&lt;p&gt;What I'd do differently&lt;br&gt;
Test against reality before trusting separation. Simulations omit exactly the messy details that break you.&lt;br&gt;
Prefer robust statistics. Median and IQR over mean and max, anywhere adversarial input is possible.&lt;br&gt;
Make absence suspicious. I twice built signals a bot could dodge by simply not sending the field — omitting scored better than reporting honestly. Any new signal must make its own absence count against you.&lt;br&gt;
Distrust your own green checkmarks. Especially when a human can see the opposite happening.&lt;br&gt;
The honest limitations&lt;br&gt;
Everything above detects clumsy automation. None of it stops someone who properly models limb dynamics — real minimum-jerk curves, correlated tremor, plausible timing.&lt;/p&gt;

&lt;p&gt;Every client-side signal is forgeable. The browser reports JSON an attacker fully controls. You're not building a wall, you're raising the cost of an attack until it exceeds what the attack earns. The durable signals are all server-side: TLS fingerprinting, ASN reputation, cross-customer threat data.&lt;/p&gt;

&lt;p&gt;It's also worth saying that Cloudflare Turnstile is free and unlimited, and for most low-risk forms it's the sensible choice.&lt;/p&gt;

&lt;p&gt;The thing I built&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://coolosj-shield-api.shrijesh-spatil.workers.dev/#how" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;coolosj-shield-api.shrijesh-spatil.workers.dev&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
 — invisible CAPTCHA, proof-of-work plus behavioural scoring, running on Cloudflare Workers. Free tier is 100 verifications/day.

&lt;p&gt;The widget on the homepage is the real engine, not a recording. It scores your actual pointer movement as you use the page.&lt;/p&gt;

&lt;p&gt;If you break it, I'd genuinely like to know how. Contact me: &lt;a href="mailto:coolosjstudios@gmail.com"&gt;coolosjstudios@gmail.com&lt;/a&gt; (yes, i need funding)&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>security</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
