Anglerfish is a CLI for security teams in Microsoft 365 shops. It plants Outlook canary messages with Microsoft Graph, then detects mailbox access by matching MailItemsAccessed events from the Microsoft 365 Unified Audit Log back to local deployment records — using the tenant's own audit telemetry as the detection plane. No callback receiver, DNS zone, webhook, or external SIEM is required to identify access; correlation runs against records you keep on disk.
It supports hidden-folder draft canaries, inbox send canaries, local health checks for draft deployments, and audit-log monitoring without callback infrastructure.
Note
Detection is delayed, not real-time. Microsoft Unified Audit Log records typically appear 60–90 minutes after the access event, sometimes longer. Anglerfish surfaces the event when it lands; it does not stream from Exchange directly. See the threat model for details.
Non-interactive demo deployment (anglerfish --demo --non-interactive ...):
Alert detection (anglerfish monitor):
Deploying a canary writes a local deployment record; when the canary is read, a
MailItemsAccessed event lands in the Unified Audit Log and Anglerfish
correlates it back to that record. These sanitized artifacts show the shape of
the evidence without exposing tenant data:
Warning
This tool is for authorized security testing and defensive canary deployments only. Mail.ReadWrite application permission grants tenant-wide mailbox write access by default. Production use requires formal approval and explicit scoping decisions.
1. Deploy anglerfish -> Microsoft Graph -> Outlook draft or inbox canary
2. Access mailbox activity -> Microsoft 365 Unified Audit Log -> MailItemsAccessed
3. Detect anglerfish monitor -> matches MailItemsAccessed to deployment record -> alert
Anglerfish is intentionally narrow in this release: Outlook only, application authentication only, and one primary workflow built around deploy, list, verify, cleanup, and monitor.
Draft deployments add a per-canary ID to the hidden folder name and require an internetMessageId correlation key in the deployment record.
| Tool | Open-source | Self-hosted | Third-party data plane | Tenant-native telemetry |
|---|---|---|---|---|
| Anglerfish | yes (MIT) | yes | no | yes (UAL) |
| Managed Canarytokens / Canarytools | no | no (SaaS) | yes (Thinkst) | n/a (callback pattern) |
| Self-hosted Canarytokens | yes | yes | operator-controlled | n/a (callback pattern) |
| Defender for Office 365 anomalous mailbox detection | no | n/a (Microsoft-hosted) | n/a | yes (UAL) |
DIY Sentinel KQL on MailItemsAccessed |
yes (operator-built content) | no (Microsoft-hosted SIEM) | no | yes (UAL) |
Where Anglerfish fits. Anglerfish is not a Canarytokens replacement. It is a narrower, M365-native option for teams that want canary deploys plus native UAL correlation without standing up a separate canary platform, configuring webhook receivers, or maintaining their own KQL hunt. If you already run Sentinel and write your own queries, the Sentinel KQL snippet gives you the same correlation primitive without the CLI; if you want a single self-contained CLI that handles deploy, list, verify, cleanup, and detection, Anglerfish covers that path.
Anglerfish sits next to several existing approaches; each does something Anglerfish does not.
- Thinkst Canary / Canarytokens — the most mature canary platform; commercial managed appliance plus an open-source self-hosted token server. Detection is callback-based (DNS, HTTP, SMTP). Anglerfish is M365-only, has no token server, and does not use callback transport — it correlates against the tenant's existing audit log instead.
- DIY Sentinel KQL hunts on
MailItemsAccessed— same audit primitive, surfaced through Microsoft Sentinel queries (the Microsoft Sentinel community repo is the canonical example library). Anglerfish adds the deploy side and gives operators a non-SIEM path; the bundleddocs/sentinel-kql.mdsnippet is provided so Sentinel users can validate Anglerfish's correlation against their own queries. - Defender for Office 365 anomalous-mailbox detection — Microsoft-hosted, statistical, and not deterministic per-canary. Anglerfish is deterministic: a deployed canary plus a
MailItemsAccessedevent with the canary'sinternetMessageIdis the alert. - Mailoney / open-source mail honeypots — different threat model (inbound network/SMTP); not M365-native and does not interact with Exchange Online or the Unified Audit Log.
| Command | Purpose |
|---|---|
anglerfish |
Interactive Outlook canary deploy |
anglerfish list |
List deployment records |
anglerfish verify |
Check active draft-mode Outlook canaries |
anglerfish cleanup <record> |
Remove a deployed Outlook canary |
anglerfish demo-access <record> |
Read a deployed canary through Graph to generate authorized audit evidence |
anglerfish monitor |
Poll for Outlook access alerts |
Notes:
draftis the default and best-supported operator path.sendis supported for deploy, cleanup, list, and monitor.verifyis draft-only because send-mode records do not keep a hidden folder to inspect.
git clone https://github.com/vortacity/anglerfish.git
cd anglerfish
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install -e .scripts/quickstart.sh automates the venv and install steps above.
Anglerfish reads credentials from the process environment. It does not auto-load .env, so export values directly or source a file yourself before running commands.
export ANGLERFISH_TENANT_ID="..."
export ANGLERFISH_CLIENT_ID="..."
export ANGLERFISH_APP_CREDENTIAL_MODE="secret"
export ANGLERFISH_CLIENT_SECRET="..."Dry-run the default Outlook workflow:
anglerfish --dry-run --non-interactive \
--canary-type outlook \
--template "Fake Password Reset" \
--target adele.vance@contoso.com \
--delivery-mode draftDeploy a real canary and write a local record:
export RECORD="$HOME/.anglerfish/records/adele-password-reset.json"
anglerfish --non-interactive \
--canary-type outlook \
--template "Fake Password Reset" \
--target adele.vance@contoso.com \
--delivery-mode draft \
--output-json "$RECORD"Trigger authorized access for a demo tenant, then poll after UAL ingestion:
anglerfish demo-access --non-interactive "$RECORD"
anglerfish monitor --once --records-dir "$HOME/.anglerfish/records"Try the product offline:
anglerfish --demo
anglerfish monitor --demo --count 2For a complete Entra app registration walkthrough, see Demo tenant setup guide.
Application authentication is the only supported auth model in this release.
Credential selection:
--credential-mode secretorANGLERFISH_APP_CREDENTIAL_MODE=secret--credential-mode certificateorANGLERFISH_APP_CREDENTIAL_MODE=certificateautois also accepted and chooses whichever single credential type is configured
Secret mode:
export ANGLERFISH_TENANT_ID="<tenant-guid>"
export ANGLERFISH_CLIENT_ID="<app-client-id>"
export ANGLERFISH_APP_CREDENTIAL_MODE="secret"
export ANGLERFISH_CLIENT_SECRET="<client-secret>"Certificate mode:
export ANGLERFISH_TENANT_ID="<tenant-guid>"
export ANGLERFISH_CLIENT_ID="<app-client-id>"
export ANGLERFISH_APP_CREDENTIAL_MODE="certificate"
export ANGLERFISH_CLIENT_CERT_PFX_PATH="/path/to/app-cert.pfx"
export ANGLERFISH_CLIENT_CERT_PASSPHRASE="<optional-passphrase>"PEM certificate configuration is also supported. See .env.example for the full variable set.
| Workflow | Permission | API |
|---|---|---|
| Draft deploy, cleanup, verify | Mail.ReadWrite |
Microsoft Graph |
| Send deploy | Mail.ReadWrite, Mail.Send |
Microsoft Graph |
| Monitor | ActivityFeed.Read |
Office 365 Management Activity API |
Grant admin consent after adding the permissions.
Warning
Mail.ReadWrite application permission grants tenant-wide mailbox write access by default. Production use requires formal approval and explicit scoping decisions. Operators can use Exchange Online RBAC for Applications to scope access to selected mailboxes, but must ensure unscoped Microsoft Entra grants do not remain in place.
Bundled Outlook templates:
Fake Password ResetFake Wire TransferIT Compliance Audit NoticePayroll Direct Deposit Update
Custom Outlook YAML templates are supported through ANGLERFISH_TEMPLATES_DIR:
export ANGLERFISH_TEMPLATES_DIR="$PWD/custom-templates"
anglerfish --non-interactive \
--canary-type outlook \
--template "Fake Password Reset" \
--target adele.vance@contoso.com \
--delivery-mode draft \
--var company_name="Contoso"--template names are case-insensitive. Repeat --var KEY=VALUE to fill template variables.
Interactive deploy:
anglerfishNon-interactive draft deploy:
anglerfish --non-interactive \
--canary-type outlook \
--template "Fake Password Reset" \
--target adele.vance@contoso.com \
--delivery-mode draft \
--output-json ~/.anglerfish/records/adele-draft.jsonNon-interactive send deploy:
anglerfish --non-interactive \
--canary-type outlook \
--template "Fake Wire Transfer" \
--target adele.vance@contoso.com \
--delivery-mode send \
--output-json ~/.anglerfish/records/adele-send.jsonList records:
anglerfish list --records-dir ~/.anglerfish/recordsVerify draft-mode records:
anglerfish verify --records-dir ~/.anglerfish/records
anglerfish verify ~/.anglerfish/records/adele-draft.jsonCleanup:
anglerfish cleanup --non-interactive ~/.anglerfish/records/adele-draft.json
anglerfish cleanup --non-interactive ~/.anglerfish/records/adele-send.jsonTrigger authorized access to generate audit evidence:
anglerfish demo-access --non-interactive ~/.anglerfish/records/adele-draft.json
anglerfish demo-access --non-interactive ~/.anglerfish/records/adele-send.jsondemo-access performs a Graph read of the deployed canary so a permitted demo tenant can generate a real MailItemsAccessed event. It does not bypass Unified Audit Log latency; poll after the event is available.
Monitor for access:
anglerfish monitor --records-dir ~/.anglerfish/records
anglerfish monitor --once --records-dir ~/.anglerfish/records
anglerfish monitor --records-dir ~/.anglerfish/records \
--alert-log ~/.anglerfish/alerts.jsonl \
--slack-webhook-url https://hooks.slack.com/services/...The monitor flags can also be set via environment variables
(ANGLERFISH_MONITOR_STATE_FILE, ANGLERFISH_MONITOR_ALERT_LOG,
ANGLERFISH_SLACK_WEBHOOK_URL, ANGLERFISH_MONITOR_NO_CONSOLE); CLI flags
take precedence. See .env.example for the full variable set.
Unified Audit Log polling is delayed, not an immediate stream. Microsoft does not guarantee a return time for audit records; core service records are typically available after 60 to 90 minutes. See Microsoft audit search guidance.
To keep the poll loop running unattended, examples/anglerfish-monitor.service is an optional sample systemd unit that supervises anglerfish monitor --no-console. It is a convenience example for operators, not a built-in daemon mode.
The no third-party data plane claim applies to detection. Optional Slack alerting sends post-detection notifications to the configured webhook.
Suppress known-good actors:
anglerfish monitor --exclude-app-id "<known-good-app-id>"--exclude-app-id is a static allowlist for known-good actors such as backup, DLP, or eDiscovery tools. The option is repeatable when more than one known-good app principal should be excluded from matching. Do not exclude the actor or app used to generate demo evidence.
Demo mode:
anglerfish list --records-dir examples/demo-records
anglerfish cleanup --demo --non-interactive examples/demo-records/outlook-draft-record.json
anglerfish cleanup --demo --non-interactive examples/demo-records/outlook-send-record.json
anglerfish monitor --demo --count 2anglerfish verify --demo intentionally includes gone/error rows and exits nonzero, so it is omitted from the copy-paste demo block.
anglerfish --help
anglerfish verify --help
anglerfish demo-access --help
anglerfish monitor --helpThose help screens are the source of truth for the current command surface. This release only supports Outlook deploy/detect workflows.


