Among browser automation tools, Puppeteer has occupied a unique position for years. Compared with the more heavyweight Selenium ecosystem, it provides developers with direct, high-performance control over Chromium right out of the box, all within the Node.js environment.
Puppeteer has become something of an industry standard, largely thanks to its huge ecosystem of ready-to-use plugins and its close integration with the V8 engine. In practice, you can combine Puppeteer with puppeteer-extra-plugin-stealth, invest in high-quality proxies, and carefully spoof Canvas and WebGL fingerprints. For many use cases, this combination can be enough.
The situation changes, however, when you are dealing with websites protected by more advanced anti-bot systems. A scraper that works perfectly on less protected targets can still find itself blocked by services such as Cloudflare, Akamai, or DataDome.
The issue isn't necessarily related to your scraper's logic or the quality of the proxies you're using. Instead, the source of the problem can be found in one of the core technologies that makes Puppeteer so powerful and convenient: the Chrome DevTools Protocol (CDP).
This low-level browser control protocol can leave behind distinctive digital traces. Modern anti-fraud and bot-detection systems can analyze these signals and use them to distinguish automated browsers from genuine users.
So, where exactly do these leaks come from?
Let's take a closer look at how these signals are exposed and why simple spoofing techniques applied to a standard Puppeteer setup are no longer always enough.
How WebSocket Can Turn Into a Local Scanner
First, let's cover some basics. WebSocket is a TCP-based communication protocol designed to support real-time data exchange between a client and a server.
It was originally developed for use cases such as low-latency browser games and real-time features like typing indicators in chat applications. However, anti-fraud developers soon recognized another possibility: if a browser is capable of opening a socket connection to a remote server, there is usually nothing stopping it from trying to connect to services running on the user's local machine as well.
The process is fairly straightforward. Once the target website loads—or after a user logs in—a hidden anti-fraud script can start making connection attempts asynchronously. It doesn't need to check every port on the machine. Instead, it can probe 127.0.0.1 against a predefined set of ports, including 9222, which is commonly associated with CDP, as well as ports frequently used by local VPN clients and database servers.
This simple technique is enough to find what the script is looking for.
Why browsers allow this
Modern browsers and operating systems have two architectural characteristics that make this possible.
Bypassing strict CORS restrictions. If an anti-fraud script tried to scan ports using a regular HTTP request (
via fetch()orXMLHttpRequest), the browser would block access to the response because of its security measures, same-origin policy, CORS restrictions, and mixed content rules. WebSockets work differently. The protocol does not use the traditional CORS model to restrict access to responses. Instead, it sends anOriginheader, which the server may choose to validate. Simply determining whether a connection succeeds or fails already provides valuable information for analysis.The firewall's blind spot. The
loopbackinterface (127.0.0.1) enjoys an exceptionally high level of trust within the operating system, as the traffic sent from the browser to a local port never leaves your network interface.
Built-in firewalls on systems such as Windows and macOS generally don't inspect or filter these internal connections in the same way they would external network traffic. They are typically treated as inherently trusted.
This effectively gives anti-fraud systems a legitimate and largely unrestricted way to probe parts of the local environment.
But there is still an obvious question: if browser security prevents a script from directly reading system-level responses, how can it determine whether a particular port is actually open?
The answer lies in timing attacks.
How a timing attack works
The browser sandbox places strict limits on what scripts are allowed to access. Because of the Same-Origin Policy (SOP) and CORS restrictions, a script cannot simply inspect a response from a local service or retrieve the exact error code generated when a connection fails. For a timing attack, however, that information isn't necessary.
The browser sandbox places strict limits on what scripts are allowed to access. Because of the Same-Origin Policy (SOP) and CORS restrictions, a script cannot simply inspect a response from a local service or retrieve the exact error code generated when a connection fails. For a timing attack, however, that information isn't necessary.
The process works like this:
- The script starts an internal timer (typically using
performance.now()). - It initiates a WebSocket connection:
new WebSocket('ws://127.0.0.1:9222') - The script waits for the
onerrorevent to fire. - Once the error occurs, the timer stops, and the elapsed time is calculated.
The important part is what happens inside the operating system's network stack when it receives the connection attempt.
When the port is closed.
The operating system immediately rejects the incoming SYN packet by returning a TCP RST packet. The browser then closes the socket and triggers the onerror handler. On a local machine, the entire process will typically take only around 1–5 milliseconds.
When the port is open.
The connection first completes the standard TCP three-way handshake. The browser then sends an HTTP Upgrade request to negotiate a WebSocket connection. Even when the service listening on that port isn't actually a WebSocket server—for example, a database or SSH service—the connection generally isn't rejected immediately. It can fail only after the protocol mismatch is detected. The extra handshake and data exchange increase the response time to roughly 10–50 milliseconds.
When a firewall filters the port.
Instead of rejecting the SYN packet, the firewall silently drops it. The browser receives no response from the operating system and continues waiting until the connection attempt eventually times out. Here, the delay is measured in seconds rather than milliseconds.
Modern browsers deliberately reduce the precision of JavaScript timers, typically bringing it down to approximately 0.1–1 ms. But timing attacks don't require nanosecond-level precision.
The difference between an immediate TCP RST and a slower connection attempt can be measured in tens of milliseconds, which is more than enough to distinguish the two cases reliably.
More sophisticated anti-fraud systems also account for variables such as CPU utilization, JavaScript garbage collection, and operating-system behavior. As a result, they don't necessarily depend on fixed, hardcoded timing thresholds.
Instead, the script can calibrate itself first.
Before checking the ports it is actually interested in, it can make a connection attempt to a random, high-numbered port that is expected to be closed. For example:
ws://127.0.0.1:54321
The resulting response time provides a baseline for what a closed port looks like on that particular machine at that particular moment.
The system can then compare the timing measurements from ports commonly associated with multi-accounting tools against this baseline and look for unusual deviations.
Why anti-fraud systems care about your localhost
The primary objective of an anti-fraud system is to deanonymize browser profiles and detect multi-accounting.
Most anti-detect solutions focus heavily on spoofing browser fingerprints—Canvas, WebGL, fonts, and similar signals—while paying far less attention to the network stack.
Open local ports associated with automation tools become highly reliable indicators that a digital identity has been spoofed.
What scoring algorithms look for
The timing data collected within fractions of a second is sent to the anti-fraud scoring server.
There, the observed port pattern is analyzed. Depending on the results, negative risk points may be added to your profile's overall score.
If port 9222 is open, it's a strong indication that the browser is being controlled through the Chrome DevTools Protocol (CDP) by automation frameworks such as Puppeteer, Playwright, or Selenium.
At that point, mouse movement emulation or simulated human input is unlikely to help. The session may receive a very high risk score, resulting in anything from a CAPTCHA challenge to an outright account ban, depending on how aggressive the anti-fraud system is.
Likewise, if ports such as 22 (SSH), 3306 (MySQL), or ports commonly used by local proxy applications are open, it's difficult to pass as a typical user device.
The anti-fraud system is likely to conclude that it's dealing with a virtual machine, a rented server, or a professional multi-accounting workstation, significantly reducing the trust assigned to the account.
Cross-linking and account farm detection
This may be the biggest concern for anyone operating multiple accounts.
Imagine that you're running 50 browser profiles on a single machine.
Each profile has:
- a unique browser fingerprint;
- its own dedicated proxy;
- a different IP address.
From the perspective of an external website, these profiles can appear to belong to 50 separate users. But underneath those browser sessions, the same operating system or server is still being used.
As a result, the same background services remain active and expose the same collection of open ports. This particular combination can effectively act as a super fingerprint.
An anti-fraud system may notice that dozens of supposedly unrelated users share the exact same—and potentially unusual—local service configuration. From there, it can correlate those profiles, identify them as belonging to a single account farm, and potentially shadow-ban the entire group.
In other words, localhost scanning can allow anti-bot systems to look beyond the browser fingerprint itself and uncover the infrastructure sitting behind your multiple accounts.
How to protect yourself: isolation and firewall configuration
When dealing with sophisticated anti-fraud systems, your defensive strategy should focus on a few key principles:
Isolate the Network Stack
Use virtual machines or containers with properly isolated network environments. When the browser operates inside a fully isolated environment, timing-based probes should only observe the response characteristics of ports available within that environment. Services running directly on the host become inaccessible from inside the isolated environment.
Configure the Firewall Correctly
A firewall shouldn't simply drop every connection attempt—it should reject unwanted connections immediately. In particular, use REJECT, which immediately returns a TCP RST packet, rather than DROP, which silently discards packets and makes the browser wait for a timeout.
A REJECT response behaves more like a naturally closed port and therefore produces timing characteristics that are less distinctive for timing-based detection.
Block Localhost Access Entirely
The most aggressive—and potentially most effective—option is to prevent the browser from accessing localhost altogether. This can neutralize anti-fraud scripts that attempt to probe services running on the local machine.
There is a drawback, though. Some legitimate applications, including software used with hardware security keys or e-signature tools, rely on local agents to communicate with the browser. Preventing that communication can itself look unusual and potentially contribute to a higher risk score.
Therefore, there are two practical approaches:
Fine-Grained Filtering. Allow access only to trusted local services at the firewall or browser-extension level. At the same time, immediately reject unwanted attempts to establish WebSocket connections with unknown local ports.
Use Specialized Tools. Another option is to use software that addresses these issues at the browser-kernel level, such as anti-detect browsers.
For example, Octo Browser includes built-in protection against local port scanning. Octo intercepts any attempts by scripts, including those using the WebSocket API, to access addresses within the 127.0.0.0/8 range or localhost, handling them directly at the browser API level.
Testing protection measures
You can easily see how this works with a simple test.
First, start any local server on your device so that it opens a listening port.
An open port will look something like this.
Next, open any website in your browser and attempt to access the open port.
In a standard Chrome browser, the response arrives with a slight delay. That delay confirms that the port is open.
Perform the same test in Octo Browser, and you'll see that the port appears to be closed.
Octo Browser controls access to your local ports to prevent data leaks. Try it for free with the promo code DEVTO (4 days of Starter subscription for free).
Does Private Network Access (PNA) solve the problem?
Private Network Access (PNA) is a security feature available in modern browsers, particularly Chrome, designed to prevent public websites from sending requests directly to devices or servers on a user's local network.
Modern browsers try to reduce this type of exposure through several mechanisms:
- restricting or completely blocking access to localhost from insecure public contexts;
- requiring a Secure Context (HTTPS) before certain connections can be established;
- performing CORS preflight requests before permitting access to local network resources.
However, developers of anti-fraud systems have adapted to these protections as well. They can use public domains that resolve to 127.0.0.1—for example, yourapp.localhost.com—along with valid HTTPS certificates to get around some of these basic restrictions.
More importantly, PNA does not protect against TCP-level timing attacks.
Even when PNA eventually blocks a request because the required headers aren't present, the browser still has to establish a TCP connection before it can determine whether those headers are acceptable.
If the port is closed, the operating system immediately rejects the connection with a TCP RST packet. If the port is open, the TCP handshake completes successfully. The browser then proceeds with the data exchange, encounters the PNA restrictions, terminates the connection, and only afterward reports the error.
Those additional steps can introduce delays measured in tens of milliseconds(exactly the kind of timing difference that anti-fraud algorithms can use).
In other words, PNA prevents websites from accessing local data, but it doesn't hide the time required to establish a connection.**
Conclusion
Effective anonymity today requires a comprehensive approach.
You can build what appears to be a perfect browser profile, but one overlooked debugging port such as 9222 or an exposed database port can undermine otherwise thorough fingerprint spoofing.
The network stack therefore deserves the same attention as the browser fingerprint itself. Isolate your working environments, configure the firewall carefully, or use solutions that address these types of leaks directly at the browser-kernel level.




Top comments (0)