Haven Docs

Blocklist distribution

Gambling operators register new domains constantly, and cycle through numbered mirror domains deliberately to outlast blocklists. A blocker whose rules only change when the app updates is behind from the day it ships.

Haven therefore updates its rules without an app update — and does so in a way that cannot be turned into an attack on the user.

The shape of an update#

A background worker runs roughly every twelve hours, only when a network is available. It asks the backend for everything that changed after the version currently applied, and receives a signed envelope: a payload plus a detached signature and the algorithm identifier.

The client then enforces three properties, in this order:

1. Authenticity#

The signature must verify as ECDSA P-256 / SHA-256 against a public key pinned inside the app. If verification fails, the update is rejected outright.

This is the trust anchor of the whole mechanism. A forged delta, a tampered delta, or a delta from anywhere other than the holder of the backend's private key never reaches the database.

Because the key is pinned in the app, rotating the server's signing key requires shipping a new app version. That is not a limitation, it is the point: it means a compromise of the distribution host alone cannot change what the app blocks.

The algorithm identifier in the envelope is also checked, so an envelope cannot ask the client to verify it under something weaker.

2. Rollback protection#

A delta is applied only if its version is strictly newer than the last applied version.

Replaying an old, correctly-signed delta is therefore useless: an attacker who captured yesterday's update cannot use it to revert today's rules and quietly unblock a set of domains.

3. Bounded authority#

The delta's rule kinds are mapped through an explicit allow-list before anything is written. Two things follow:

  • A delta can add or remove operator domains, wildcards, keywords, encrypted-DNS resolvers and allowlist entries. It cannot touch the user's own personal filters — those live under separate rule kinds that the sync path has no mapping for.
  • A future backend introducing a new rule kind is silently skipped by an older app rather than crashing it.

Only after the rules have actually been applied is the stored version advanced, so an interrupted update is retried rather than being recorded as done.

Failure behaviour#

Everything about this path is designed so that failure is safe:

  • A failed sync retries with backoff.
  • The locally-cached rules keep working in the meantime — the app is offline-first.
  • A rejected signature simply means no update, never a degraded rule set.
  • A user who is never online is still protected by the bundled seed list.

A blocklist update can never make protection weaker than it already was, except through an explicitly signed and strictly newer removal.

The bundled seed#

The app ships with a curated starting list so blocking works on first launch, fully offline, before any sync. It covers well-known operators across several markets, licensed and offshore, plus mirror-domain patterns and keyword tiers.

It is deliberately a starting point, kept curated and conservative, because every entry ships in the binary and cannot be revoked without an app update. The live product is driven by the synced rules.

The seed also contains allowlist entries, including for the official self-exclusion register — a site whose name would otherwise be caught by a keyword rule, and the last site anyone should be blocked from reaching.

Manual sync#

The Guard screen has a "sync now" action that runs the same path and reports the outcome (updated, already up to date, or the failure detail) with the resulting version number.

It exists so the mechanism is verifiable by the user rather than being an invisible promise. A completed sync also counts as a security check and updates the "last checked" line on the home screen.