惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
AI
AI
博客园 - 三生石上(FineUI控件)
腾讯CDC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Y
Y Combinator Blog
大猫的无限游戏
大猫的无限游戏
H
Hackread – Cybersecurity News, Data Breaches, AI and More
雷峰网
雷峰网
NISL@THU
NISL@THU
S
Schneier on Security
T
Threatpost
T
Tenable Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
IT之家
IT之家
宝玉的分享
宝玉的分享
T
Tailwind CSS Blog
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy & Cybersecurity Law Blog
I
Intezer
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
T
Threat Research - Cisco Blogs
SecWiki News
SecWiki News
AWS News Blog
AWS News Blog
博客园 - Franky
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Proofpoint News Feed
V
V2EX
Recorded Future
Recorded Future
Microsoft Security Blog
Microsoft Security Blog
S
Secure Thoughts
Google DeepMind News
Google DeepMind News
MongoDB | Blog
MongoDB | Blog
Apple Machine Learning Research
Apple Machine Learning Research
Project Zero
Project Zero
PCI Perspectives
PCI Perspectives
G
GRAHAM CLULEY
Help Net Security
Help Net Security
Cloudbric
Cloudbric
Recent Announcements
Recent Announcements
V
Visual Studio Blog
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
C
CERT Recently Published Vulnerability Notes
The Cloudflare Blog
Forbes - Security
Forbes - Security
C
Cisco Blogs
O
OpenAI News
www.infosecurity-magazine.com
www.infosecurity-magazine.com

Hacker News

Introducing Claude Opus 4.7 Qwen Studio The Future of Everything is Lies, I Guess: Where Do We Go From Here? GitHub - SeanFDZ/macmind: Single-layer transformer in HyperTalk for the classic Macintosh Show HN: Agent-cache – Multi-tier LLM/tool/session caching for Valkey and Redis Moving a large-scale metrics pipeline from StatsD to OpenTelemetry / Prometheus GitHub - Nightmare-Eclipse/RedSun: The Red Sun vulnerability repository GitHub - SethPyle376/hiraeth: Local AWS emulator focused on fast integration testing, with SQS support, SQLite-backed state, and a debug-friendly web UI. GitHub - macOS26/Agent: Any AI, replaces Claude Code, Cursor, OpenClaw. Over 18 LLM providers (Claude, OpenAI, Gemini, Ollama, Zai, HF, Qwen) wired into a native Mac app that writes code, builds Xcode projects, bumps versions, manages git, automates Safari, use AppleScript, JS or Accessibility, extend Agent! w/ MCP Servers, run tasks from your iPhone via Messages. YouTube now lets you turn off Shorts I Made a Terminal Pager Burgers | マクドナルド公式 Commands — HackerNews CLI documentation ChatGPT for Excel PiCore - Raspberry Pi Port of Tiny Core Linux Live Nation illegally monopolized ticketing market, jury finds Google Broke Its Promise to Me. Now ICE Has My Data. Founding Engineer at Adaptional | Y Combinator CRISPR takes important step toward silencing Down syndrome’s extra chromosome GitHub - saffron-health/libretto: The AI toolkit for building reliable browser automations US v. Heppner (S.D.N.Y. 2026) no attorney-client privilege for AI chats [pdf] Unexpected €54k billing spike in 13 hours: Firebase browser key without API restrictions used for Gemini requests Retrofitting JIT Compilers into C Interpreters IPv6 – Google The Accursèd Alphabetical Clock Cybersecurity Looks Like Proof of Work Now Fragments: April 14 Cal.com Goes Closed Source: Why AI Security Is Forcing Our Decision | Cal.com - Scheduling Software for Online Bookings Laravel raised money and now injects ads directly into your agent When moving fast, talking is the first thing to break Too much Discussion of the XOR swap trick – Heather Cafe Introduction to Spherical Harmonics for Graphics Programmers The Grand Line Building a Z-Machine in the worst possible language High-Level Rust: Getting 80% of the Benefits with 20% of the Pain GitHub - duguyue100/midnight-captain: Inspired by Midnight Commander, tailored to my taste. How to build a `git diff` driver · Jamie Tanna | Software Engineer Center for Responsible, Decentralized Intelligence at Berkeley The Local Universe’s Expansion Rate Is Clearer Than Ever, but Still Doesn’t Add Up - A new synthesis of astronomical measurements confirms a persistent mismatch that could point to physics beyond current models The air throughout our homes is infused with microplastics. But there are things you can do to breathe less of them The disturbing white paper Red Hat is trying to erase from the internet – OSnews The Future of Everything is Lies, I Guess: Annoyances ‘Abhorrent’: the inside story of the Polymarket gamblers betting millions on war Productive procrastination — Max van IJsselmuiden maps, territory and LMs 447 Terabytes per Square Centimetre at Zero Retention Energy: Non-Volatile Memory at the Atomic Scale on Fluorographane Show HN: Pardonned.com – A searchable database of US Pardons 20 Years on AWS and Never Not My Job The Seasons are Wrong Artemis II crew splashes down near San Diego after historic moon mission We gave an AI a 3 year retail lease in SF and asked it to make a profit | Andon Labs How a dancer with ALS used brainwaves to perform live On filing the corners off my MacBooks Installing every* Firefox extension OpenClaw’s memory is unreliable, and you don’t know when it will break Steve Blank Nowhere Is Safe Chimpanzees in Uganda locked in vicious 'civil war', say researchers watgo - a WebAssembly Toolkit for Go linux/Documentation/process/coding-assistants.rst at master · torvalds/linux GitHub - callumlocke/json-formatter: Makes JSON easy to read. Founding Product Engineer at Bild AI | Y Combinator A compelling title that is cryptic enough to get you to take action on it GitHub - Keychron/Keychron-Keyboards-Hardware-Design: Industrial design files for Keychron keyboards and mice. 100+ models with CAD assets in STEP, DXF, DWG, and PDF. Source-available, with commercial use allowed for original compatible accessories within the license terms. [ANNOUNCE] WireGuardNT v0.11 and WireGuard for Windows v0.6 Released 1D-Chess Helium Is Hard to Replace Cooperative Vectors Introduction | Evolve Keeping a Postgres queue healthy — PlanetScale Our response to the Axios developer tool compromise Do Americans read print books, e-books or audiobooks more? The Zettelkasten Method in Obsidian: A Practical Setup Guide Artemis II Is Competency Porn and We Are Starving For It WeakC4 Flight Viz — Cockpit View A Mexican surveillance giant you’ve never heard of is now watching the U.S. border Surelock: Deadlock-Free Mutexes for Rust RISC-V 101 – what is it and what does it mean for Canonical? | Ubuntu The Problem That Built an Industry How Much Linear Memory Access Is Enough? | Solidean Investigating Split Locks on x86-64 Simplest hash functions Sybilproof reputation mechanisms (2005) [pdf] What is a property? How Complex is my Code? Static code analysis in Kotlin — tools overview Toffoli gates are all you need PGLite evangelism dcmake: a new CMake debugger UI Clojure on Fennel part one: Persistent Data Structures Fragments: April 2 Python Release Python install manager 26.1 The Life and Death of the Book Review - Liberties Introducing Database Traffic Control — PlanetScale Bitcoin miners are losing $19,000 on every BTC produced as difficulty drops 7.8% God sleeps in the minerals Building slogbox Apple Silicon and Virtual Machines: Beating the 2 VM Limit Who was “Not Even Wrong” first? Pokemon Evolution Vs Darwinian Evolution The APL Programming Language Source Code
Mullvad exit IPs as a fingerprinting vector
2026-05-15 · via Hacker News

Mullvad is one of the few VPN providers that offers multiple exit IPs for its servers. If two people connect to the same server, they will usually end up with different public IPs.

With only 578 servers (compared to Proton VPN’s 20,000), this kind of vertical scaling makes sense to avoid cramming too many users onto one IP, which would be a problem on sites with overzealous IP blocks and ratelimits.

Surprisingly, the exit IP you are given is not randomized each time you connect to the server, but deterministically picked based on your WireGuard key, which rotates every 1 to 30 days (unless you use a third-party client, in which case it never rotates).

But wait.. if each server assigns you an independently picked static exit IP, wouldn’t just a few of those be enough to uniquely identify you among every other Mullvad user?

Putting it to the test

I wrote a script that repeatedly changes my pubkey and fetches exit IPs for a set of 9 servers. Leaving it running for a night produced data points for 3650 pubkeys, which is enough to map out the exit IP range for each server:

Hostname Start IP End IP # IPs
au-syd-wg-101 103.136.147.5 103.136.147.64 60
cl-scl-wg-001 149.88.104.4 149.88.104.14 11
de-ber-wg-007 193.32.248.245 193.32.248.252 8
dk-cph-wg-002 45.129.56.196 45.129.56.226 31
fi-hel-wg-201 185.65.133.10 185.65.133.75 66
us-lax-wg-001 23.234.72.36 23.234.72.126 91
us-nyc-wg-602 146.70.168.132 146.70.168.190 59
us-sjc-wg-302 142.147.89.212 142.147.89.224 13
za-jnb-wg-002 154.47.30.145 154.47.30.155 11

The pool sizes add up to over 8.2 trillion exit IP combinations for these servers, so you’d think each pubkey would be assigned a unique combination of IPs since the odds of a collision are so astronomicaly low. And yet, somehow all the pubkeys I tested were assigned just one of 284 combinations.

What’s going on here?

Different IPs, same proportion

You can calculate a numerical position for an exit IP by counting its distance from the pool’s starting IP.

For example, the IP 103.136.147.53 assigned by au-syd-wg-101 would have a 1-based index of 49 (X.X.X.53 - X.X.X.5 + 1).

Now, if you take the IP positions for any of the 284 combinations linked above, and you divide them by pool size, a common ratio emerges:

Server IP Position Pool size Ratio
au-syd-wg-101 103.136.147.53 49 60 0.816
cl-scl-wg-001 149.88.104.12 9 11 0.818
de-ber-wg-007 193.32.248.251 7 8 0.875
dk-cph-wg-002 45.129.56.220 25 31 0.806
fi-hel-wg-201 185.65.133.63 54 66 0.818
us-lax-wg-001 23.234.72.109 74 91 0.813
us-nyc-wg-602 146.70.168.179 48 59 0.813
us-sjc-wg-302 142.147.89.222 11 13 0.846
za-jnb-wg-002 154.47.30.153 9 11 0.818

Each IP lands within the same percentile of its pool, in this case, the 81st.

This explains the limited number of combinations, Mullvad will only assign neighboring exit IPs across all its servers. But why?

Feature or bug?

Curiously, the servers cl-scl-wg-001 and za-jnb-wg-002 consistently share IP indexes with each other across all 284 observed IP combinations.

The thing they have in common is a pool size of 11, and this gives us a clue about what’s happening.

In any language, if you initiate an RNG with a static seed, a rand-between call with the same bounds will always produce the same result:

use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;

fn main() {
    let seed = 1234;
    for _ in 1..100 {
        let mut rng = StdRng::seed_from_u64(seed);
        let number = rng.random_range(0..1000);
        println!("{}", number) // will always print 56
    }
}

So, the shared indexes between these two servers indicate that Mullvad is probably using some sort of seed-based RNG to pick exit IP indexes, where the upper bound parameter is the pool size.

This is fairly straightforward, but what happens when the bounds are changed?

use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;

fn main() {
    let seed = 12345;
    for bound in 10..100 {
        let mut rng = StdRng::seed_from_u64(seed);
        let number = rng.random_range(0..bound);
        let ratio = number as f64 / bound as f64;
        println!("{} {:.3} ", number, ratio)
    }
}
5 0.500 
5 0.455 
6 0.500 
6 0.462 
7 0.500 
7 0.467 
8 0.500 
9 0.529 
9 0.500 
10 0.526 
10 0.500 
11 0.524 
11 0.500 
12 0.522 
12 0.500 
13 0.520 
13 0.500 
14 0.519 
14 0.500 
15 0.517
...

As it turns out, the entropy pool of the RNG is unaffected by the bounds you provide, and at least in Rust, the same float is generated on each first call and used as a multiplier scale for to the bounds, like so: min + round((max - min) * float) (this may be a giant oversimplification)

This lines up with the behavior we’ve seen in Mullvad’s exit IP picking algorithm, so it’s safe to say that this is the cause of it.

Rust as the backend language makes sense too, considering that the client is also written in it.

The thing is, almost none of my programmer friends were able to accurately describe what random_range would produce in the second code snippet, and the actual behavior took me by surprise too. It’s reasonable to think that each increment to the bounds would skew with the entropy and result in a different number, even though that’s not what happens.

Is it possible that the Mullvad devs shared this common misconception, while actually intending for there to be an unbounded number of exit IP combinations? I don’t know, but it’s a funny thought.

Correlating identities

I made a tool that can deduce the minimum and maximum float value for a given combination of IPs, available at https://tmctmt.github.io/mullvad-seed-estimator/.

This particular set of IPs in the screenshot resolves to a float value between 0.2909 and 0.2943 for a difference of 0.0034, which means that 0.34% of Mullvad users share these IPs. At a ball park estimate of a 100,000 active Mullvad users, this equates to 340 users.

This is definitely not as unique as I originally thought, but at the same time, >99% accuracy is really not that bad?

As an example, imagine that you are a moderator on a forum and you suspect that a new face is actually a sockpuppet of a user you banned the day prior. You check the IP logs, and despite using different Mullvad servers, both accounts resolve to the overlapping float ranges 0.4334 - 0.4428 and 0.4358 - 0.4423. This gives you a >99% chance that they are the same person.

Now apply this to IP logs obtained through data breaches and legal channels and you can see how you could get deanonymized behind a VPN through similar correlation attacks.

Protecting yourself

  • Avoid switching servers more than once per pubkey
  • Force rotate your pubkey by logging out of the Mullvad app