Skip to content

navigator.connection returns unrealistic values (downlink=10, rtt=0) when launched with HTTP proxy #447

Description

@liberoa
## Description

`navigator.connection.downlink` and `navigator.connection.rtt` return unrealistic values when CloakBrowser is launched with a browser-level HTTP proxy.

With the same Docker container, same Xvfb environment, same proxy, and same test page:

- Stock Chromium reports normal values, e.g. `downlink ~= 1.7`, `rtt ~= 300`
- CloakBrowser reports `downlink = 10`, `rtt = 0`

This makes CloakBrowser distinguishable from stock Chromium on sites that collect Network Information API signals.

## Environment

- CloakBrowser version: `0.4.10`
- Cloak Chromium version: `146.0.7680.177.5`
- OS: Debian GNU/Linux 13 (trixie)
- Runtime: Docker + Xvfb
- Browser mode: headed
- Proxy type: browser-level HTTP proxy
- Proxy example: `--proxy-server=http://host.docker.internal:18081`

## Reproduction

### 1. CloakBrowser with proxy

```python
import asyncio
import tempfile
from cloakbrowser import launch_persistent_context_async

async def main():
    context = await launch_persistent_context_async(
        user_data_dir=tempfile.mkdtemp(prefix="cloak-nqe-"),
        headless=False,
        proxy="http://host.docker.internal:18081",
        args=[
            "--no-sandbox",
            "--fingerprint=123456",
            "--fingerprint-platform=windows",
        ],
    )

    page = context.pages[0] if context.pages else await context.new_page()
    await page.goto("https://browserleaks.com/javascript", wait_until="domcontentloaded")
    await page.wait_for_timeout(3000)

    result = await page.evaluate("""
        () => ({
            userAgent: navigator.userAgent,
            webdriver: navigator.webdriver,
            connection: navigator.connection ? {
                effectiveType: navigator.connection.effectiveType,
                downlink: navigator.connection.downlink,
                rtt: navigator.connection.rtt,
                saveData: navigator.connection.saveData,
                type: navigator.connection.type || null,
            } : null,
        })
    """)

    print(result)
    await context.close()

asyncio.run(main())

Observed result:

{
  "webdriver": false,
  "connection": {
    "effectiveType": "3g",
    "downlink": 10,
    "rtt": 0,
    "saveData": false,
    "type": null
  }
}

2. Stock Chromium with the same proxy

In the same Docker container, using Debian Chromium:

apt-get update
apt-get install -y --no-install-recommends chromium

Then launch stock Chromium with the same proxy:

import asyncio
import json
import os
import subprocess
import tempfile
import urllib.request
from playwright.async_api import async_playwright

async def wait_http(port):
    for _ in range(150):
        try:
            return json.load(urllib.request.urlopen(f"http://127.0.0.1:{port}/json/version", timeout=1))
        except Exception:
            await asyncio.sleep(0.2)
    raise RuntimeError("CDP not ready")

async def main():
    port = 9662
    user_data = tempfile.mkdtemp(prefix="raw-chromium-")
    args = [
        "/usr/bin/chromium",
        f"--remote-debugging-port={port}",
        "--remote-allow-origins=*",
        f"--user-data-dir={user_data}",
        "--no-first-run",
        "--no-default-browser-check",
        "--no-sandbox",
        "--proxy-server=http://host.docker.internal:18081",
        "data:text/html,<html>probe</html>",
    ]

    env = os.environ.copy()
    env["DISPLAY"] = os.environ.get("DISPLAY", ":99")

    proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env)

    try:
        await wait_http(port)

        async with async_playwright() as pw:
            browser = await pw.chromium.connect_over_cdp(f"http://127.0.0.1:{port}")
            context = browser.contexts[0]
            page = context.pages[0] if context.pages else await context.new_page()

            await page.goto("https://browserleaks.com/javascript", wait_until="domcontentloaded")
            await page.wait_for_timeout(3000)

            result = await page.evaluate("""
                () => ({
                    userAgent: navigator.userAgent,
                    webdriver: navigator.webdriver,
                    connection: navigator.connection ? {
                        effectiveType: navigator.connection.effectiveType,
                        downlink: navigator.connection.downlink,
                        rtt: navigator.connection.rtt,
                    } : null,
                })
            """)

            print(result)
            await browser.close()
    finally:
        proc.terminate()

asyncio.run(main())

Observed stock Chromium result:

{
  "webdriver": false,
  "connection": {
    "effectiveType": "3g",
    "downlink": 1.7,
    "rtt": 300
  }
}

Additional Notes

I also captured Chrome NetLog from CloakBrowser.

The underlying Network Quality Estimator appears to have reasonable values, for example:

{
  "name": "NETWORK_QUALITY_CHANGED",
  "params": {
    "downstream_throughput_kbps": 1400,
    "effective_connection_type": "3G",
    "http_rtt_ms": 308,
    "transport_rtt_ms": 125
  }
}

But the JavaScript-exposed Network Information API still returns:

{
  "downlink": 10,
  "rtt": 0
}

So the issue seems to be in the JS-exposed navigator.connection layer, not the underlying network measurement.

Expected Behavior

CloakBrowser should expose Network Information API values that are consistent with stock Chromium under the same proxy/environment, or provide explicit fingerprint flags to control these values.

For example:

{
  "effectiveType": "3g",
  "downlink": 1.5,
  "rtt": 250
}

instead of:

{
  "effectiveType": "3g",
  "downlink": 10,
  "rtt": 0
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions