5 minute read

Is it possible ? Yes. How ?

Because in this case… timing is everything.

Background

The Pop-Up

Have you seen this prompt recently?

If you use a modern browser, there’s a good chance you’ve either seen it, or you’re about to.

This dialog was introduced as part of Local Network Access (LNA). The premise is straightforward. Public websites should not be able to silently reach into your local network and talk to private resources, such as your router admin panel, your local dev server, your NAS, a Raspberry Pi on 192.168.x.x. LNA draws a hard boundary between public IP space and private/loopback address space, and requires explicit user permission before a cross-boundary fetch is allowed to proceed.

What LNA is meant to do

Under the hood, this boundary is enforced by a function such as URLLoaderThrottle (in Chrome and Chromium). When a public origin tries to access a of a less public IP the following sequence plays out:

  • Request initiated
    fetch() or XHR call is made to an internal IP or localhost
  • IP resolves
    Hostname resolved; address classified as private or loopback
  • LocalNetworkAccessCheck triggers
    A TCP connection is opened to the target IP:Port to determine reachability
  • Request deferred — user prompted
    IPC message sent to browser process; prompt appears if port is open
  • User decides
    Allow, Block, or ignore the prompt
  • Request resumes or fails
    Preflight sent on Allow; CORS error on Block; fetch remains Pending if ignored

Importantly : The fetch promise remains in a pending state throughout the entire pause. From the page’s perspective, the request is simply waiting. From a timing perspective, that wait is information.

LNA enabled browsers

As of February 2026, LNA is available on the following browsers :

Browser LNA Support Version Status — Feb 2026
Chrome / Chromium Supported Chrome 142+  ·  Sept 2025 Full LNA enforcement
Microsoft Edge Supported Edge 142+  ·  2025 Full LNA enforcement
Firefox In trials Nightly only In development
Safari Not supported N/A Partial / different model

The Finding

Port Scanning

Here is the crux of the finding as part of this research. To decide whether to show you the prompt at all, the browser first has to check whether the private target’s port is actually reachable. It does this by opening a TCP connection, before any permission is granted or denied.

The implication is quiet but significant. The port state has already been determined before the user has taken any action :

  • Port Closed : the TCP handshake gets an RST almost instantly, and the fetch rejects in milliseconds. No LNA prompt is required.
  • Port Open : the TCP handshake succeeds, the browser defers the request, the prompt appears, and the fetch sits in a pending state for as long as the user takes to decide, or indefinitely if they ignore it.
%%{init: {'theme': 'dark', 'themeVariables': {'fontFamily': 'Share Tech, sans-serif', 'fontSize': '16px'}}}%% graph TD; id(Page Loaded) --> id1(Internal Resource Access Initiated) id1 --> id2(Port Probed) id2 -->|Port Open| id3(User Prompted) id2 -->|Port Closed| id4(Preflight fails - Immediately) id3 -->|Allow| id5(Preflight Sent) id3 -->|Deny| id6(Preflight fails CORS Error)

This creates a trivially observable timing differential:

Request Duration — Observed from Page Context
Port OPEN
pending... (LNA prompt shown, awaiting user)
>> ms
Port CLOSED
RST — rejected instantly
< 5ms
0
~5ms
250ms
500ms
750ms
≥1s+
Any response beyond a few milliseconds indicates an open port — the 2s abort timeout used in the PoC is a practical scan cutoff, not the detection threshold.

The malicious page does not need the user to click Allow or Block. The timing delta between an immediate rejection and a prolonged pending state is sufficient to fingerprint port state.

State of LNA Prompt vs. Port State

Prompt State Port State
"Allow"Port can be probed
"Block"Port cannot be probed
Pending promptPort can be probed

Based on this its safe to say that LNA may be intended to protect knowledge of such port states when the user “Blocks” LNA.

Proof of Concept

Basic Test

Start a local HTTP server on any port :

python3 -m http.server 30000

Open a browser’s developer console from any public-origin page. Paste the following. Importantlly, when the LNA prompt appears, do not click Allow or Block. Simply observe the console output.

// Target port to probe
const port = 30000;

// Allows us to cancel the request after a timeout
const c = new AbortController();

// Record the start time
const t = Date.now();

// Abort the request after 2 seconds
setTimeout(() => c.abort(), 2000);

// If the request times out (AbortError), the port is open — closed ports fail instantly
fetch(`http://localhost:${port}`, { mode: "no-cors", signal: c.signal })
  .catch(e => console.log(e.name === "AbortError" ? `open (${Date.now() - t}ms)` : "closed"));

Scaling to the Full Port Range

An optimized version can sweep the full 65,535 TCP port range using batched, concurrent fetches with tuned abort timing can be found here :

Source

Live demo

An example run:

Implications

Port scanning via browsers is not new. What LNA changes is the quality of the signal. The LNA probe is a deliberate TCP handshake with a binary outcome, producing a clean, reliable timing split.

Browser & User Fingerprinting

Different OS and software configurations expose different port signatures. Combined with existing fingerprinting signals, a port map assembled in seconds meaningfully contributes to cross-session tracking.

Internal Network Reconnaissance

The graver scenario is enterprise. An employee whose browser bridges the public internet and an internal network is an ideal unwitting proxy. A malicious page can use LNA timing to probe RFC 1918 ranges to map live hosts and open services, with no installation, no privileges, and no interaction beyond a page load.

The Prompt Is Not the Defence

It is worth stating directly: LNA’s prompt was never intended to prevent port discovery. It was intended to prevent unauthorized data exchange with local resources. The port-state leakage described here is a side-effect of the mechanism used to decide whether to show the prompt, and not a flaw in the prompt itself.

Summary

LNA’s TCP probe, the mechanism that decides whether to show the prompt, runs before the user sees anything. That probe leaks port state. Open ports stall, closed ports reject instantly. The delta is measurable from JavaScript with no special permissions.

Allow, Block, or ignore : it doesn’t matter. By the time the prompt appears, the scan is already done.