send_telegram posts with parse_mode='HTML' but interpolated the hostname,
alert type, message, value and threshold straight into the body. Telegram
then parses those as markup, so a message as ordinary as
Disk usage > 90% on /var & /tmp
is rejected with "can't parse entities" and the entire notification is
dropped — precisely when something is already wrong on the box. A value
containing <b> could also inject markup into the rendered alert.
Escape every substituted value with html.escape(quote=False); the
template's own tags are untouched.
Proving tests pinned against the unfixed service first: the three
escaping cases fail without this change, the literal-markup case passes,
so they test the behaviour and not the implementation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>