From 41b9d2c91180c132a55905bf349af8d36391020b Mon Sep 17 00:00:00 2001 From: CarterPerez-dev Date: Thu, 14 May 2026 00:47:08 -0400 Subject: [PATCH] fix(canary-phase10): escape geo wrapping parens for MarkdownV2 before rollup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit catch (code-reviewer agent). formatGeo emitted literal `(` and `)` around the geo block (e.g. `(Toronto, CA)`), but `(` and `)` are in the MarkdownV2 reserved set when used outside `[text](url)` link delimiter positions. Telegram would reject the entire message with `Bad Request: can't parse entities: Character '(' is reserved`. Fix: write the wrapping parens as `\(` and `\)` literals in the three parens-construction switch arms. The dynamic field values inside still flow through EscapeMD as before. The `[View full event timeline](url)` inline link is left alone — its `(` `)` ARE link delimiters and per V2 rules must NOT be escaped (only `)` and `\` inside the URL portion need escapes, and our manage URL has neither). Regression test: TestSender_Send_MessageContainsKeyFields now asserts `require.Contains(text, \\(Toronto, CA\\))` so substring matching can't hide future regressions on the wrapping parens. --- .../backend/internal/notify/telegram/sender.go | 6 +++--- .../backend/internal/notify/telegram/sender_test.go | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/PROJECTS/beginner/canary-token-generator/backend/internal/notify/telegram/sender.go b/PROJECTS/beginner/canary-token-generator/backend/internal/notify/telegram/sender.go index 80c49c46..49de6952 100644 --- a/PROJECTS/beginner/canary-token-generator/backend/internal/notify/telegram/sender.go +++ b/PROJECTS/beginner/canary-token-generator/backend/internal/notify/telegram/sender.go @@ -249,11 +249,11 @@ func formatGeo(evt *event.Event) string { var parens string switch { case city != "" && country != "": - parens = "(" + EscapeMD(city) + ", " + EscapeMD(country) + ")" + parens = `\(` + EscapeMD(city) + ", " + EscapeMD(country) + `\)` case country != "": - parens = "(" + EscapeMD(country) + ")" + parens = `\(` + EscapeMD(country) + `\)` case city != "": - parens = "(" + EscapeMD(city) + ")" + parens = `\(` + EscapeMD(city) + `\)` } if parens == "" && asnOrg == "" { return "" diff --git a/PROJECTS/beginner/canary-token-generator/backend/internal/notify/telegram/sender_test.go b/PROJECTS/beginner/canary-token-generator/backend/internal/notify/telegram/sender_test.go index d83c8c87..02411320 100644 --- a/PROJECTS/beginner/canary-token-generator/backend/internal/notify/telegram/sender_test.go +++ b/PROJECTS/beginner/canary-token-generator/backend/internal/notify/telegram/sender_test.go @@ -186,8 +186,11 @@ func TestSender_Send_MessageContainsKeyFields(t *testing.T) { ) require.Contains(t, text, "envfile") require.Contains(t, text, `203\.0\.113\.45`, "IP dots escaped") - require.Contains(t, text, "Toronto") - require.Contains(t, text, "CA") + require.Contains(t, + text, + `\(Toronto, CA\)`, + "geo wrapping parens escaped (V2 reserved chars)", + ) require.Contains(t, text, `Cloudflare, Inc\.`, "asn_org . escaped") require.Contains(t, text, "View full event timeline", "manage link present") require.Contains(t,