From f68be6db4e6124ab924eed2fb7146e6e0bb42bc5 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 26 Aug 2026 08:45:16 +0200 Subject: [PATCH 1/2] fix: strip ANSI/OSC escape sequences from SSH test and job output Remote shells emit OSC sequences (e.g. \e]11;#RRGGBB\a for background colour) from startup files even during non-interactive SSH commands. These caused the SSH connectivity test to report "failed" despite a successful connection (trim($output) === 'ok' check failed), and appeared as garbled text in job execution output stored in the DB. New AnsiStripper utility class strips CSI, OSC, and Fe escape sequences. Applied in SshTestEndpoint (before success check and response) and in ExecutionFinishEndpoint (before output is written to execution_log). Also fix newline rendering in output display: detail.php and timeline.php used `break-all` without `whitespace-pre-wrap`; HTML collapses \n to spaces in inline elements. Replaced with `whitespace-pre-wrap break-words`. --- CHANGELOG.md | 10 ++++ agent/VERSION | 2 +- .../src/Endpoints/ExecutionFinishEndpoint.php | 3 +- agent/src/Endpoints/SshTestEndpoint.php | 3 +- agent/src/Util/AnsiStripper.php | 49 +++++++++++++++++++ web/VERSION | 2 +- web/templates/cron/detail.php | 4 +- web/templates/timeline.php | 4 +- 8 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 agent/src/Util/AnsiStripper.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f7f92a..9e0ab72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). --- +## [5.2.1] – branch: `fix/ansi-escape-in-ssh-output` + +### Fixed + +- **SSH-Verbindungstest – ANSI/OSC-Escape-Sequenzen:** Remote-Shells senden aus ihren Startup-Dateien (`.bashrc`/`.zshrc`) OSC-Sequenzen (z. B. `\e]11;#RRGGBB\a` zur Hintergrundfarbe) auch bei nicht-interaktiven SSH-Befehlen. Diese landeten bisher ungefiltert im `output`-Feld des SSH-Tests, ließen den Success-Check (`trim($output) === 'ok'`) fehlschlagen und zeigten dem Nutzer fälschlicherweise „Fehlgeschlagen" an — obwohl die Verbindung funktionierte. Neue Hilfsklasse `AnsiStripper` (CSI-, OSC- und Fe-Sequenzen) wird in `SshTestEndpoint` auf die SSH-Ausgabe angewendet. +- **Job-Output – ANSI/OSC-Escape-Sequenzen:** Dieselben Sequenzen wurden via `cron-wrapper.sh` in `execution_log.output` gespeichert und in der UI als Zeichenmüll angezeigt. `ExecutionFinishEndpoint` wendet `AnsiStripper::strip()` beim Einlesen des Outputs an, bevor er in der DB gespeichert wird. +- **Output-Anzeige – Zeilenumbrüche:** Span-Elemente in `detail.php` (Ausführungshistorie) und `timeline.php` verwendeten `break-all` ohne `whitespace-pre-wrap`; HTML kollabiert `\n` in Inline-Elementen zu Leerzeichen. Ersetzt durch `whitespace-pre-wrap break-words` — Zeilenumbrüche werden jetzt korrekt gerendert. + +--- + ## [5.2.0] – branch: `feature/minor-improvements` ### Fixed diff --git a/agent/VERSION b/agent/VERSION index 7cbea07..26d99a2 100644 --- a/agent/VERSION +++ b/agent/VERSION @@ -1 +1 @@ -5.2.0 \ No newline at end of file +5.2.1 diff --git a/agent/src/Endpoints/ExecutionFinishEndpoint.php b/agent/src/Endpoints/ExecutionFinishEndpoint.php index 293cbfa..b66b945 100644 --- a/agent/src/Endpoints/ExecutionFinishEndpoint.php +++ b/agent/src/Endpoints/ExecutionFinishEndpoint.php @@ -50,6 +50,7 @@ use Cronmanager\Agent\Cron\CrontabManager; use Cronmanager\Agent\Notification\MailNotifier; +use Cronmanager\Agent\Util\AnsiStripper; use Cronmanager\Agent\Util\ExitCodeMatcher; use Cronmanager\Agent\Notification\TelegramNotifier; use Monolog\Logger; @@ -146,7 +147,7 @@ public function handle(array $params): void $executionId = (int) $body['execution_id']; $jobId = (int) $body['job_id']; $exitCode = (int) $body['exit_code']; - $output = isset($body['output']) ? (string) $body['output'] : ''; + $output = AnsiStripper::strip(isset($body['output']) ? (string) $body['output'] : ''); $target = isset($body['target']) && is_string($body['target']) && $body['target'] !== '' ? $body['target'] : null; diff --git a/agent/src/Endpoints/SshTestEndpoint.php b/agent/src/Endpoints/SshTestEndpoint.php index 453a9a8..20a8b87 100644 --- a/agent/src/Endpoints/SshTestEndpoint.php +++ b/agent/src/Endpoints/SshTestEndpoint.php @@ -35,6 +35,7 @@ namespace Cronmanager\Agent\Endpoints; use Cronmanager\Agent\Ssh\SshConfigParser; +use Cronmanager\Agent\Util\AnsiStripper; use Monolog\Logger; /** @@ -124,7 +125,7 @@ public function handle(array $params): void $exitCode = 0; exec($command, $output, $exitCode); - $outputText = implode("\n", $output); + $outputText = AnsiStripper::strip(implode("\n", $output)); $success = ($exitCode === 0 && trim($outputText) === 'ok'); $this->logger->info('SshTestEndpoint: SSH probe completed', [ diff --git a/agent/src/Util/AnsiStripper.php b/agent/src/Util/AnsiStripper.php new file mode 100644 index 0000000..1b7da32 --- /dev/null +++ b/agent/src/Util/AnsiStripper.php @@ -0,0 +1,49 @@ + + * @license GNU General Public License version 3 or later + */ + +namespace Cronmanager\Agent\Util; + +/** + * Strips ANSI and OSC terminal escape sequences from a string. + * + * Remote shells often emit escape sequences from their startup files + * (.bashrc/.zshrc) even during non-interactive SSH commands. These + * sequences must be removed before storing or comparing command output. + * + * Handled sequence types: + * - CSI ESC [ … final-byte (colour, cursor, erase, …) + * - OSC ESC ] … BEL|ST (title, background colour, shell integration, …) + * - Fe ESC @-_ (single-character escape sequences) + */ +final class AnsiStripper +{ + /** + * Regular expression that matches all three sequence families above. + * + * Groups: + * 1. CSI: ESC [ + * 2. OSC: ESC ] (BEL | ESC \) + * 3. Fe: ESC followed by a single byte in 0x40–0x5F + */ + private const PATTERN = '/\x1b(?:\[[0-?]*[ -\/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\\\)|[@-_])/'; + + /** + * Remove all ANSI/OSC escape sequences from the given string. + * + * @param string $text Raw text that may contain escape sequences. + * + * @return string Cleaned text with all escape sequences removed. + */ + public static function strip(string $text): string + { + return (string) preg_replace(self::PATTERN, '', $text); + } +} diff --git a/web/VERSION b/web/VERSION index 7cbea07..26d99a2 100644 --- a/web/VERSION +++ b/web/VERSION @@ -1 +1 @@ -5.2.0 \ No newline at end of file +5.2.1 diff --git a/web/templates/cron/detail.php b/web/templates/cron/detail.php index 12fce94..8fc77b0 100644 --- a/web/templates/cron/detail.php +++ b/web/templates/cron/detail.php @@ -565,12 +565,12 @@ class="hidden flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 + class="font-mono text-xs whitespace-pre-wrap break-words"> 200): ?>