From 31a43b15c3b9defa882c1ef4abdf16ca7272510f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Mar 2026 12:40:21 +0000 Subject: [PATCH] =?UTF-8?q?fix(ops):=20three=20targeted=20fixes=20?= =?UTF-8?q?=E2=80=94=20MarkdownV2=20escaping,=20CPU=20tracer=20args,=20jq?= =?UTF-8?q?=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rw_cpu_guard.sh: - Add escape_md_v2() helper: escapes all 20 MarkdownV2 special characters (backslash processed first to prevent double-escaping) - tg_alert(): escape the full composed text (prefix + caller message) via escape_md_v2() before sending; switch parse_mode Markdown → MarkdownV2; raw ${short_cmd} can no longer break message formatting or leak syntax - CPU tracer: switch ps format 'cmd' → 'args' so the full executable path and every argument are captured; combined with the existing $5..$NF loop this gives a complete, delimiter-safe command string in every CSV row runewager_redeploy.sh: - _health_check pretty-print cascade: python3 -m json.tool → jq . → raw; diagnostics never fail when neither pretty-printer is installed https://claude.ai/code/session_01WpEqAiDbpqnBywMjYXJHf4 --- scripts/runewager_redeploy.sh | 8 +++++-- scripts/rw_cpu_guard.sh | 39 ++++++++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/scripts/runewager_redeploy.sh b/scripts/runewager_redeploy.sh index 41067b5..4cbe973 100755 --- a/scripts/runewager_redeploy.sh +++ b/scripts/runewager_redeploy.sh @@ -251,10 +251,14 @@ _health_check() { http_code=$(curl -sS --max-time 3 -o /dev/null -w "%{http_code}" "$HEALTH_URL" 2>/dev/null) || true if [[ "$http_code" == "200" ]]; then ok "Bot healthy at ${HEALTH_URL} (${elapsed}s)" - # Print health payload — degrade gracefully if python3 absent + # Pretty-print health payload. Cascade: python3 → jq → raw. local _payload; _payload=$(curl -sS --max-time 3 "$HEALTH_URL" 2>/dev/null) if command -v python3 >/dev/null 2>&1; then - echo "$_payload" | python3 -m json.tool 2>/dev/null | head -20 | sed 's/^/ /' || echo "$_payload" | head -20 | sed 's/^/ /' + echo "$_payload" | python3 -m json.tool 2>/dev/null | head -20 | sed 's/^/ /' \ + || echo "$_payload" | head -20 | sed 's/^/ /' + elif command -v jq >/dev/null 2>&1; then + echo "$_payload" | jq . 2>/dev/null | head -20 | sed 's/^/ /' \ + || echo "$_payload" | head -20 | sed 's/^/ /' else echo "$_payload" | head -20 | sed 's/^/ /' fi diff --git a/scripts/rw_cpu_guard.sh b/scripts/rw_cpu_guard.sh index 907e185..47d405c 100755 --- a/scripts/rw_cpu_guard.sh +++ b/scripts/rw_cpu_guard.sh @@ -223,6 +223,33 @@ _load_env() { # ─────────────────────────────────── TELEGRAM ALERT ────────────────────────── +# Escape all MarkdownV2 special characters so arbitrary text is safe to insert +# into a Telegram MarkdownV2 message. Must be called before building the text +# that is handed to sendMessage. +escape_md_v2() { + local s="$1" + s="${s//\\/\\\\}" # backslash — must come first + s="${s//_/\\_}" + s="${s//\*/\\*}" + s="${s//\[/\\[}" + s="${s//\]/\\]}" + s="${s//\(/\\(}" + s="${s//\)/\\)}" + s="${s//~/\\~}" + s="${s//\`/\\\`}" + s="${s//>/\\>}" + s="${s//#/\\#}" + s="${s//+/\\+}" + s="${s//-/\\-}" + s="${s//=/\\=}" + s="${s//|/\\|}" + s="${s//\{/\\{}" + s="${s//\}/\\}}" + s="${s//./\\.}" + s="${s//!/\\!}" + printf '%s' "$s" +} + tg_alert() { local msg="$1" [[ -z "$TG_BOT_TOKEN" || -z "$TG_ADMIN_IDS" ]] && return 0 @@ -232,6 +259,9 @@ tg_alert() { (( elapsed < _ALERT_COOLDOWN )) && return 0 _LAST_ALERT_TS=$now + # Escape the full text (prefix + caller message) for MarkdownV2 + local safe_text; safe_text=$(escape_md_v2 "🛡 [rw_cpu_guard] ${msg}") + IFS=',' read -ra _ids <<< "$TG_ADMIN_IDS" local id for id in "${_ids[@]}"; do @@ -240,8 +270,8 @@ tg_alert() { curl -sS --max-time 6 \ "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \ --data-urlencode "chat_id=${id}" \ - --data-urlencode "text=🛡 [rw_cpu_guard] ${msg}" \ - -d "parse_mode=Markdown" \ + --data-urlencode "text=${safe_text}" \ + -d "parse_mode=MarkdownV2" \ >/dev/null 2>&1 || true done } @@ -650,7 +680,10 @@ _run_forensics() { # ── 1-second CPU tracer (catches sub-8s spikes the guard daemon may miss) ─ echo "timestamp,pid,ppid,cpu%,mem%,cmd" > "$trace" ( while true; do - ps -eo pid,ppid,pcpu,pmem,cmd --sort=-pcpu --no-headers 2>/dev/null \ + # Use 'args' (not 'cmd') so the full command path and every argument + # are captured. Fields $5..$NF are joined into one value so commands + # with spaces in their paths or arguments are never truncated. + ps -eo pid,ppid,pcpu,pmem,args --sort=-pcpu --no-headers 2>/dev/null \ | awk -v ts="$(date '+%F %T')" '$3+0 > 15 { cmd = ""; for (i=5; i<=NF; i++) cmd = cmd (i>5 ? " " : "") $i; gsub(/,/, ";", cmd);