CLI tool
mozilla/blurts-server avatar
mozilla/blurts-server

blurts-server: the service behind Mozilla Monitor and the Firefox privacy panel

Mozilla Monitor arms you with tools to keep your personal information safe. Find out what hackers already know about you and learn how to stay a step ahead of them.

946 stars317 forksFluentMPL-2.0

At a glance

What is it?
blurts-server is the Next.js service that powers monitor.mozilla.org, ingesting breach notifications from Have I Been Pwned through PubSub and a webhook, storing them in Postgres, and serving a versioned contract to the Firefox privacy panel. It is a Mozilla product repository rather than a general purpose library, and its documentation is written for people joining the team.
Who is it for?
blurts-server is worth reading if you are building a breach notification service or working on Firefox privacy features, because it shows the full shape of the problem: PubSub ingestion, a webhook endpoint, Postgres storage, cron jobs, a localization toolchain and a versioned browser facing contract.
Can I use it commercially?
Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Fluent, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One service, two consumers: the website and the browser

blurts-server is the backend for the monitor.mozilla.org service and website. The summary in the README is the whole product in one line: Firefox Monitor notifies users when their credentials have been compromised in a data breach, and the code here implements that notification service. The repository description puts it more personally, saying the service arms you with tools to keep your personal information safe and helps you find out what hackers already know about you.

The architecture that matters for an engineer is the split between the public website and the browser. The website is a Next.js application served by this repository. The Firefox facing API lives under the path /api/firefox/v1 and is consumed by the Firefox privacy panel, which is the part of Firefox that shows a user their exposure. The repository therefore serves two audiences with different needs: a web application with a localisation and design system on one side, and a machine facing API on the other.

The package manifest confirms the shape. The package is named @mozilla/monitor, marked private, and typed as an ES module. The engines field pins node at 20.20.x and npm at 10.8.x, which is a tight constraint and a hint at how reproducible the deployment needs to be.

The primary language listed for the repository is Fluent, Mozilla's localisation format. That is a signal rather than an oddity: localisation is treated as a first class part of the codebase here, not an afterthought bolted on at the end of a release.

openapi.yml is a contract that is linted, not documentation

The Firefox facing API is specified in openapi.yml at the repository root, and the README is explicit that this file is the contract for the Firefox-facing API under /api/firefox/v1, consumed by the Firefox privacy panel.

The contract is validated with the Redocly CLI, which supports OpenAPI 3.1, and the two commands given are:

sh
npx @redocly/cli@2 lint                              # validate, should report zero problems
npx @redocly/cli@2 build-docs openapi.yml -o /tmp/api.html  # read it as the client team will

The second command is the interesting one. Rendering the specification to HTML and reading it as the browser team would is a cheap way to find out what you actually promised, and the README frames the lint target as the source of truth: it needs no arguments because redocly.yaml names the description, it switches off one rule on purpose with a comment saying why, and anything it reports is worth fixing.

This is a small workflow with a large payoff for a project split across teams. A browser team and a server team can disagree about a field name, and the disagreement is cheaper to resolve against a linted specification than in a bug report. The redocly.yaml file at the repository root holds the lint configuration, and the deliberate rule exception is documented in place rather than left as a mystery.

The same repository keeps the specification and the implementation together, which is the part that many API contracts in the wild do not manage.

Breach data arrives through PubSub and a webhook

Breach data is powered by haveibeenpwned.com, and the README links the Have I Been Pwned about page for the what and why of breach alerts. Ingest is the part of the system with the most moving pieces.

Monitor uses GCP PubSub for processing incoming breach data, and the repository ships a local emulator setup so a developer can work without touching the cloud service. The compose stack provisions a topic named hibp-breaches with a subscription named hibp-cron, which names the flow: a breach arrives on a topic, and a subscription named after a cron job consumes it.

The cron job that pulls breaches into the database is a real script in the manifest, named dev:cron:db-pull-breaches, and there is a sibling for pushing breach data out to Remote Settings, which is how Firefox itself receives the list of known breaches. The alert side has its own script, dev:cron:breach-alerts, which runs the email breach alert job.

The second ingestion path is a direct webhook, and the README gives a curl command that emulates Have I Been Pwned notifying the API about a new breach:

sh
curl -d '{ "breachName": "000webhost", "hashPrefix": "test", "hashSuffixes": ["test"] }' \
  -H "Authorization: Bearer unsafe-default-token-for-dev" \
  http://localhost:6060/api/v1/hibp/notify

The payload shape is worth reading closely. A breach name, a hash prefix, and a list of hash suffixes is a k-anonymity style lookup, so the service can match a credential without receiving the credential itself. The bearer token in the example is literally named unsafe-default-token-for-dev, which tells you the local default is for development only and that the production value is one of the secrets you have to obtain.

Web app, cron jobs and a second tsconfig

The package manifest is where the shape of the codebase becomes clear, and it is worth reading the scripts section closely rather than just the dev and start entries.

The dev script builds the Nimbus types and then runs next dev with turbo on port 6060, and the start script is next start. Between them sits build, which runs build-glean, build-nimbus, the Next.js build and build-cronjobs, and cloudrun, which runs the migrations and then starts the server, which is the deployment entry point for a container platform.

The cron jobs are first class. There are scripts for email breach alerts, for deleting unverified subscribers, for pulling breaches into the database, and for updating breaches in Remote Settings, and they are run through tsx with a dedicated tsconfig, tsconfig.cronjobs.json. That separate config file matters: a cron script is a batch process, not a request handler, and keeping it out of the main TypeScript project configuration keeps its type environment separate from the Next.js build.

Two build steps have to run before anything works. build-glean generates the Glean telemetry files, and the README says it needs to be re-run any time the Glean yaml files are updated. build-nimbus does the same for Nimbus, the experimentation configuration, with a watch variant that rebuilds types when config/nimbus.yaml changes. Between them, the two Mozilla platform services for measurement and for remote configuration are compiled into the application rather than fetched at runtime.

The lint script is a chain, not a single tool: stylelint over the SCSS, a prettier check over src, eslint with max-warnings set to zero, then next typegen and a tsc type check with no emit, and finally a Nimbus validation step. The ordering suggests a project where types and generated configuration are treated as build artefacts that must agree.

Getting a local copy running on port 6060

The setup path in the README is explicit, and it is the fastest way to understand what the service needs. Requirements are Volta for the Node and npm versions, Postgres, Python, and k6 as a load testing tool, with a note recommending Postgres.app on a Mac.

Clone and install first:

sh
git clone https://github.com/mozilla/blurts-server.git
cd blurts-server
sh
npm install

Then copy the environment template, which the README treats as a step to repeat whenever the example file changes:

sh
cp .env.local.example .env.local

Python is needed for the Fluent linter, installed from a requirements file in the .github directory:

sh
pip install -r .github/requirements.txt

OR

pip3 install -r .github/requirements.txt

Next come the generated files. build-glean produces the Glean files and build-nimbus the Nimbus files, and both have to be re-run when their source yaml changes. There is also a one-off step to create location data:

sh
npm run create-location-data

The README explains that this script is only needed manually for local development, and that the data feeds the onboarding exposures scan by autocompleting the City and state input.

Finally, run it. npm start runs a production style build with minified and bundled assets, while npm run dev runs in development mode, and the README says to navigate to localhost:6060 if the server started successfully. The most common failure it warns about is an error reading Required environment variable was not set, which means the values are missing from .env.local, and the documented route to them is the Firefox Monitor 1Password Vault or asking an engineer over Magic-Wormhole.

A local stack that includes Postgres, PubSub and an observability pipeline

The compose file is broader than a typical web app stack, and each service tells you something about how the project is operated.

Postgres runs with the image postgres:latest, port 5432 published, and the logs turned on with a log_statement=all argument, plus a healthcheck that runs pg_isready on a one second interval with a long start period. The init directory is mounted from .docker/postgres, which is where the schema and seed data for a fresh volume come from. The README also documents the manual path: createdb blurts, createdb test-blurts for the tests, a DATABASE_URL in .env.local, and the migrations run with npm run db:migrate.

PubSub is emulated through a google/cloud-sdk image whose entrypoint runs a setup script that creates the topic and subscription from environment variables. That is the emulator path described in the README, and the manual equivalent is to start it with gcloud beta emulators pubsub start and then point the application at it by running the emulator environment init and npm run dev in the same shell.

The rest of the stack is telemetry. An OpenTelemetry collector image, a Grafana Tempo instance for traces, and Prometheus for metrics, with the collector publishing an OTLP HTTP receiver on 4318 and a Prometheus scrape endpoint on 9464. The application image also carries Sentry release and source map upload configuration as build arguments.

Having a collector, a trace backend and a metrics scraper in the local development stack is a strong hint about production expectations. It means a developer can exercise the instrumentation path locally, which is usually a good thing and occasionally an unwelcome amount of setup for a first run.

Container image, localisation and the Firefox coupling

The Dockerfile is small and careful. It starts from node:20.20-alpine, adds Python 3 with pip, and creates a dedicated application user with uid 10001 rather than running as root. Dependencies are installed with npm ci and the audit disabled, the.env file is copied in, and the build arguments for the release identifier, the Sentry DSN, the source map upload flag and the analytics debug mode are declared as build arguments and set as environment variables.

The Glean telemetry build runs during the image build, with the Python and pip executables pointed at the interpreter that was installed for the Fluent linter, which is the reason Python is in the image at all.

That is the localisation story, and it is worth separating out. The primary language of the repository is Fluent, and the localisation artefacts are visible in the layout: l10n.toml, a locales directory, and a locales-pending directory for strings not yet translated. The Fluent linter is installed from the requirements file and run as part of the toolchain, so a broken translation reference fails the build rather than reaching users.

The other coupling worth naming is the hard dependency on Firefox. The API under /api/firefox/v1 exists for the Firefox privacy panel, the breach list is distributed to browsers through Remote Settings. That is what makes this a product codebase rather than a reusable service, and it is the single biggest obstacle to running it as a standalone breach notification tool.

Instead of running the service, calling Have I Been Pwned directly

The realistic alternative to deploying this codebase is not a competing Mozilla project, it is the upstream service. haveibeenpwned.com is the breach data source that Monitor itself consumes, and for a project that only needs to check whether a given email address appears in a known breach, a direct API call is a fraction of the work.

The difference in approach is the whole stack. A direct call gives you a list of breach names and dates for one address, and nothing else. Monitor adds a Postgres database of breaches and exposed accounts, so a lookup can be answered from local storage. It adds the k-anonymity notification webhook, so a new breach is pushed in rather than polled for. It adds email breach alerts through a cron job, and it adds a subscriber model with an unverified subscriber cleanup script. It adds the Remote Settings publication step, which is the mechanism by which Firefox itself learns about a breach. It adds a website with a full localisation pipeline, and a Firefox privacy panel integration.

Those are the reasons to run the service rather than call the API. They are also the reasons it is heavy: a Postgres instance, a PubSub topic and subscription, four cron scripts, a build pipeline for Glean and Nimbus, a Fluent linter and a container image.

So the decision is about which half of the problem you have. Checking a password against known breaches is a solved, one request problem. Telling users what was exposed, keeping that data current, and pushing it into a browser is an operational project, and that is the half blurts-server implements.

MPL-2.0, a dated release train, and what the licence asks of you

The licence field says MPL-2.0, the Mozilla Public License. It is a file level copyleft licence: if you modify a file of the covered source, you must publish your modified version of that file under the same licence, while the larger work around it can stay proprietary. For a service repository, the practical effect is limited, because you are not linking a library into a product so much as running a service. The licence matters more if you lift code out of the repository, for example the localisation tooling or the client code, into a closed source application. That is a licensing observation rather than legal advice.

On maintenance, the repository is not archived and the last push was on 2026-10-19, which is recent. The release history is dated and regular: 2026.10.13, then 2026.09.24, then 2026.09.09, with version strings that are the release date itself. The gap between those dates is short enough to call the release cadence steady, which is consistent with a service under active operational load rather than a library with occasional updates.

The upgrade cost for an outside user is therefore low in the sense that nothing depends on this code, and high in the sense that a fork would have to track a codebase that changes several times a month, with a cron, a build and an API contract that all move together.

Editorial conclusion

blurts-server is worth reading if you are building a breach notification service or working on Firefox privacy features, because it shows the full shape of the problem: PubSub ingestion, a webhook endpoint, Postgres storage, cron jobs, a localization toolchain and a versioned browser facing contract. It is not something to fork and run as your own breach service, because the breach feed itself comes from haveibeenpwned.com and the environment values come from Mozilla's internal vaults. Before using any of it, run npx @redocly/cli@2 lint on openapi.yml to see the contract in its current state, and check that the Node and npm versions in package.json, 20.20.x and 10.8.x, match what your environment provides.

Frequently asked questions

How do I run blurts-server locally?

Clone the repository, run npm install, copy .env.local.example to .env.local, install the Fluent linter with pip install -r .github/requirements.txt, then run npm run build-glean, npm run build-nimbus and npm run create-location-data. Start it with npm run dev and open localhost:6060.

Where does the breach data for Mozilla Monitor come from?

The README states that breach data is powered by haveibeenpwned.com. Incoming data is processed through GCP PubSub, and a webhook at /api/v1/hibp/notify accepts a notification with a breach name, a hash prefix and a list of hash suffixes.

What is the openapi.yml file used for?

It is the contract for the Firefox-facing API under /api/firefox/v1, consumed by the Firefox privacy panel. The README says to validate it with npx @redocly/cli@2 lint, which needs no arguments because redocly.yaml names the description.

How does a developer test the PubSub ingest locally?

The README describes running the emulator with gcloud beta emulators pubsub start --project=your-project-name, or using the compose stack, which provisions a topic named hibp-breaches and a subscription named hibp-cron. In a separate shell you run the emulator environment init followed by npm run dev.

What licence is blurts-server released under?

The licence field on the repository says MPL-2.0, the Mozilla Public License, which applies copyleft at the level of individual files.

Is blurts-server still maintained?

The repository is not archived and the last push was on 2026-10-19. Tagged releases are dated and recent, including 2026.10.13, 2026.09.24 and 2026.09.09.

Official sources

  1. License: MPL-2.0
  2. mozilla/blurts-server on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mozilla-blurts-server.svg)](https://hysenlabs.com/projects/mozilla-blurts-server)

Was this useful?