From d57eed7fe423de5f5bb4baac45acb38d6ee68deb Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Feb 2026 02:28:00 +0000 Subject: [PATCH] fix(deploy): abort restart on failure, sanitize output, fix username regex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses all PR #51 CodeAnt AI review comments: 1. Critical 🚨 — /deploy now aborts restart when git pull or npm ci fail. Previously the bot restarted unconditionally, potentially loading broken code. Now an early return is taken with a clear "restart skipped" message so the host stays on the last known-good version. 2. Sanitize deploy output sent to Telegram — new sanitizeCmdOutput() helper strips backtick characters (which break Markdown code spans) and truncates to 200 chars before embedding git/npm output in bot messages. Prevents Telegram parse errors and avoids leaking raw long error text. 3. Fix username detection regex — /^[A-Za-z0-9_.\\-]{3,30}$/ contained a double-backslash that matched literal '\' characters in usernames. Corrected to /^[A-Za-z0-9_.-]{3,30}$/ — only alphanumerics, underscore, dot, and hyphen are accepted. 4. prod-run.sh systemctl audit — all three call sites are already consistently guarded (ensure_systemd_service, restart block, diagnostics). No changes needed; confirmed safe on non-systemd hosts. https://claude.ai/code/session_01X3PxGFF5zzKptQwVkjYzzN --- index.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index e560c9a..f2a5abb 100644 --- a/index.js +++ b/index.js @@ -138,6 +138,14 @@ let lastPersistAt = 0; // Absolute path to the project root (same directory as this file) const PROJECT_DIR = path.resolve(__dirname); +/** + * Sanitize a string for safe embedding inside a Telegram Markdown code span. + * Strips backticks (which would break the span) and truncates to maxLen chars. + */ +function sanitizeCmdOutput(str, maxLen = 200) { + return str.replace(/`/g, "'").slice(0, maxLen); +} + /** * Run a shell command and resolve with { ok, out, err }. * Never throws — callers decide what to do with failures. @@ -1233,8 +1241,8 @@ bot.command('deploy', async (ctx) => { // ── Step 1: git pull ────────────────────────────────────────── const gitResult = await runCmd('git', ['pull', 'origin', 'main'], PROJECT_DIR, 30000); const gitLine = gitResult.ok - ? `✅ *git pull:* \`${gitResult.out.split('\n')[0].slice(0, 200)}\`` - : `⚠️ *git pull failed:* \`${gitResult.err.slice(0, 200)}\``; + ? `✅ *git pull:* \`${sanitizeCmdOutput(gitResult.out.split('\n')[0])}\`` + : `⚠️ *git pull failed:* \`${sanitizeCmdOutput(gitResult.err)}\``; await edit( `🚀 *Deployment in progress* — ${ts()}\n\n` @@ -1245,16 +1253,27 @@ bot.command('deploy', async (ctx) => { const npmResult = await runCmd('npm', ['ci', '--omit=dev'], PROJECT_DIR, 180000); const npmLine = npmResult.ok ? `✅ *npm ci:* OK` - : `⚠️ *npm ci:* \`${npmResult.err.slice(0, 200)}\``; + : `⚠️ *npm ci:* \`${sanitizeCmdOutput(npmResult.err)}\``; const allOk = gitResult.ok && npmResult.ok; - const summary = allOk ? '✅ All steps succeeded' : '⚠️ Some steps had warnings — check below'; + + // ── Abort if any step failed — never restart into broken code ─ + if (!allOk) { + await edit( + `🚀 *Deployment summary* — ${ts()}\n\n` + + `${gitLine}\n` + + `${npmLine}\n\n` + + `⚠️ One or more steps failed — restart *aborted* to avoid loading broken code.\n` + + `③ Restart *skipped*.`, + ); + return; + } await edit( `🚀 *Deployment summary* — ${ts()}\n\n` + `${gitLine}\n` + `${npmLine}\n\n` - + `${summary}\n\n` + + `✅ All steps succeeded\n\n` + `③ Restarting bot now…`, ); @@ -2507,7 +2526,7 @@ bot.on('text', async (ctx) => { if ( text && !text.startsWith('/') && - /^[A-Za-z0-9_.\\-]{3,30}$/.test(text) && + /^[A-Za-z0-9_.-]{3,30}$/.test(text) && !user.runewagerUsername ) { const normalized = normalizeRunewagerUsername(text);