Skip to content

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.

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