{"id":49595679,"url":"https://github.com/francesco149/dmarc-analyzer","last_synced_at":"2026-05-04T04:05:12.490Z","repository":{"id":348528808,"uuid":"1184030283","full_name":"Francesco149/dmarc-analyzer","owner":"Francesco149","description":"Automatically scan and analyze DMARC aggregate reports, for quick visual inspection","archived":false,"fork":false,"pushed_at":"2026-04-01T13:59:03.000Z","size":314,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-01T15:34:56.859Z","etag":null,"topics":["email","nixos","nixos-flake","nixos-module","nixos-service","vibe-coded"],"latest_commit_sha":null,"homepage":"https://github.com/Francesco149/dmarc-analyzer?tab=readme-ov-file#dmarc-analyzer","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Francesco149.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-17T07:19:26.000Z","updated_at":"2026-04-01T13:59:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Francesco149/dmarc-analyzer","commit_stats":null,"previous_names":["francesco149/dmarc-analyzer"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/Francesco149/dmarc-analyzer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Francesco149%2Fdmarc-analyzer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Francesco149%2Fdmarc-analyzer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Francesco149%2Fdmarc-analyzer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Francesco149%2Fdmarc-analyzer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Francesco149","download_url":"https://codeload.github.com/Francesco149/dmarc-analyzer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Francesco149%2Fdmarc-analyzer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32593955,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"online","status_checked_at":"2026-05-04T02:00:06.625Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["email","nixos","nixos-flake","nixos-module","nixos-service","vibe-coded"],"created_at":"2026-05-04T04:04:59.278Z","updated_at":"2026-05-04T04:05:12.466Z","avatar_url":"https://github.com/Francesco149.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dmarc-analyzer\n\n\u003e [!WARNING]\n\u003e Vibe coded — use at your own risk.\n\u003e\n\u003e This is an ad hoc tool I put together for my own setup. It is not robust, not\n\u003e production-hardened, and not intended for general use. It works for me. It may\n\u003e not work for you, may break unexpectedly, and comes with no guarantees\n\u003e whatsoever. If you use it, you're on your own.\n\nA self-hosted DMARC aggregate report analyzer. Scans a Maildir for DMARC report\nemails, extracts and parses the XML attachments, and serves a small web UI\nshowing a feed of reports with pass/fail breakdowns per sending IP.\n\n![Screenshot](ss.png)\n\n---\n\n## How it works\n\nThree moving parts:\n\n- **`dmarc-scanner.py`** — a Python script that scans a Maildir, extracts DMARC\n  zip/xml attachments from matching emails, parses the XML, and writes a\n  `reports.json` feed file. Runs as a systemd oneshot on a timer.\n- **`dmarc-server.py`** — a minimal stdlib Python HTTP server that serves the\n  frontend HTML and the `reports.json` feed on `localhost:PORT`. Intended to sit\n  behind a reverse proxy.\n- **`src/`** — the frontend source. `build.py` combines `index.html`,\n  `style.css`, `app.js`, inlined fonts, and bundled JSZip into a single\n  self-contained `dmarc-feed.html`.\n\n---\n\n## Project layout\n\n```text\ndmarc-analyzer/\n├── build.py              # assembles dmarc-feed.html from src/\n├── dmarc-feed.html       # built output — committed, used by the flake\n├── src/\n│   ├── index.html        # HTML shell with {{PLACEHOLDER}} tokens\n│   ├── style.css\n│   └── app.js\n├── package.json          # jszip + fontsource deps for build\n├── flake.nix\n├── module.nix            # NixOS module\n├── packages.nix          # packages\n├── dmarc-scanner.py\n└── dmarc-server.py\n```\n\n---\n\n## Building the frontend\n\nOne-time setup:\n\n```bash\nnpm install\n```\n\nAfter editing anything in `src/`:\n\n```bash\npython3 build.py\n```\n\nOr use the dev shell:\n\n```bash\nnix develop\npython3 build.py\n```\n\n---\n\n## NixOS module\n\nAdd the flake input to your system flake:\n\n```nix\ninputs.dmarc.url = \"github:Francesco149/dmarc-analyzer\";\n```\n\nImport the module and configure:\n\n```nix\nimports = [ inputs.dmarc.nixosModules.dmarc-analyzer ];\n\nservices.dmarc-analyzer = {\n  enable        = true;\n  mailDir       = \"/var/vmail/example.com/postmaster/mail\";\n  scanUser      = config.mailserver.vmailUserName;\n  port          = 8741;\n\n  # Default is 127.0.0.1. Set to a LAN IP if your reverse proxy\n  # is on a different machine. Restrict with a firewall rule.\n  listenHost    = \"0.0.0.0\";\n\n  scanInterval  = \"15min\";  # systemd OnUnitActiveSec format\n  maxReports    = 200;\n};\n```\n\nWire into your reverse proxy — e.g. Caddy:\n\n```Caddyfile\ndmarc.box.example.com {\n    reverse_proxy 10.0.10.x:8741\n}\n```\n\n### Module options\n\n| Option         | Default          | Description                                                                                                                     |\n| -------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |\n| `enable`       | —                | Enable the service                                                                                                              |\n| `mailDir`      | —                | Path to Maildir or mbox containing DMARC report emails                                                                          |\n| `scanUser`     | `dmarc-analyzer` | User to run the scanner as. Must have read access to `mailDir`. With nixos-mailserver, set to `config.mailserver.vmailUserName` |\n| `port`         | `8741`           | Port the HTTP server listens on                                                                                                 |\n| `listenHost`   | `127.0.0.1`      | Address to bind to                                                                                                              |\n| `scanInterval` | `15min`          | How often to scan for new reports                                                                                               |\n| `maxReports`   | `200`            | Max reports kept in `reports.json`                                                                                              |\n\n### Notes\n\n- The module does **not** touch Caddy, nginx, `/etc/hosts`, or any other system\n  config. Reverse proxy wiring is left to you.\n- The scanner runs as `scanUser` (e.g. `virtualMail`) so it can read a\n  `700`-mode Maildir. Output files are written as group `dmarc-analyzer` so the\n  HTTP server can read them.\n- State lives in `/var/lib/dmarc-analyzer/`. Deleting it is safe — systemd will\n  recreate the directories on next start via `StateDirectory`.\n- The HTTP server only ever serves two paths: `/` (the frontend) and\n  `/data/reports.json`. Everything else returns 404.\n\n---\n\n## Firewall\n\nIf `listenHost` is not `127.0.0.1`, restrict access with a firewall rule. With\nNixOS iptables:\n\n```nix\nnetworking.firewall.extraCommands = ''\n  iptables -A nixos-fw -s \u003crproxy-ip\u003e -d \u003cmail-lan-ip\u003e -p tcp --dport 8741 -j nixos-fw-accept\n'';\nnetworking.firewall.extraStopCommands = ''\n  iptables -D nixos-fw -s \u003crproxy-ip\u003e -d \u003cmail-lan-ip\u003e -p tcp --dport 8741 -j nixos-fw-accept || true\n'';\n```\n\n---\n\n## Frontend — standalone use\n\nThe built `dmarc-feed.html` is fully self-contained (fonts and JSZip inlined).\nIt can be used without the backend — just open it in a browser and upload `.zip`\nor `.xml` report files directly. When hosted, it automatically fetches\n`/data/reports.json` on load.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrancesco149%2Fdmarc-analyzer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffrancesco149%2Fdmarc-analyzer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrancesco149%2Fdmarc-analyzer/lists"}