How Haskell's Type System Could Have Prevented Facebook's 533M Record Breach
Haskell's compile-time type checking makes it structurally impossible to confuse lookup keys with authorization contexts, preventing IDOR vulnerabilities before code runs.
A 2019 Facebook breach exposed 533 million phone numbers through a type confusion vulnerability—where a field intended for lookups was mistakenly used in an authorization context. Haskell's expressive type system catches this entire class of bug at compile time: function signatures explicitly declare what types can flow where, and the compiler rejects any code that violates those constraints. Combined with immutable data structures, Haskell makes data flow auditable without chasing mutations across a large codebase. While Haskell doesn't prevent logic errors or infrastructure misconfigurations, it structurally eliminates the confusion bugs that caused breaches like Facebook's—offering a meaningful trade-off for security-critical systems despite steeper learning curves and a smaller ecosystem.
In 2019, attackers scraped 533 million Facebook phone numbers through an API that exposed user data because a field meant for lookups was also used for returns. The bug wasn't exotic. It was a type confusion: the API accepted queries for phone numbers and handed back user records instead of catching the mismatch. That class of error—data flowing somewhere it shouldn't because the system couldn't enforce what type of thing belonged where—has a name in security circles: an IDOR, or insecure direct object reference. But the deeper cause is often type-system weakness. When the language lets you pass a phone number into a function expecting a user ID and doesn't complain until runtime, you're relying on manual checks to catch what the compiler should have refused. Haskell's type system works differently. A function that accepts a phone number and returns a user record is typed to say exactly that. If you try to pass a phone number into a function expecting an authenticated session token, the compiler rejects it before the code ever runs. This isn't a linter rule or a style guide—it's structural. The type signature makes it physically impossible to confuse a lookup key with an authorization context without the compiler objecting. This extends to data structures. Haskell's algebraic data types let you define exactly what states are valid. A message can be pending, delivered, or read—but not some impossible hybrid of all three. That constraint means the code path that would accidentally expose user data alongside a phone number lookup doesn't exist in any reachable form. Immutability adds another layer. In Haskell, once data is created, it can't be quietly modified by some side effect elsewhere in the codebase. The path from "phone number entered" to "user record returned" is visible in the type signatures and data flow. You can audit it without chasing mutations through a large codebase. None of this makes Haskell invulnerable. Type safety stops a specific category of bug—wrong-type flows, invalid state access, confused identifiers. It doesn't prevent logic errors, dependency vulnerabilities, or misconfigured infrastructure. A Haskell service can still be deployed with bad TLS settings or weak authentication logic. Choosing Haskell means choosing which classes of bugs you will make structurally impossible, versus which you still have to audit and test for manually. For teams evaluating languages for security-critical infrastructure, that distinction matters. Rust and Go both offer memory safety and strong typing. Haskell adds compile-time enforcement of data flow integrity and state validity at a level that catches an entire family of confusion bugs—the same family that burned Facebook. The tradeoffs are real: a steeper learning curve, a smaller ecosystem, harder hiring. Whether those costs are worth the guarantees depends on what the system needs to do and what failure looks like if it goes wrong.