stamparm/maltrail · warning
invalid 'ALERT_FORMAT
Error message
invalid 'ALERT_FORMAT' ('%s') What it means
Maltrail's alert body formatter applies the ALERT_FORMAT config value as a printf-style template against the event fields. When the template references missing keys or malformed placeholders, the % formatting raises KeyError/TypeError/ValueError; the code catches it and logs "invalid 'ALERT_FORMAT' ..." via log_error, returning None so no alert body is produced.
Solutions
- Fix ALERT_FORMAT to reference only fields that exist in the event dict (e.g. %(dst_ip)s, plus %(json)s).
- Escape literal percent signs as %% in ALERT_FORMAT.
- Run config.ALERT_FORMAT against a sample event dict offline (template % dict) to validate before deploying.
- Switch to the fields['json'] placeholder if you want the full event payload in one token.
Example fix
# before ALERT_FORMAT = "Alert on sensor %(sensor)s - uptime 99% - %(unknown_field)s" # after ALERT_FORMAT = "Alert on sensor %(sensor)s - uptime 99%% - %(json)s"
Defensive patterns
Strategy: validation
Validate before calling
fields = dict(event); fields["json"] = json_line(event)
try:
(config.ALERT_FORMAT or "") % fields
except (KeyError, TypeError, ValueError):
raise ValueError("ALERT_FORMAT references missing fields or has bad % placeholders") Type guard
def valid_alert_format(tpl, fields):
if not isinstance(tpl, str): return False
try: tpl % fields; return True
except (KeyError, TypeError, ValueError): return False Try / catch
try:
body = alert.body(event)
except Exception:
body = None # body() already logged; fall back to json_line(event) raw payload Prevention
- Only use placeholders that exist in the event dict (or fields['json'])
- Escape literal % as %% in ALERT_FORMAT
- Test new ALERT_FORMAT values against a sample event before deploying
- Prefer the %(json)s placeholder for full-payload alerts
When it happens
Trigger: ALERT_FORMAT contains a placeholder like %(missing_key)s with no matching field in the event dict, or uses a % character not intended as a placeholder (e.g. '99%'), causing ValueError 'incomplete format' or a TypeError on bad substitution types.
Common situations: Users copying templates from other alerting tools that use $VAR or {} syntax instead of Python %()s; embedding a literal percent sign in a URL or percentage without escaping it as %%; renaming event fields in a fork while old templates reference them.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- alert webhook POST failed
- [x] invalid IP address
- not a Maltrail provenance sidecar (bad magic)
- provenance sidecar is truncated
- packet too short for header-protection sample
AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13).
Data as JSON: /api/errors/8af652b3503ed898.
Report an issue: GitHub.
Appendix: source
Thrown at core/alert.py:136
_throttle[key] = now
return False
def body(event):
"""The request body, from ALERT_FORMAT.
A format string rather than a fixed structure, for the same reason CEF_FORMAT is one: there is
no webhook standard. Slack, Mattermost, Rocket.Chat and Google Chat take {"text": ...}, Discord
takes {"content": ...}, Teams wants an Adaptive Card, and a SIEM wants the event itself.
"""
template = config.ALERT_FORMAT or ""
fields = dict(event)
fields["json"] = json_line(event)
try:
return template % fields
except (KeyError, TypeError, ValueError) as ex:
log_error("invalid 'ALERT_FORMAT' ('%s')" % ex, single=True)
return None
def json_line(event):
"""The event as LOGSTASH_SERVER sends it, so anything already parsing that keeps working."""
from collections import OrderedDict
return json.dumps(OrderedDict((key, event.get(key, "")) for key in
("timestamp", "sensor", "severity", "src_ip", "src_port", "dst_ip",
"dst_port", "proto", "type", "trail", "info", "reference")))
def send(event):
"""POST one event. Never raises: a webhook outage must not stop the server or the tailer."""
payload = body(event)
if payload is None:
return FalseView on GitHub (pinned to 77cfb06d76)