izm1chael/mailhook
GitHub: izm1chael/mailhook
一个自托管的邮件安全网关,通过多引擎扫描流水线监控 IMAP 邮箱并自动隔离或删除恶意邮件。
Stars: 0 | Forks: 0
# MailHook
[](https://github.com/izm1chael/mailhook/actions/workflows/ci.yml)
[](https://github.com/izm1chael/mailhook/actions/workflows/release.yml)
[](https://github.com/izm1chael/mailhook/releases/latest)
[](https://github.com/izm1chael/mailhook/pkgs/container/mailhook)
[](https://scorecard.dev/viewer/?uri=github.com/izm1chael/mailhook)
[](https://www.bestpractices.dev/projects/12986)
[](LICENSE)
[](app/go.mod)
一个自托管的邮件安全网关。MailHook 通过 IDLE 监控 IMAP 邮箱,将
每封收到的邮件经过一个多引擎扫描 pipeline 处理,并自动
隔离或删除威胁。它提供了一个 Web dashboard,用于审查、放行、审计
以及管理允许/阻止列表。
MailHook 作为一个单独的 Go 二进制文件发布(YARA 使用了 CGO),并与 Rspamd 和
ClamAV 一起运行。本 README 是完整的文档:涵盖了功能、架构、所有
受支持的安装和部署方式、配置、运维和开发。
## 目录
1. [概述](#overview)
2. [功能](#features)
3. [架构](#architecture)
4. [要求](#requirements)
5. [生成必要的密钥](#generating-the-required-secrets)
6. [安装与部署](#installation-and-deployment)
- [方法 1:Docker Compose(推荐)](#method-1-docker-compose-recommended)
- [方法 2:带有 AI 扫描层的 Docker Compose](#method-2-docker-compose-with-the-ai-scanner-tier)
- [方法 3:多合一单容器](#method-3-all-in-one-single-container)
- [方法 4:Debian / Ubuntu (.deb) 结合 systemd](#method-4-debian--ubuntu-deb-with-systemd)
- [方法 5:RHEL / Fedora (.rpm) 结合 systemd](#method-5-rhel--fedora-rpm-with-systemd)
- [方法 6:从源码构建并运行二进制文件](#method-6-build-from-source-and-run-the-binary)
7. [配置参考](#configuration-reference)
8. [AI 扫描层(可选)](#the-ai-scanner-tier-optional)
9. [在反向代理后运行](#running-behind-a-reverse-proxy)
10. [运维](#operations)
11. [使用 Dashboard](#using-the-dashboard)
12. [开发](#development)
13. [项目结构](#project-layout)
14. [安全模型](#security-model)
15. [故障排除](#troubleshooting)
16. [许可协议](#license)
## 概述
MailHook 连接到一个或多个 IMAP 账户,使用 IMAP IDLE 监视收件箱,并
在收到新邮件时进行处理。每封邮件都由一组引擎并发扫描,
由判定引擎汇总结果,然后 MailHook 执行一个 IMAP 操作:
将邮件保留在原处、对其打标、将其移动到隔离文件夹,或者将其删除。
操作员可以通过内置的 Web dashboard 审查和管理被隔离的邮件。
所有数据都存储在本地的 SQLite 中。没有外部数据库,也没有云
依赖,尽管可以通过 API key 启用可选的声誉服务(VirusTotal、AbuseIPDB)。
## 功能
- 针对一个或多个邮箱的 IMAP IDLE 接入,并带有周期性的恢复扫描,可以
捕获重连期间遗漏的任何邮件。
- 每封邮件运行一个并发的、多引擎扫描 pipeline:
- Rspamd 用于垃圾邮件评分,ClamAV 用于恶意软件,YARA 用于自定义规则。
- URL 威胁情报源:URLhaus、OpenPhish、PhishTank、ThreatFox(保存在内存索引中)。
- 使用 SSRF 安全的 dialer 跟踪重定向链来进行 URL 还原(去除短链接)。
- 通过 RDAP 检测新注册域名(NRD)。
- IP 信誉(AbuseIPDB),以及 VirusTotal 和 MalwareBazaar 哈希查询。
- HTML 走私和隐藏文本/零字号字体启发式检测。
- 一个可选的 ONNX AI 层(DistilBERT 钓鱼检测加上 DGA CNN),位于 `ai` 构建标签后。
- 一个优先级的判定引擎,将结果映射为通过、打标、隔离或删除,
并在关键扫描器不可用时采取失败即关闭策略(隔离以供人工审查)。
- 隔离工作流:移动到隔离区、放行回收件箱、删除和
重新扫描。IMAP 状态和本地数据库保持同步。
- 一个使用 Tailwind 和 Alpine.js 构建的 Web dashboard,由二进制文件提供服务,通过 Server-Sent Events 提供实时更新、隔离管理、允许/阻止列表、统计
和审计日志。
- 默认安全:使用 AES-256-GCM 加密存储的 IMAP 凭据和 API key,
带有速率限制登录的 bcrypt 管理员身份验证、CSRF 保护,以及严格的
基于 nonce 的 Content Security Policy,没有 `unsafe-inline` 且没有 `unsafe-eval`。
- 回溯性威胁情报源追溯,根据
更新的情报源重新评估最近的安全邮件,并隔离新识别出的威胁。
## 架构
```
IMAP (IDLE) +-------------+
mailbox ------------------> MailHook --->| Rspamd |
| | +-------------+
quarantine / delete <---------+ +------->| ClamAV |
(IMAP actions) scanners +-------------+
|
+-- SQLite (scans, audit, allow/block lists)
+-- Web dashboard (HTTP, CSP, SSE)
```
MailHook 通过 HTTP 与 Rspamd 通信,通过 clamd TCP 协议与 ClamAV 通信。在
推荐的 Docker 部署中,它们作为私有网络上分离的容器运行。在
多合一镜像中,它们作为受监控的进程在单个容器内运行。在
裸机宿主机上,它们作为各自的服务运行。
## 要求
- 一台可以运行 Docker(推荐)或 Linux 服务的宿主机。
- MailHook 可以访问的 Rspamd 和 ClamAV。
- 对于源码构建:Go 1.26 或更高版本,一个 C 工具链,以及 libyara 4.3 或更高版本。
- 一个支持带有 IDLE 的 IMAP 的邮箱。
默认端口:Web dashboard `8080`,Rspamd `11333`,ClamAV `3310`。
## 生成必要的密钥
MailHook 在未设置三个密钥的情况下拒绝启动,并且它会拒绝
示例中提供的占位符值。生成它们一次:
```
# CSRF signing key(必须至少为 32 个字符)
openssl rand -hex 32
# Database encryption key(必须正好是 64 个十六进制字符 = 32 bytes)
openssl rand -hex 32
# 作为 bcrypt hash 的管理员密码(cost 12)
htpasswd -nbBC 12 admin 'your-strong-password' | cut -d: -f2
```
这些对应于 `MAILHOOK_CSRF_SECRET`、`MAILHOOK_DB_ENCRYPTION_KEY` 和
`MAILHOOK_ADMIN_PASSWORD_BCRYPT`。Makefile 目标 `make setup-password` 可以为你生成
bcrypt 哈希。
## 安装与部署
支持六种方法。大多数操作员应该使用方法 1。
### 方法 1:Docker Compose(推荐)
这会在私有网络上启动 MailHook、Rspamd 和 ClamAV 三个强化容器,并
仅在 loopback 上发布 dashboard。
```
git clone https://github.com/izm1chael/mailhook.git
cd mailhook
cp .env.example .env # then fill in the three secrets above
cp config.example.yaml config.yaml # then add your IMAP account(s)
docker compose up -d --build
```
Dashboard 随后可通过 `http://127.0.0.1:8080` 访问。ClamAV 在首次
启动时下载其病毒数据库,这可能需要几分钟时间,因此在完成之前,`/health` 可能会将 ClamAV 报告为
不可用。
有用的包装脚本:
```
make up # docker compose up -d --build
make down # docker compose down
make logs # follow logs
```
### 方法 2:带有 AI 扫描层的 Docker Compose
AI 层增加了 DistilBERT 钓鱼检测和 DGA CNN。它需要模型文件
(参见 [AI 扫描层](#the-ai-scanner-tier-optional)),并通过
override 文件进行叠加:
```
docker compose -f docker-compose.yml -f docker-compose.ai.yml up -d --build
```
### 方法 3:多合一单容器
`app/Dockerfile.allinone` 将 MailHook、Rspamd 和 ClamAV 打包到一个容器中,
由 s6-overlay 监控。这对于单节点宿主机和测试非常方便。
它比单独运行 MailHook 消耗更多的内存,因为 ClamAV 会将其特征
数据库保留在 RAM 中。
```
docker build -f app/Dockerfile.allinone -t mailhook:allinone .
docker run -d --name mailhook \
-p 127.0.0.1:8080:8080 \
--env-file .env \
-v "$PWD/config.yaml:/etc/mailhook/config.yaml:ro" \
-v mailhook-data:/data \
mailhook:allinone
```
### 方法 4:Debian / Ubuntu (.deb) 结合 systemd
使用 nfpm 构建原生包并安装。该包将二进制文件安装到
`/usr/local/bin/mailhook`,一个强化的 systemd unit,以及 `/etc/mailhook` 下的配置。它
声明 `clamav-daemon`、`clamav-freshclam` 和 `rspamd` 为依赖项,以便它们被
自动安装。
```
cd app && make package-deb # produces ../dist/mailhook__.deb
sudo apt install ./dist/mailhook_*.deb
```
安装后:
```
# 编辑环境文件并添加这三个 secrets(见上文)。
sudoedit /etc/mailhook/mailhook.env
# 添加你的 IMAP account(s)。
sudoedit /etc/mailhook/config.yaml
sudo systemctl enable --now mailhook
systemctl status mailhook
journalctl -u mailhook -f
```
systemd unit 以专用的 `mailhook` 用户身份运行,并启用了
强化功能(`NoNewPrivileges`、`ProtectSystem=strict`、`ProtectHome`、空 capability 集以及
system-call 过滤器)。运行时数据位于 `/var/lib/mailhook`,情报源缓存位于
`/var/cache/mailhook`。
### 方法 5:RHEL / Fedora (.rpm) 结合 systemd
RPM 等同于 DEB,并依赖于 `clamd` 和 `rspamd`。
```
cd app && make package-rpm # produces ../dist/mailhook-..rpm
sudo dnf install ./dist/mailhook-*.rpm
```
然后配置 `/etc/mailhook/mailhook.env` 和 `/etc/mailhook/config.yaml` 并
完全按照方法 4 中的方式启用服务。AI 变体包可通过
`make package-deb-ai` 和 `make package-rpm-ai` 获取。
### 方法 6:从源码构建并运行二进制文件
```
cd app
make build # standard binary at ../bin/mailhook
# 或者:make build-ai # 包含 ONNX AI tier (-tags ai)
```
使用通过环境变量提供的配置运行它,并将其指向你
自己的 Rspamd 和 ClamAV:
```
export MAILHOOK_ADMIN_PASSWORD_BCRYPT='...'
export MAILHOOK_CSRF_SECRET='...'
export MAILHOOK_DB_ENCRYPTION_KEY='...'
export MAILHOOK_RSPAMD_URL='http://127.0.0.1:11333'
export MAILHOOK_CLAMAV_ADDR='127.0.0.1:3310'
export MAILHOOK_CONFIG='./config.yaml'
../bin/mailhook
```
源码构建需要安装 libyara(4.3 或更高版本)。提供的 Dockerfile
从源码编译 YARA 以确保可重现性,并且原生包静态链接
它,因此不需要运行时 libyara 依赖。
## 配置参考
全局设置来自环境变量。每个账户的 IMAP 设置来自
YAML 文件(`config.yaml`)。有关完整的
带注释的集合,请参见 `.env.example` 和 `config.example.yaml`。最重要的变量:
| 变量 | 默认值 | 用途 |
|---|---|---|
| `MAILHOOK_ADMIN_USER` | `admin` | Dashboard 用户名 |
| `MAILHOOK_ADMIN_PASSWORD_BCRYPT` | (必填)| 管理员密码的 Bcrypt 哈希 |
| `MAILHOOK_CSRF_SECRET` | (必填)| CSRF token 的 HMAC key,32 个字符以上 |
| `MAILHOOK_DB_ENCRYPTION_KEY` | (必填)| 用于静态加密密钥的 64 位十六进制字符 AES-256 key |
| `MAILHOOK_LISTEN` | `0.0.0.0:8080` | Web 监听地址 |
| `MAILHOOK_RSPAMD_URL` | `http://rspamd:11333` | Rspamd endpoint |
| `MAILHOOK_CLAMAV_ADDR` | `clamav:3310` | ClamAV (clamd) 地址 |
| `MAILHOOK_YARA_RULES_DIR` | `/rules` | `.yar` 规则文件目录 |
| `MAILHOOK_SPAM_SCORE` | `5.0` | 达到/超过此 Rspamd 分数时隔离邮件 |
| `MAILHOOK_REJECT_SCORE` | `15.0` | 达到/超过此 Rspamd 分数时删除邮件 |
| `MAILHOOK_VT_API_KEY` | empty | VirusTotal key(为空则禁用) |
| `MAILHOOK_ABUSEIPDB_KEY` | empty | AbuseIPDB key(为空则禁用) |
| `MAILHOOK_DATA_DIR` | `/data` | 存储的 EML 和数据库位置 |
| `MAILHOOK_DB_PATH` | `/data/mailhook.db` | SQLite 数据库路径 |
| `MAILHOOK_RETENTION_DAYS` | `30` | 清洁邮件 EML 的保留时间(如果保留的话) |
| `MAILHOOK_EML_QUARANTINE_RETENTION_DAYS` | `90` | 隔离 EML 的保留时间 |
| `MAILHOOK_TRUSTED_PROXIES` | empty | 其 `X-Forwarded-For` 受信任的 CIDR |
| `MAILHOOK_METRICS_ALLOWED_CIDRS` | `127.0.0.1/32,::1/128` | 允许访问 `/metrics` 和 `/api/scan` 的 CIDR |
| `MAILHOOK_TRUSTED_AUTHSERV_ID` | empty | 其 `Authentication-Results` 受信任的 authserv-id |
| `MAILHOOK_REDACT_WEBHOOK_PII` | `false` | 在 ntfy/webhook payload 中掩盖发件人和主题 |
| `MAILHOOK_INSECURE_COOKIES` | `false` | 禁用 Secure cookie 标志(仅限本地 HTTP 开发) |
| `MAILHOOK_LOG_LEVEL` / `MAILHOOK_LOG_FORMAT` | `info` / `json` | 日志记录 |
每个账户的 IMAP 配置(`config.yaml`):
```
accounts:
- name: primary # unique label, no spaces or slashes
host: imap.example.com
port: 993
user: security@example.com
pass: app-password
mailbox: INBOX # folder to monitor
quarantine: Quarantine # folder threats are moved to (created if missing)
tls_skip_verify: false # set true only for self-signed test servers
```
`config.yaml` 中的账户密码会在启动时迁移到数据库中,并在静态
使用 `MAILHOOK_DB_ENCRYPTION_KEY` 加密。也可以在运行时从
Dashboard 的“设置”页面管理账户。
## AI 扫描层(可选)
默认构建中排除了 AI 层。要使用它,请使用 `-tags ai`
(`make build-ai`)构建并提供模型文件。辅助脚本:
```
cd app
make models-dl # fetch the Tranco greylist
make models-bert # export the DistilBERT phishing model to ONNX
make models-dga # build the DGA CNN model (see scripts/export_dga_onnx.py)
make build-ai # build the AI-enabled binary
```
缺少模型不是致命的:该子扫描器将被跳过并记录在日志中,而 pipeline 的其余部分
继续运行。
## 通过纯 HTTP 访问(无反向代理)
默认情况下,Session cookie 会设置 `Secure` 标志,这会指示浏览器
仅在 HTTPS 上发送它们。当你从
**非 localhost** 地址直接通过纯 HTTP 访问 MailHook 时(例如,在局域网 IP 上发布的 Docker 或虚拟机的私有
地址),浏览器会默默丢弃 Session cookie,并且在登录后你会被立即重定向
回登录页面。
要解决此问题,请在你的 `.env` 文件中设置 `MAILHOOK_INSECURE_COOKIES=true`:
```
MAILHOOK_INSECURE_COOKIES=true
```
## 在反向代理后运行
Dashboard 使用纯 HTTP 通信,旨在在生产环境中位于终止 TLS 的反向
代理(Caddy、nginx、Traefik)之后。当你这样做时:
1. 将 `MAILHOOK_TRUSTED_PROXIES` 设置为代理的 CIDR,以便正确从
`X-Forwarded-For` 读取客户端 IP(用于登录速率限制和审计日志)。
2. 保持 `MAILHOOK_METRICS_ALLOWED_CIDRS` 的严格限制。它默认仅允许 loopback,并对
`/metrics` 和基准测试 `/api/scan` endpoint 同时进行控制。
3. 不要将 `/metrics` 或 `/api/scan` 转发给不受信任的客户端。
Dashboard 发送 HSTS 和严格的 CSP,因此它期望通过 HTTPS 提供服务。
## 运维
Endpoints:
- `GET /healthz` 是容器健康检查使用的未经身份验证的存活探针。
- `GET /health`(需身份验证)返回完整的组件健康状况和今天的统计数据,并
在关键组件出现降级时返回 503。
- `GET /metrics` 暴露 Prometheus metrics,受 `MAILHOOK_METRICS_ALLOWED_CIDRS` 限制。
维护在本地时间 03:00 自动运行:EML 保留修剪、IP 信誉
缓存清理、完整性检查以及压缩的数据库备份(VACUUM INTO)。威胁
情报源按 `MAILHOOK_FEED_REFRESH_INTERVAL`(默认 6h)设置的时间间隔刷新,并
可以从“设置”页面按需刷新。
备份:SQLite 数据库和数据目录下的 `emls` 目录保存了所有状态。
备份 `MAILHOOK_DATA_DIR`(并妥善保管 `MAILHOOK_DB_ENCRYPTION_KEY`,因为没有它将无法解密存储的
凭据)。
更新 Docker 部署:
```
git pull
docker compose up -d --build
```
## 使用 Dashboard
- Dashboard:最近的扫描、判定和状态,以及处理新邮件时的实时更新。
- 隔离区:审查被拦截的邮件,在 sandboxed frame 中预览净化的 HTML,放行
到收件箱、删除、重新扫描或学习为垃圾邮件。支持批量操作。
- 列表:按地址或域管理允许和阻止列表,包括批量导入。
- 统计:随时间变化的各个判定和各个发件人的细分。
- 审计:每一个自动化和手动操作的记录。
- 设置:阈值、API key、通知、扫描器开关、endpoint、账户
以及密码更改。
## 开发
```
cd app
make build # standard binary
make build-ai # AI-enabled binary
make test # full test suite
make test-race # race detector
make test-cover # coverage profile
make vet # go vet
make lint # golangci-lint (if installed)
make simulate # verdict-engine scenario comparison (standard vs AI)
make bench # pipeline benchmarks
```
Docker 镜像构建:`make docker-build`、`make docker-build-ai`、
`make docker-build-standard`。
## 项目结构
```
app/ Go source
pipeline/ message parsing, scanner fan-out, verdict engine
scanners/ rspamd, clamav, yara, urlcheck, urlunshorten, nrd, vt, ...
imap/ IMAP listener, actions, recovery, manager
web/ HTTP server, handlers, templates, embedded static assets
db/ models, migrations, SQLite access, at-rest encryption
config/ configuration loading and validation
notify/ ntfy and webhook notifications
cmd/ seed, simulate, bench, soak helpers
Dockerfile standard image
Dockerfile.ai AI-variant image
Dockerfile.allinone single-container image (s6-overlay)
rules/ YARA rules
rspamd-config/ Rspamd local.d overrides
packaging/ nfpm DEB/RPM config, systemd unit, all-in-one assets
docker-compose.yml main stack (mailhook + rspamd + clamav)
docker-compose.ai.yml AI-variant override
docker-compose.bench.yml benchmarking stack
```
## 安全模型
- 管理员身份验证使用 bcrypt,并带有基于 IP 的登录速率限制和锁定机制。
- Session 是 HttpOnly、SameSite=Strict 的 cookie;CSRF 使用签名的双重提交 token。
- Dashboard 设置了基于 nonce 的 CSP,没有 `unsafe-inline` 且没有 `unsafe-eval`,
加上 `X-Content-Type-Options`、`X-Frame-Options`、`Referrer-Policy` 和 HSTS。
- IMAP 凭据和 API key 使用 AES-256-GCM 进行静态加密。
- 出站 URL 解析(还原短链接、RDAP、webhook)使用 SSRF 安全的 dialer,它
仅解析一次并拒绝非公开地址。
- 隔离的邮件 HTML 已经过净化,并且仅在 sandboxed iframe 中预览。
在 HTTPS 后运行 MailHook,将受信任的代理和 metrics CIDR 设置为符合你的
部署,并在上线前轮换示例密钥。
## 故障排除
- `/health` 在首次启动时显示 ClamAV 不可用:ClamAV 仍在下载其
特征数据库。请等待几分钟。
- 启动时立即退出并抱怨密钥:三个必需密钥之一
缺失或仍设置为其占位符值。
- 本地测试期间通过纯 HTTP 登录状态无法保持:设置
`MAILHOOK_INSECURE_COOKIES=true`(生产环境中切勿如此)。
- `/metrics` 或 `/api/scan` 返回 403:调用者的 IP 不在
`MAILHOOK_METRICS_ALLOWED_CIDRS` 中,或者由于
未设置 `MAILHOOK_TRUSTED_PROXIES` 导致 `X-Forwarded-For` 不受信任。
## 许可协议
在 [MIT License](LICENSE) 下发布。版权所有 (c) 2026 izm1chael。
标签:CNCF毕业项目, EVTX分析, Go语言, IMAP监控, 垃圾邮件过滤, 日志审计, 程序破解, 自托管, 请求拦截, 邮件安全网关