<?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>Forem: CloakHQ</title>
    <description>The latest articles on Forem by CloakHQ (@cloakhq).</description>
    <link>https://forem.com/cloakhq</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3800833%2F16927d39-4740-4296-8993-8057a297ec0d.png</url>
      <title>Forem: CloakHQ</title>
      <link>https://forem.com/cloakhq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/cloakhq"/>
    <language>en</language>
    <item>
      <title>We Were Scoring 0.1 on reCAPTCHA v3. So We Rewrote Chromium</title>
      <dc:creator>CloakHQ</dc:creator>
      <pubDate>Thu, 07 May 2026 10:43:36 +0000</pubDate>
      <link>https://forem.com/cloakhq/we-were-scoring-01-on-recaptcha-v3-so-we-rewrote-chromium-31lm</link>
      <guid>https://forem.com/cloakhq/we-were-scoring-01-on-recaptcha-v3-so-we-rewrote-chromium-31lm</guid>
      <description>&lt;p&gt;We were automating sites behind Cloudflare, reCAPTCHA, DataDome. Tried every stealth tool we could find. playwright-stealth, patchright, undetected-chromedriver. Each one worked until it didn't, and when it broke, it broke silently: sessions just stopped working and you'd spend days figuring out why.&lt;/p&gt;

&lt;p&gt;reCAPTCHA v3 kept returning 0.1. If you haven't worked with v3 before: it doesn't show you a challenge. It watches the session silently and produces a score. Below 0.5 means bot. We were getting 0.1, which means "bot, confidently."&lt;/p&gt;

&lt;p&gt;We needed something universal. One tool that held up across all of them, not just the one you were fighting that week.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why JS patches don't work long-term
&lt;/h2&gt;

&lt;p&gt;Most stealth library works the same way: inject JavaScript into the page before it loads, spoof the values detection scripts look for. &lt;code&gt;navigator.webdriver&lt;/code&gt; set to false. Fake plugin lists. Canvas overrides.&lt;/p&gt;

&lt;p&gt;They all failed the same check.&lt;/p&gt;

&lt;p&gt;It took some time to understand why, but once we saw it, it was obvious. JavaScript patches run inside the browser. The browser itself (its C++ internals, the TLS fingerprint it sends, the CDP protocol behavior, the way input events are structured at the engine level) is still stock headless Chrome. Detection systems compare what JavaScript reports against everything else: what the network stack says, what the GPU reports, what the audio context returns. When those don't match, you get flagged.&lt;/p&gt;

&lt;p&gt;A JS patch puts a mask on a robot. The seams are visible because they're there.&lt;/p&gt;

&lt;p&gt;The fix had to happen at a different level.&lt;/p&gt;




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

&lt;p&gt;We patched Chromium at the source. Actual C++ changes compiled into the binary before it runs. Canvas, WebGL, audio context, fonts, GPU strings, screen properties, network timing, CDP input behavior. All modified before compilation.&lt;/p&gt;

&lt;p&gt;Websites can't tell it from a real Chrome session, because at the engine level, it is one.&lt;/p&gt;

&lt;p&gt;The first test run: reCAPTCHA v3 returned 0.9. We ran it again. Same result. Ran it on a fresh session. Same result. Then we started running it against everything that had been blocking us (Cloudflare, DataDome, FingerprintJS) and it went through them one by one, cleanly. Honestly, we expected it to help. We didn't expect it to work that well, that consistently. Sites that had been blocking us for months just... loaded.&lt;/p&gt;

&lt;p&gt;That internal fix became CloakBrowser. We open-sourced it in February.&lt;/p&gt;




&lt;h2&gt;
  
  
  Drop-in replacement
&lt;/h2&gt;

&lt;p&gt;Same API as Playwright and Puppeteer. Swap the import, nothing else changes.&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;# before
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;
&lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;().&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;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# after
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cloakbrowser&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;launch&lt;/span&gt;
&lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// after&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cloakbrowser&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;browser&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;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The binary ships with 57 source-level C++ patches and auto-generates a random fingerprint seed on every launch. Each session looks like a different device.&lt;/p&gt;

&lt;p&gt;For behavioral detection on top of the fingerprint, &lt;code&gt;humanize=True&lt;/code&gt; replaces mouse movement, keyboard input, and scroll with patterns that match real user behavior: Bezier curves, per-character typing delays, realistic scroll acceleration. One flag, no code changes.&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;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;humanize&lt;/span&gt;&lt;span class="o"&gt;=&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# types character by character
&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;button[type=submit]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;       &lt;span class="c1"&gt;# Bezier curve to click target
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Test results
&lt;/h2&gt;

&lt;p&gt;Verified against live services, May 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;reCAPTCHA v3&lt;/td&gt;
&lt;td&gt;0.9 (server-verified)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Turnstile (managed + non-interactive)&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FingerprintJS&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BrowserScan&lt;/td&gt;
&lt;td&gt;Normal (4/4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ShieldSquare&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;navigator.webdriver&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;false at source level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDP automation detection&lt;/td&gt;
&lt;td&gt;Not detected&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;reCAPTCHA scores it as a normal browser, because at the engine level, it is one.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it doesn't solve
&lt;/h2&gt;

&lt;p&gt;Proxy reputation is separate. Datacenter IPs get hard-blocked on aggressive sites regardless of how clean the fingerprint is. Residential proxies, ideally ISP/static rather than shared pools, are still part of the stack.&lt;/p&gt;

&lt;p&gt;And some advanced configurations still catch us. The arms race is real, and we'd rather say that than pretend otherwise.&lt;/p&gt;




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

&lt;p&gt;No install needed to test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; cloakhq/cloakbrowser cloaktest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runs the full stealth test suite against live detection sites from your machine. Or install directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;cloakbrowser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;cloakbrowser playwright-core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Binary downloads automatically on first run, around 200MB, cached locally. Works on Linux, macOS, Windows.&lt;/p&gt;

&lt;p&gt;The repo is &lt;a href="https://github.com/CloakHQ/CloakBrowser" rel="noopener noreferrer"&gt;github.com/CloakHQ/CloakBrowser&lt;/a&gt;. If you try it and something still gets blocked, open an issue with the site. That's exactly the feedback that drives the next build.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;CloakBrowser is free and open source (MIT). The compiled binary has a separate license: free to use, no redistribution.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>webscraping</category>
      <category>python</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
