Alert rules¶
Alert rules turn captured events into notifications. After every event is enriched, each enabled rule's condition is tested against the freshly updated attacker profile (and the triggering event); a match queues a notification through the rule's configured channel — Slack, email, or a generic webhook.
Rules are managed in the admin under Alert rules. A starter set can be seeded with python manage.py seed_default_alert_rules (created disabled, so nothing fires until you fill in a real destination and switch them on).
Condition syntax¶
A condition is a single flat JSON object with exactly three keys:
{"field": "threat_score", "op": "gte", "value": 80}
There is no nesting and no AND/OR — one rule tests one field. Want a compound alert? Create multiple rules.
Malformed conditions are safe: an unknown field or operator, missing keys, or a type mismatch is logged and treated as "no match" — a broken rule can never break the enrichment pipeline.
Supported fields¶
| Field | Read from | Type | Notes |
|---|---|---|---|
threat_score | attacker profile | int 0–100 | Cumulative score after this event's enrichment |
is_known_scanner | attacker profile | bool | Set when a JA3 fingerprint matches the known-scanner list |
event_count | attacker profile | int | Total events from this IP |
country_code | attacker profile | string | Two-letter ISO code from GeoIP; may be null before a GeoIP hit |
decoy_type | triggering event | string | admin, env, wpAdmin, api, canary, custom |
Supported operators¶
| Op | Meaning | Notes |
|---|---|---|
gt | greater than | Null or mismatched-type actual value → no match, never an error |
gte | greater than or equal | " |
lt | less than | " |
lte | less than or equal | " |
eq | equal | Works for booleans and strings too |
in | membership | value must be a JSON list |
Examples¶
{"field": "threat_score", "op": "gte", "value": 80}
{"field": "is_known_scanner", "op": "eq", "value": true}
{"field": "decoy_type", "op": "eq", "value": "canary"}
{"field": "country_code", "op": "in", "value": ["CN", "RU", "KP", "IR"]}
{"field": "event_count", "op": "gt", "value": 100}
Throttling — the mute window¶
A noisy attacker matching threat_score >= 80 would otherwise fire on every single probe. Instead, each (rule, attacker) pair is muted for ALERT_REFIRE_WINDOW_SECONDS (default 3600 — once per attacker per hour per rule) after a dispatch is queued. A different attacker matching the same rule still alerts immediately.
The rule's last_fired timestamp and fire_count counter (visible in the admin list) track rule-wide activity.
Notifiers¶
The channel is set per rule via notifier_type + notifier_config. All notifiers share a hard rule: they never raise. A dead webhook or SMTP server is logged and swallowed — alert delivery can't break enrichment.
Slack — notifier_type: slack¶
Posts a formatted message to a Slack incoming webhook.
{"webhook_url": "https://hooks.slack.com/services/T000/B000/XXXX"}
Email — notifier_type: email¶
Sends through Django's configured email backend, from DEFAULT_FROM_EMAIL. to may be a single address or a list:
{"to": "secops@example.com"}
{"to": ["secops@example.com", "oncall@example.com"]}
Webhook — notifier_type: webhook¶
POSTs a structured JSON payload to any URL — the integration point for SIEMs, PagerDuty, n8n, or your own glue code:
{"url": "https://example.com/hooks/honeydj"}
Payload shape:
{
"rule": "High threat score (>= 80)",
"ip": "203.0.113.42",
"country": "Russia",
"country_code": "RU",
"threat_score": 92,
"is_known_scanner": true,
"tags": ["sql_injection", "sqlmap"],
"decoy_type": "admin",
"path": "/admin/?id=1%20OR%201%3D1",
"method": "GET",
"timestamp": "2026-07-03T14:00:00+00:00",
"profile_url": "https://intel.example.com/hd-xyz/profiles/attackerprofile/7/change/"
}
Slack and email messages carry the same fields as readable lines, including a clickable link to the attacker's admin profile page when ADMIN_BASE_URL is set (see below).
"Send test alert"¶
Every rule has a Send test alert action — on the changelist (select rules → action dropdown) and as a button on the rule's own edit page. It sends one real notification through the rule's channel using a representative sample attacker/event (IP 203.0.113.42, threat score 92, sqlmap tags), then reports success or failure as an admin message.
Use it after wiring up a webhook URL or inbox to confirm delivery works before enabling the rule — the condition is not evaluated and the mute window is not touched.
Related settings¶
| Setting | Default | Purpose |
|---|---|---|
ALERT_REFIRE_WINDOW_SECONDS | 3600 | Per-(rule, attacker) mute window |
ALERT_WEBHOOK_TIMEOUT | 5.0 | HTTP timeout for Slack and webhook POSTs |
ADMIN_BASE_URL | "" | Absolute base URL used to build clickable profile links in alerts |