Skip to content

Cybersecurity · 7 July 2026

PII-Free by Design: Enforcing Privacy at the Ingestion Boundary

A privacy claim you can test is worth more than one you assert. Here is how a versioned contract and a content-inspecting PII guard make the ingestion boundary itself the enforcement, and a real bug that discipline caught.

Written by

Pancham Narang

6 min read

"We take privacy seriously" is something every platform says and few can prove. The stronger, testable claim is that the platform never stores raw personal data, because the ingestion boundary rejects it. Privacy here is not a policy document that lives in a wiki; it is a guard that fails a data load. This note is about how that guard works, the contract system behind it, the integration loop it enables, and a real bug it caught before any customer could hit it.

Contracts before data

Nothing lands untyped. Every feed a customer sends, from players and activity to transactions, sessions, the game catalogue, campaigns and optional enrichment feeds, is defined by a versioned contract: a schema that declares each field, its type and the privacy assumptions it must honour. The types do real work rather than merely documenting intent. An identifier is constrained to be opaque and rejects values that look like natural identifiers, so an email, a phone number or a card-shaped string is refused at parse time, because a real identifier for us is a pseudonymous token, never a human-meaningful one.

The same discipline runs through the rest of the schema. Monetary values are integers in minor units, never a formatted, locale-bearing string, which removes an entire class of currency- and locale-parsing bugs downstream. A country is a two-letter code, not free text. Timestamps are UTC instants. The contract is the first filter: a payload that does not fit is rejected with a structured report before a single row is persisted.

Contracts are versioned because feeds evolve. A customer integrated against an earlier version keeps working when a later one adds an optional field, and a breaking change gets a new major version with both accepted during a migration window. That versioning is what lets the privacy guarantees be enforced rather than merely requested; the contract is a machine-checked agreement, not a PDF the customer's engineers skim once.

The PII guard

Contracts constrain shape; the PII guard inspects content. Before any validated batch is written, it is scanned against a set of personal-data classes: IP address, email, phone number, bank account, postal address, government identifier, date of birth, raw geolocation and more. Each class matches on two axes. It matches field names, so a column called email is suspicious regardless of what it currently contains, and it matches value patterns, so a string shaped like an IP address is caught even when it sits in a column innocently named notes. If anything matches, the whole batch is rejected and quarantined, and the report names the offending fields and classes but never the offending values, because echoing detected personal data back inside an error message would itself be a leak.

The principle the guard exists to enforce is simple: send derived, pseudonymised signals, not raw data. Instead of an IP address, a customer sends the connection's country and a customer-derived risk band. Instead of a raw device fingerprint, a salted, opaque device key that links a device across that customer's own sessions but means nothing off-platform and cannot be reversed. Instead of a raw payment identifier, a salted instrument key.

The platform gets the signal it needs for modelling, that this is the same device, that this connection looks risky, that this player pays with a consistent instrument, without ever holding the sensitive value that signal was derived from. That is what PII-free by design means in practice: the useful signal survives, the raw value never enters.

A bug the guard caught

Here is the part that makes me trust the design rather than just believe in it. We added a set of optional enrichment feeds and, as part of a pre-integration readiness pass, wrote a test that pushes one realistic row through each feed end-to-end. The device-signal feed failed immediately, not on the test's own assertion but at the PII guard. Two of its fields carried names beginning with the token for IP address, and the guard's rule matches any field whose name contains that token. The feed, exactly as contracted, would have been rejected on every single load and could never have reached production. It had been registered and reviewed but never actually pushed through the guard with real rows, so the defect was invisible until something exercised the whole path.

The fix was pointedly not to weaken the guard, which was right to be suspicious of such a field name. The field names were the mistake: those columns carry IP-derived signals, a country and a risk band, never the address itself, so we renamed them to describe what they actually are. That both clears the guard and is more honest about the data. The guard stayed strict, the contract got clearer, the downstream schema and design doc were regenerated to match, and the whole thing was caught before a customer ever integrated against the feed.

A strict boundary is only trustworthy if you prove you have actually run data through it. A rule nobody has tested is a rule nobody can rely on.

Self-testing without landing anything

Because the boundary is deliberately strict, integration could be miserable: send a batch, get a rejection, guess at the cause, repeat. So we exposed a dry-run validation endpoint that runs the exact same envelope, PII and contract checks as the real ingest and returns a structured report without storing anything. A stage indicator tells a customer's engineers precisely where they failed, whether at the envelope, the PII guard or a specific contract field, so they are never guessing.

They iterate against it freely until their payloads are clean, then flip the same payloads to the real endpoint with confidence. Privacy enforcement and a pleasant integration experience are not in tension when the identical validation runs in both a "just tell me" and a "now do it" mode.

The takeaway is simple: a privacy claim you can test is worth more than one you assert, and the way you get there is to make the boundary itself the enforcement, then prove, with real rows, that the boundary actually runs.