Skip to main content

SheriffMark

License: AGPL v3

Open-source brand-protection monitoring: watches for newly created domains resembling your brand (typosquats, lookalikes, combosquats) and alerts you, with evidence to support enforcement action. Self-host it on your own infrastructure — any cloud, or none at all.

This project is open source (AGPL-3.0) and cloud-agnostic by design: containers, SQLite by default (Postgres opt-in), SMTP, and configurable auth — built-in email/password by default, plus optional external OIDC (Supabase Auth, Keycloak, Authentik, Auth0, Zitadel, ...) and SAML 2.0 SSO, any combination enabled per deployment.

Screenshots

Brand list Findings table with risk scores and a blocklist flag
Finding detail: registration, abuse contact, and the incident timeline

Quickstart

The fastest way to try it: package it once, then run one command.

git clone https://github.com/arunprasad/sheriffmark.git
cd sheriffmark
pip install -r requirements-dev.txt   # gets `build`, for the step below
./scripts/build.sh                    # builds the frontend, bundles it into a wheel
pipx install dist/sheriffmark-*.whl   # or: pip install dist/sheriffmark-*.whl
sheriffmark serve

That's one process on one port (http://127.0.0.1:8000 by default — --host 0.0.0.0 to accept non-local connections) serving both the API and the built UI, writing to a SQLite file in your current directory. No Docker, no Postgres, no separate frontend dev server. sheriffmark --help lists the rest (migrate, worker, account create-account/ reset-password).

Once a release is published, this collapses further to pipx install sheriffmark with no clone or build step at all — see CONTRIBUTING.md for the maintainer-side publish step.

Prefer Docker, or need Postgres? See "Self-hosting" below.

Layout

core/           — pure detection logic (variant generation, RDAP/DNS/CT
                  checks, risk scoring). Zero cloud or tenant awareness.
adapters/       — storage/notifier implementations behind core/'s interfaces
shared/         — DB models, config, cross-cutting logic (e.g. plan limits)
                  used by both web/ and worker/
web/api/        — FastAPI app (JSON API + serves the built frontend)
web/frontend/   — React + shadcn/ui, Vite-built to static assets
worker/         — daily batch pipeline entrypoint
migrations/     — Alembic schema migrations
sheriffmark/    — the `sheriffmark` CLI (pip/pipx entry point) — thin
                  wrappers around the packages above, see Quickstart
scripts/        — build.sh: bundles the frontend + Python package into
                  one installable wheel

Self-hosting

Everything here runs as plain Docker containers — no dependency on any specific cloud provider. Two deployables:

  • worker/ — a scheduled batch job (cron, Cloud Run Job, ECS Scheduled Task, a VPS crontab — anything that can run a container on a schedule). Does the actual detection work and sends email digests.
  • web/ — a long-running HTTP service (the dashboard + API). Auth works out of the box (built-in email/password, AUTH_ENABLE_LOCAL=true by default) — no external provider required. See .env.example for AUTH_ENABLE_OIDC/AUTH_ENABLE_SAML if you'd rather point at one.

Both need DATABASE_URL and, for worker/, SMTP credentials for sending digests. See .env.example for the full list.

Database: SQLite by default, Postgres when you need it. Unset DATABASE_URL and the app writes to a single SQLite file — nothing else to run. This covers the common case (web/ and worker/ on the same host, sharing a mounted volume) with zero extra services. Switch to Postgres (DATABASE_URL=postgresql+psycopg://..., driver already baked into both Dockerfiles) once you actually need it:

  • Real concurrent write load (many brands, frequent scans).
  • web/ and worker/ run as separate containers that don't share a filesystem — e.g. web/ on Cloud Run and worker/ as an ECS Scheduled Task. This is the one case SQLite can't cover at all: it's a single file, so both processes need to see the same file, which only a shared volume/host mount guarantees.

Either way it's a connection-string change, not a code change.

No built-in "forgot password" flow yet — if you lose an account's password, or want to bootstrap the first account without going through the signup form, use the admin CLI directly against the database (no running server needed):

python -m web.api.manage create-account you@example.com
python -m web.api.manage reset-password you@example.com

Both prompt for the password interactively. This is also the account- recovery path for a self-hosted deployment: if you can reach DATABASE_URL, you can always recover an account this way.

Multi-tenancy is built in (useful if you're self-hosting this to watch brands for multiple clients, e.g. an agency) but there's no billing or plan-limit gating in this open-source build — usage is unlimited by default.

Local development

Nothing to run except Python — DATABASE_URL defaults to a SQLite file in the repo root:

cp .env.example .env
pip install -r requirements-dev.txt
alembic upgrade head
python -m worker.seed   # optional: one tenant + one brand for manual testing
pytest -q

To run the worker for real: python -m worker.main (needs SMTP env vars set in .env to actually send a digest; without them, findings still get written to the DB, only the send step will fail).

Prefer to develop against Postgres instead (matches production more closely if that's what you'll deploy)? Uncomment DATABASE_URL in .env, pip install -r requirements-postgres.txt, then:

docker compose up -d postgres
alembic upgrade head
pytest -q

Frontend:

cd web/frontend
npm install
cp .env.example .env.local   # just VITE_API_BASE_URL for local dev — auth is all backend-side
npm run dev

See CONTRIBUTING.md for the fuller dev workflow and this project's design principles.

Running the worker in Docker

With the SQLite default, web/ and worker/ just need to share a mounted directory:

mkdir -p data
alembic upgrade head   # from the host — writes ./sheriffmark.db
cp sheriffmark.db data/

docker build -f worker/Dockerfile -t domain-watch-worker:latest .
docker run --rm \
  -v "$(pwd)/data:/data" \
  -e DATABASE_URL="sqlite:////data/sheriffmark.db" \
  domain-watch-worker:latest

Run web/'s image the same way (-v "$(pwd)/data:/data" -e DATABASE_URL=sqlite:////data/sheriffmark.db) so both containers see the same file.

Using Postgres instead:

docker compose up -d postgres
alembic upgrade head   # from the host, against localhost:5432
python -m worker.seed

docker build -f worker/Dockerfile -t domain-watch-worker:latest .
docker run --rm \
  --network <project-dir-name>_default \
  -e DATABASE_URL="postgresql+psycopg://watch:watch@postgres:5432/watch" \
  domain-watch-worker:latest

Note the container reaches Postgres via the compose service name (postgres), not localhostlocalhost inside a container means the container itself. Find the actual network name with docker network ls if <project-dir-name>_default doesn't match.

worker/main.py handles SIGTERM/SIGINT gracefully — docker stop (and most schedulers' cancellation signal) lets it wind down at the next safe point rather than force-killing it.

Both worker/ and web/ have also been deployed and verified live on Google Cloud Run, purely as one example target — nothing in either Dockerfile is GCP-specific.

License

AGPL-3.0. Contributions welcome — see CONTRIBUTING.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sheriffmark-0.1.0.tar.gz (734.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sheriffmark-0.1.0-py3-none-any.whl (357.8 kB view details)

Uploaded Python 3

File details

Details for the file sheriffmark-0.1.0.tar.gz.

File metadata

  • Download URL: sheriffmark-0.1.0.tar.gz
  • Upload date:
  • Size: 734.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for sheriffmark-0.1.0.tar.gz
Algorithm Hash digest
SHA256 516560a77b277ecd26980f0f283956f2fd8b72e0eaa4ad8084d82906af35073d
MD5 9bbc293992dbd9630be21805231945de
BLAKE2b-256 f8142b5064dd32032b2a80809eba1294401d18bf3864eee85d6d836d99d9f6c8

See more details on using hashes here.

Provenance

The following attestation bundles were made for sheriffmark-0.1.0.tar.gz:

Publisher: release.yml on arunprasad/sheriffmark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sheriffmark-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: sheriffmark-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 357.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for sheriffmark-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5e7fc1210ca4750613234882aa768b67ffaa6a2ceb7f59ed1f055286e512bca0
MD5 1b149ffe8a8b5e066a28630d5cdfe2f1
BLAKE2b-256 0ba56515229adf84e6c39584737c0d9a6c58832414642107fba674733565e262

See more details on using hashes here.

Provenance

The following attestation bundles were made for sheriffmark-0.1.0-py3-none-any.whl:

Publisher: release.yml on arunprasad/sheriffmark

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page