跳到正文
DEV Community· Nullprint·· 3 小時前AI 評分55

代理伺服器顯示紐約、瀏覽器卻顯示上海:如何用 10 行 Playwright 找出時區洩漏

Your proxy says New York, your browser says Shanghai: finding time zone leaks in 10 lines of Playwright

AI 導讀

文章示範以 Playwright 對照出口 IP 的地理位置與瀏覽器回報時區,檢查代理環境是否出現時區洩漏。檢查應在頁面內進行,因 Node 的 fetch 不會經瀏覽器代理連線,而 Node 時區亦取決於執行裝置;出口 IP 在同一工作階段也可能改變。作者建議每次啟動瀏覽器時按出口 IP 設定時區、語言及地理位置,並記錄 IP 位置與時區以檢查漂移。

正文

You bought a good residential proxy in Texas. Your scraper or your multi-account setup still gets flagged. The IP looks clean, so what gave you away?

Very often it's not the IP. It's the clock.

The check that sites actually run

A website sees two places at once: where your IP address is, and what time zone your browser reports. Most visitors are in the time zone their IP points to. When the two disagree, that alone is a signal.

This isn't a theory. Fingerprint, one of the larger device-intelligence vendors, documents a VPN detection method called timezoneMismatch. It's flagged "when the geolocated IP address of the device does not match the originTimezone from the system settings" (Smart Signals reference). The same docs note that a VPN doesn't change originTimezone, which is why it's useful to them: it tells them where the machine really is.

So a proxy in Dallas plus a browser that says Asia/Shanghai is about as loud as it gets.

Check it yourself in two lines

You don't need a fingerprinting service to see what they see. Inside any Playwright script, ask the page two questions: what time zone does the browser report, and where does the IP resolve?

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ proxy: { server: process.env.PROXY } });
  const page = await browser.newPage();
  await page.goto('about:blank');

  const tz = await page.evaluate(() => Intl.DateTimeFormat().resolvedOptions().timeZone);
  const ip = await page.evaluate(async () => (await fetch('https://ipinfo.io/json')).json());

  console.log({ tz, city: ip.city, region: ip.region, country: ip.country });
  await browser.close();
})();

If tz is America/Chicago and city is Dallas, you're consistent. If tz is your laptop's zone and the city is somewhere else, you've found your leak.

Two things to know about this snippet:

  • Run the checks from inside the page, not from Node. Node's own fetch goes out on your machine's connection, not the browser's proxy, and Node's time zone is your machine's, not the page's.
  • Log the result on every run. The answer can change between runs even when your config doesn't (more on that below).

Three things that bit us this week

We spent the week verifying exits for a set of browser profiles, each on its own proxy. These are the problems that actually showed up, not hypotheticals.

1. Some IP lookup services refuse some IPs. One of our static datacenter-style IPs simply couldn't reach ipinfo; requests failed outright. The fix was a fallback that only returns the IP (https://api.ipify.org?format=json), then resolving the location separately. If your check silently fails, you'll think you're fine when you just didn't measure.

2. "Sticky" doesn't always mean "same city". We pinned a residential session to Dallas. Most checks came back Dallas or a suburb (Euless, Fort Worth). One check, about fifteen minutes in, came back from Appleton, Wisconsin. Same session token, different state. If your time zone was set once at the start, that run would have shipped America/Chicago from a Wisconsin IP: still Central, by luck. With a different pairing you'd be an hour or two off.

3. Suburbs aren't the city, and that's fine. Lookup databases often name the suburb, not the metro. Don't match on city names; check that the region is right and the distance is small. We use "same state and within 80 km of the target city" as our rule.

Fixing it

The rule is simple to say: the time zone and language should follow the exit IP, every time the browser starts. In practice:

  1. Resolve the exit IP from inside the browser at launch.
  2. Set the browser's time zone (and navigator.language / Accept-Language) to match that location.
  3. Re-check after any proxy change, and log the pair (IP location, browser time zone) so you can spot drift later.

In plain Playwright you can set timezoneId and locale when you create a context:

const context = await browser.newContext({ timezoneId: 'America/Chicago', locale: 'en-US' });

The catch is that you have to know the right values before you create the context, and keep them right as proxies rotate.

That's the part we automated in Nullprint: each profile looks up its exit when it launches, sets time zone, language and geolocation to match, and its check-up flags a mismatch before you start working. Whatever tool you use, make sure something is doing this on every launch, not once at setup.

Nullprint check-up: time zone and language aligned to the exit IP
A Nullprint check-up: time zone and language are taken from the exit IP (here America/Chicago, en-US).

A short checklist

Before blaming your proxy provider, check:

  • [ ] Browser time zone matches the IP's region
  • [ ] navigator.language and Accept-Language match the country
  • [ ] WebRTC doesn't expose your real IP (run any WebRTC leak test)
  • [ ] Geolocation, if granted, is near the IP, not your desk
  • [ ] You log the exit IP and city on every run, because sessions drift

Most "my proxy got detected" stories end at the first box.

This post was drafted with AI assistance and reviewed by the Nullprint team.

來源:DEV Community · dev.to