Changeset 3717578
- Timestamp:
- 09/28/2026 04:26:32 PM (4 weeks ago)
- Location:
- fleet-agent-site-manager
- Files:
-
- 8 edited
- 1 copied
-
tags/0.61.151 (copied) (copied from fleet-agent-site-manager/trunk )
-
tags/0.61.151/fleet-agent-site-manager.php (modified) (2 diffs)
-
tags/0.61.151/includes/class-router.php (modified) (5 diffs)
-
tags/0.61.151/includes/support/class-debug-log.php (modified) (2 diffs)
-
tags/0.61.151/readme.txt (modified) (3 diffs)
-
trunk/fleet-agent-site-manager.php (modified) (2 diffs)
-
trunk/includes/class-router.php (modified) (5 diffs)
-
trunk/includes/support/class-debug-log.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
fleet-agent-site-manager/tags/0.61.151/fleet-agent-site-manager.php
r3717084 r3717578 4 4 * Plugin URI: https://github.com/mosamlife/wpmgr 5 5 * Description: Connects this WordPress site to a WPMgr control plane for backups, updates, monitoring, and security scanning. 6 * Version: 0.61.15 06 * Version: 0.61.151 7 7 * Requires at least: 6.2 8 8 * Requires PHP: 8.1 … … 21 21 } 22 22 23 define('WPMGR_AGENT_VERSION', '0.61.15 0');23 define('WPMGR_AGENT_VERSION', '0.61.151'); 24 24 define('WPMGR_WPORG_BUILD', true); 25 25 -
fleet-agent-site-manager/tags/0.61.151/includes/class-router.php
r3717084 r3717578 70 70 */ 71 71 private const REASON_WITHHELD = '(reason withheld: redaction could not complete)'; 72 73 /** 74 * What a router log line becomes when escapeControlChars() cannot 75 * complete. Fixed and self-contained — never built from any part of the 76 * line that failed to escape, because that line is exactly the untrusted 77 * value the pass could not make safe to write. 78 */ 79 private const LOG_LINE_WITHHELD = 'WPMgr Agent: log line withheld (control-character escaping could not complete).'; 72 80 73 81 private Connector $connector; … … 177 185 // already get from the 403 itself. 178 186 $category = $this->classifyTokenError($e->getMessage()); 179 \WPMgr\Agent\Support\DebugLog::write('WPMgr Agent: command authorize failed: command=' . $command . ' category=' . $category . ' reason=' . $e->getMessage()); 187 // Build and escape the line only when the channel would actually 188 // write it — escapeControlChars() runs a preg pass over the whole 189 // line, and there is no reason to pay for that on a production 190 // install with debug logging off. 191 if (\WPMgr\Agent\Support\DebugLog::isEnabled()) { 192 \WPMgr\Agent\Support\DebugLog::write( 193 self::escapeControlChars( 194 'WPMgr Agent: command authorize failed: command=' . $command . ' category=' . $category . ' reason=' . $e->getMessage() 195 ) ?? self::LOG_LINE_WITHHELD 196 ); 197 } 180 198 return $this->forbidden($category); 181 199 } … … 280 298 // into a minute. 281 299 // 282 // One failure is one log line. Line breaks in it are written as the 283 // two-character sequences \r and \n: the text survives, and nothing 284 // in it can start a log line of its own. 285 \WPMgr\Agent\Support\DebugLog::write( 286 self::foldLineBreaks( 287 'WPMgr Agent: command failed: command=' . $name 288 . ' class=' . $class 289 . ' at=' . $location 290 . ' reason=' . self::logReason($e->getMessage()) 291 ) 292 ); 300 // One failure is one log line. Every control character that could 301 // pass for a line break, hide output on a terminal, or truncate the 302 // line is written as a visible escape by escapeControlChars(): the 303 // text survives, and nothing in it can start a log line of its own 304 // or make part of a real one disappear. 305 // Build and escape the line only when the channel would actually 306 // write it — logReason() and escapeControlChars() each run preg 307 // passes over the whole line, and a huge control-heavy message 308 // must not pay for that on a production install with debug 309 // logging off. 310 if (\WPMgr\Agent\Support\DebugLog::isEnabled()) { 311 \WPMgr\Agent\Support\DebugLog::write( 312 self::escapeControlChars( 313 'WPMgr Agent: command failed: command=' . $name 314 . ' class=' . $class 315 . ' at=' . $location 316 . ' reason=' . self::logReason($e->getMessage()) 317 ) ?? self::LOG_LINE_WITHHELD 318 ); 319 } 293 320 294 321 // The response is transmitted, stored by the control plane and … … 470 497 { 471 498 return strtr($line, ["\r\n" => '\r\n', "\r" => '\r', "\n" => '\n']); 499 } 500 501 /** 502 * Make a router log line safe to write as one line to a plain-text file, 503 * whatever an exception message threw into it. Shared by every router log 504 * line that carries one — the command-failure line and the 505 * authorize-failure line both call this and nothing else. 506 * 507 * CR and LF fold first, via foldLineBreaks(), to the two-character visible 508 * sequences \r and \n. Every other character that a log reader, a 509 * terminal, or PHP's own error_log() could treat specially is then 510 * escaped: the rest of the C0 control range (tab excepted — a literal tab 511 * is harmless and common in a message), DEL, the C1 control range 512 * (U+0080-U+009F, which includes NEL, itself a line break to some 513 * readers), and the two Unicode line/paragraph separators U+2028 and 514 * U+2029, which some terminals and log viewers also treat as breaking a 515 * line. A C0/DEL byte is written as \xHH; the others, which only exist as 516 * multi-byte UTF-8 sequences, are written as \u{HHHH}. 517 * 518 * NUL is included for a second reason beyond "looks like a line break": 519 * PHP's error_log() writes to a C string under the hood, so a raw NUL 520 * silently truncates everything written after it. Escaping it here is 521 * what keeps the rest of the line from being lost. 522 * 523 * Byte-oriented throughout, like every pattern in redactReason(): the 524 * match patterns are literal byte sequences, not a /u pattern, so the 525 * pass runs the same whether or not $line is valid UTF-8, and a stray 526 * byte elsewhere in the line (e.g. inside a CJK character or a truncated 527 * multi-byte sequence) is never mistaken for one of these because the 528 * multi-byte alternatives only match their exact lead-byte-and-successor 529 * shape. 530 * 531 * A C1 control only matches the pattern above in its valid two-byte UTF-8 532 * encoding, \xC2 followed by 0x80-0x9F. A lone invalid byte in that same 533 * numeric range — e.g. a bare 0x85 (NEL) or 0x9B (an 8-bit CSI 534 * introducer), neither preceded by a \xC2 lead byte — is not that 535 * sequence and would otherwise pass through unescaped, and some 536 * terminals and log readers act on such a byte directly regardless of 537 * whether the surrounding text is valid UTF-8. So $line is checked for 538 * UTF-8 validity first (the same preg_match('//u', ...) idiom 539 * validUtf8() uses); on an invalid line every byte >= 0x80 is escaped as 540 * \xHH, in addition to the C0/C1/separator classes above — including any 541 * byte that is part of what would otherwise be a legitimate multi-byte 542 * sequence elsewhere on that same line, since a line that failed 543 * validation cannot be trusted to parse correctly byte-by-byte at all. 544 * A line that IS valid UTF-8 never takes this branch, so ordinary 545 * multi-byte text (CJK, emoji) is left untouched exactly as before. 546 * 547 * Fails closed like the redaction passes it sits next to: null when the 548 * pass cannot complete, so the caller can withhold a fixed marker instead 549 * of writing a line this function was unable to make safe. 550 * 551 * @param string $line Line to escape. 552 * @return string|null Null when the pass did not complete. 553 */ 554 private static function escapeControlChars(string $line): ?string 555 { 556 $line = self::foldLineBreaks($line); 557 558 $pattern = preg_match('//u', $line) === 1 559 ? '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|\xC2[\x80-\x9F]|\xE2\x80[\xA8\xA9]/' 560 : '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|\xC2[\x80-\x9F]|\xE2\x80[\xA8\xA9]|[\x80-\xFF]/'; 561 562 return self::pregCallbackOrNull( 563 $pattern, 564 static function (array $m): string { 565 $seq = $m[0]; 566 567 if (strlen($seq) === 1) { 568 // C0 control (tab, CR, LF excepted), DEL, or — on a line 569 // that failed UTF-8 validation only — any other single 570 // byte >= 0x80 caught by the catch-all alternative. 571 return sprintf('\\x%02X', ord($seq)); 572 } 573 574 if (strlen($seq) === 2) { 575 // C1 control, UTF-8 encoded as \xC2 followed by a byte 576 // that is numerically the codepoint itself (0x80-0x9F). 577 return sprintf('\\u{%04X}', ord($seq[1])); 578 } 579 580 // U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR. 581 return $seq[2] === "\xA8" ? '\\u{2028}' : '\\u{2029}'; 582 }, 583 $line 584 ); 472 585 } 473 586 … … 719 832 * The exception message as the local debug log line records it. 720 833 * 721 * Contract: key material is redacted by the same two passes the response 722 * uses — pass 1 and pass 2 of redactReason(), in that order — and nothing 723 * else is changed. Paths, absolute ones included, stay as thrown: they are 724 * the site owner's own diagnostic, and PHP writes them to the same log. If 725 * either pass cannot complete, the line carries REASON_WITHHELD in place of 726 * the message. 834 * Contract: the log runs the SAME key-material passes the response gets 835 * from redactReason() — redactEncodedRuns() then redactOpaqueRuns(), in 836 * that order — minus the absolute-path rule that sits between them there 837 * and collapses a whole path to "<path>". Without that rule, a path 838 * stays as thrown here, absolute ones included: it is the site owner's 839 * own diagnostic, and PHP writes it to the same log. 840 * 841 * That "stays as thrown" is a default, not a guarantee, and the two 842 * passes decide it the same way they decide it for the response: over 843 * the whole '/'-joined run, not one path segment at a time (see 844 * isEncodedRun()). When a run is judged to be encoded material, the 845 * WHOLE run is redacted — every path segment inside it, not only the 846 * part that made it qualify — because pass 1's alphabet includes '/' and 847 * treats a path and the token appended to it as one candidate. A run 848 * that is not judged encoded material is left exactly as thrown. Control 849 * characters are not this function's job; see escapeControlChars(). If 850 * either pass cannot complete, the line carries REASON_WITHHELD in place 851 * of the message. 727 852 * 728 853 * @param string $msg Raw exception message. -
fleet-agent-site-manager/tags/0.61.151/includes/support/class-debug-log.php
r3636102 r3717578 27 27 { 28 28 /** 29 * Whether this channel would write at all: WordPress debug logging is on 30 * (WP_DEBUG && WP_DEBUG_LOG) or the agent's own WPMGR_DEBUG constant is 31 * truthy. Callers with an expensive message to build — formatting, 32 * escaping — check this first, so that work only ever happens when the 33 * result would actually be written. 34 * 35 * @return bool 36 */ 37 public static function isEnabled(): bool 38 { 39 $wpDebug = defined('WP_DEBUG') && WP_DEBUG 40 && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG; 41 $agentDebug = defined('WPMGR_DEBUG') && WPMGR_DEBUG; 42 43 return $wpDebug || $agentDebug; 44 } 45 46 /** 29 47 * Write a diagnostic line, but only when debugging is enabled. 30 48 * … … 34 52 public static function write(string $message): void 35 53 { 36 $wpDebug = defined('WP_DEBUG') && WP_DEBUG 37 && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG; 38 $agentDebug = defined('WPMGR_DEBUG') && WPMGR_DEBUG; 39 40 if (!$wpDebug && !$agentDebug) { 54 if (!self::isEnabled()) { 41 55 return; 42 56 } -
fleet-agent-site-manager/tags/0.61.151/readme.txt
r3717084 r3717578 5 5 Tested up to: 7.1 6 6 Requires PHP: 8.1 7 Stable tag: 0.61.15 07 Stable tag: 0.61.151 8 8 License: MIT 9 9 License URI: https://opensource.org/licenses/MIT … … 286 286 287 287 The entries below summarize the notable changes since 0.31.1. This project ships frequently and not every intermediate patch release is listed here. Full history: https://github.com/mosamlife/wpmgr/blob/main/CHANGELOG.md 288 289 = 0.61.151 = 290 * Fixed: a router log line that carries an exception message now escapes control characters, so a command failure is always one readable line in the debug log, instead of one that could break across lines or lose part of its own text. What is sent back to the control plane is unchanged. 288 291 289 292 = 0.61.150 = … … 463 466 == Upgrade Notice == 464 467 468 = 0.61.151 = 469 Router log lines now escape control characters, so a command failure always reads as one line in the debug log instead of one that could break apart. What is sent to the control plane is unchanged. 470 465 471 = 0.61.150 = 466 472 Command failures are now diagnosable from the dashboard: the kind of error, where it was thrown, and a redacted reason, plus a matching line in the site's debug log. Also adds a content-update command for changing an existing post's title or body; nothing in the dashboard uses it yet. -
fleet-agent-site-manager/trunk/fleet-agent-site-manager.php
r3717084 r3717578 4 4 * Plugin URI: https://github.com/mosamlife/wpmgr 5 5 * Description: Connects this WordPress site to a WPMgr control plane for backups, updates, monitoring, and security scanning. 6 * Version: 0.61.15 06 * Version: 0.61.151 7 7 * Requires at least: 6.2 8 8 * Requires PHP: 8.1 … … 21 21 } 22 22 23 define('WPMGR_AGENT_VERSION', '0.61.15 0');23 define('WPMGR_AGENT_VERSION', '0.61.151'); 24 24 define('WPMGR_WPORG_BUILD', true); 25 25 -
fleet-agent-site-manager/trunk/includes/class-router.php
r3717084 r3717578 70 70 */ 71 71 private const REASON_WITHHELD = '(reason withheld: redaction could not complete)'; 72 73 /** 74 * What a router log line becomes when escapeControlChars() cannot 75 * complete. Fixed and self-contained — never built from any part of the 76 * line that failed to escape, because that line is exactly the untrusted 77 * value the pass could not make safe to write. 78 */ 79 private const LOG_LINE_WITHHELD = 'WPMgr Agent: log line withheld (control-character escaping could not complete).'; 72 80 73 81 private Connector $connector; … … 177 185 // already get from the 403 itself. 178 186 $category = $this->classifyTokenError($e->getMessage()); 179 \WPMgr\Agent\Support\DebugLog::write('WPMgr Agent: command authorize failed: command=' . $command . ' category=' . $category . ' reason=' . $e->getMessage()); 187 // Build and escape the line only when the channel would actually 188 // write it — escapeControlChars() runs a preg pass over the whole 189 // line, and there is no reason to pay for that on a production 190 // install with debug logging off. 191 if (\WPMgr\Agent\Support\DebugLog::isEnabled()) { 192 \WPMgr\Agent\Support\DebugLog::write( 193 self::escapeControlChars( 194 'WPMgr Agent: command authorize failed: command=' . $command . ' category=' . $category . ' reason=' . $e->getMessage() 195 ) ?? self::LOG_LINE_WITHHELD 196 ); 197 } 180 198 return $this->forbidden($category); 181 199 } … … 280 298 // into a minute. 281 299 // 282 // One failure is one log line. Line breaks in it are written as the 283 // two-character sequences \r and \n: the text survives, and nothing 284 // in it can start a log line of its own. 285 \WPMgr\Agent\Support\DebugLog::write( 286 self::foldLineBreaks( 287 'WPMgr Agent: command failed: command=' . $name 288 . ' class=' . $class 289 . ' at=' . $location 290 . ' reason=' . self::logReason($e->getMessage()) 291 ) 292 ); 300 // One failure is one log line. Every control character that could 301 // pass for a line break, hide output on a terminal, or truncate the 302 // line is written as a visible escape by escapeControlChars(): the 303 // text survives, and nothing in it can start a log line of its own 304 // or make part of a real one disappear. 305 // Build and escape the line only when the channel would actually 306 // write it — logReason() and escapeControlChars() each run preg 307 // passes over the whole line, and a huge control-heavy message 308 // must not pay for that on a production install with debug 309 // logging off. 310 if (\WPMgr\Agent\Support\DebugLog::isEnabled()) { 311 \WPMgr\Agent\Support\DebugLog::write( 312 self::escapeControlChars( 313 'WPMgr Agent: command failed: command=' . $name 314 . ' class=' . $class 315 . ' at=' . $location 316 . ' reason=' . self::logReason($e->getMessage()) 317 ) ?? self::LOG_LINE_WITHHELD 318 ); 319 } 293 320 294 321 // The response is transmitted, stored by the control plane and … … 470 497 { 471 498 return strtr($line, ["\r\n" => '\r\n', "\r" => '\r', "\n" => '\n']); 499 } 500 501 /** 502 * Make a router log line safe to write as one line to a plain-text file, 503 * whatever an exception message threw into it. Shared by every router log 504 * line that carries one — the command-failure line and the 505 * authorize-failure line both call this and nothing else. 506 * 507 * CR and LF fold first, via foldLineBreaks(), to the two-character visible 508 * sequences \r and \n. Every other character that a log reader, a 509 * terminal, or PHP's own error_log() could treat specially is then 510 * escaped: the rest of the C0 control range (tab excepted — a literal tab 511 * is harmless and common in a message), DEL, the C1 control range 512 * (U+0080-U+009F, which includes NEL, itself a line break to some 513 * readers), and the two Unicode line/paragraph separators U+2028 and 514 * U+2029, which some terminals and log viewers also treat as breaking a 515 * line. A C0/DEL byte is written as \xHH; the others, which only exist as 516 * multi-byte UTF-8 sequences, are written as \u{HHHH}. 517 * 518 * NUL is included for a second reason beyond "looks like a line break": 519 * PHP's error_log() writes to a C string under the hood, so a raw NUL 520 * silently truncates everything written after it. Escaping it here is 521 * what keeps the rest of the line from being lost. 522 * 523 * Byte-oriented throughout, like every pattern in redactReason(): the 524 * match patterns are literal byte sequences, not a /u pattern, so the 525 * pass runs the same whether or not $line is valid UTF-8, and a stray 526 * byte elsewhere in the line (e.g. inside a CJK character or a truncated 527 * multi-byte sequence) is never mistaken for one of these because the 528 * multi-byte alternatives only match their exact lead-byte-and-successor 529 * shape. 530 * 531 * A C1 control only matches the pattern above in its valid two-byte UTF-8 532 * encoding, \xC2 followed by 0x80-0x9F. A lone invalid byte in that same 533 * numeric range — e.g. a bare 0x85 (NEL) or 0x9B (an 8-bit CSI 534 * introducer), neither preceded by a \xC2 lead byte — is not that 535 * sequence and would otherwise pass through unescaped, and some 536 * terminals and log readers act on such a byte directly regardless of 537 * whether the surrounding text is valid UTF-8. So $line is checked for 538 * UTF-8 validity first (the same preg_match('//u', ...) idiom 539 * validUtf8() uses); on an invalid line every byte >= 0x80 is escaped as 540 * \xHH, in addition to the C0/C1/separator classes above — including any 541 * byte that is part of what would otherwise be a legitimate multi-byte 542 * sequence elsewhere on that same line, since a line that failed 543 * validation cannot be trusted to parse correctly byte-by-byte at all. 544 * A line that IS valid UTF-8 never takes this branch, so ordinary 545 * multi-byte text (CJK, emoji) is left untouched exactly as before. 546 * 547 * Fails closed like the redaction passes it sits next to: null when the 548 * pass cannot complete, so the caller can withhold a fixed marker instead 549 * of writing a line this function was unable to make safe. 550 * 551 * @param string $line Line to escape. 552 * @return string|null Null when the pass did not complete. 553 */ 554 private static function escapeControlChars(string $line): ?string 555 { 556 $line = self::foldLineBreaks($line); 557 558 $pattern = preg_match('//u', $line) === 1 559 ? '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|\xC2[\x80-\x9F]|\xE2\x80[\xA8\xA9]/' 560 : '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|\xC2[\x80-\x9F]|\xE2\x80[\xA8\xA9]|[\x80-\xFF]/'; 561 562 return self::pregCallbackOrNull( 563 $pattern, 564 static function (array $m): string { 565 $seq = $m[0]; 566 567 if (strlen($seq) === 1) { 568 // C0 control (tab, CR, LF excepted), DEL, or — on a line 569 // that failed UTF-8 validation only — any other single 570 // byte >= 0x80 caught by the catch-all alternative. 571 return sprintf('\\x%02X', ord($seq)); 572 } 573 574 if (strlen($seq) === 2) { 575 // C1 control, UTF-8 encoded as \xC2 followed by a byte 576 // that is numerically the codepoint itself (0x80-0x9F). 577 return sprintf('\\u{%04X}', ord($seq[1])); 578 } 579 580 // U+2028 LINE SEPARATOR or U+2029 PARAGRAPH SEPARATOR. 581 return $seq[2] === "\xA8" ? '\\u{2028}' : '\\u{2029}'; 582 }, 583 $line 584 ); 472 585 } 473 586 … … 719 832 * The exception message as the local debug log line records it. 720 833 * 721 * Contract: key material is redacted by the same two passes the response 722 * uses — pass 1 and pass 2 of redactReason(), in that order — and nothing 723 * else is changed. Paths, absolute ones included, stay as thrown: they are 724 * the site owner's own diagnostic, and PHP writes them to the same log. If 725 * either pass cannot complete, the line carries REASON_WITHHELD in place of 726 * the message. 834 * Contract: the log runs the SAME key-material passes the response gets 835 * from redactReason() — redactEncodedRuns() then redactOpaqueRuns(), in 836 * that order — minus the absolute-path rule that sits between them there 837 * and collapses a whole path to "<path>". Without that rule, a path 838 * stays as thrown here, absolute ones included: it is the site owner's 839 * own diagnostic, and PHP writes it to the same log. 840 * 841 * That "stays as thrown" is a default, not a guarantee, and the two 842 * passes decide it the same way they decide it for the response: over 843 * the whole '/'-joined run, not one path segment at a time (see 844 * isEncodedRun()). When a run is judged to be encoded material, the 845 * WHOLE run is redacted — every path segment inside it, not only the 846 * part that made it qualify — because pass 1's alphabet includes '/' and 847 * treats a path and the token appended to it as one candidate. A run 848 * that is not judged encoded material is left exactly as thrown. Control 849 * characters are not this function's job; see escapeControlChars(). If 850 * either pass cannot complete, the line carries REASON_WITHHELD in place 851 * of the message. 727 852 * 728 853 * @param string $msg Raw exception message. -
fleet-agent-site-manager/trunk/includes/support/class-debug-log.php
r3636102 r3717578 27 27 { 28 28 /** 29 * Whether this channel would write at all: WordPress debug logging is on 30 * (WP_DEBUG && WP_DEBUG_LOG) or the agent's own WPMGR_DEBUG constant is 31 * truthy. Callers with an expensive message to build — formatting, 32 * escaping — check this first, so that work only ever happens when the 33 * result would actually be written. 34 * 35 * @return bool 36 */ 37 public static function isEnabled(): bool 38 { 39 $wpDebug = defined('WP_DEBUG') && WP_DEBUG 40 && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG; 41 $agentDebug = defined('WPMGR_DEBUG') && WPMGR_DEBUG; 42 43 return $wpDebug || $agentDebug; 44 } 45 46 /** 29 47 * Write a diagnostic line, but only when debugging is enabled. 30 48 * … … 34 52 public static function write(string $message): void 35 53 { 36 $wpDebug = defined('WP_DEBUG') && WP_DEBUG 37 && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG; 38 $agentDebug = defined('WPMGR_DEBUG') && WPMGR_DEBUG; 39 40 if (!$wpDebug && !$agentDebug) { 54 if (!self::isEnabled()) { 41 55 return; 42 56 } -
fleet-agent-site-manager/trunk/readme.txt
r3717084 r3717578 5 5 Tested up to: 7.1 6 6 Requires PHP: 8.1 7 Stable tag: 0.61.15 07 Stable tag: 0.61.151 8 8 License: MIT 9 9 License URI: https://opensource.org/licenses/MIT … … 286 286 287 287 The entries below summarize the notable changes since 0.31.1. This project ships frequently and not every intermediate patch release is listed here. Full history: https://github.com/mosamlife/wpmgr/blob/main/CHANGELOG.md 288 289 = 0.61.151 = 290 * Fixed: a router log line that carries an exception message now escapes control characters, so a command failure is always one readable line in the debug log, instead of one that could break across lines or lose part of its own text. What is sent back to the control plane is unchanged. 288 291 289 292 = 0.61.150 = … … 463 466 == Upgrade Notice == 464 467 468 = 0.61.151 = 469 Router log lines now escape control characters, so a command failure always reads as one line in the debug log instead of one that could break apart. What is sent to the control plane is unchanged. 470 465 471 = 0.61.150 = 466 472 Command failures are now diagnosable from the dashboard: the kind of error, where it was thrown, and a redacted reason, plus a matching line in the site's debug log. Also adds a content-update command for changing an existing post's title or body; nothing in the dashboard uses it yet.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)