Plugin Directory

Changeset 3717578


Ignore:
Timestamp:
09/28/2026 04:26:32 PM (4 weeks ago)
Author:
Mosamlife
Message:

Release 0.61.151

Location:
fleet-agent-site-manager
Files:
8 edited
1 copied

Legend:

Unmodified
Added
Removed
  • fleet-agent-site-manager/tags/0.61.151/fleet-agent-site-manager.php

    r3717084 r3717578  
    44 * Plugin URI:        https://github.com/mosamlife/wpmgr
    55 * Description:        Connects this WordPress site to a WPMgr control plane for backups, updates, monitoring, and security scanning.
    6  * Version:           0.61.150
     6 * Version:           0.61.151
    77 * Requires at least: 6.2
    88 * Requires PHP:      8.1
    … …  
    2121}
    2222
    23 define('WPMGR_AGENT_VERSION', '0.61.150');
     23define('WPMGR_AGENT_VERSION', '0.61.151');
    2424define('WPMGR_WPORG_BUILD', true);
    2525
  • fleet-agent-site-manager/tags/0.61.151/includes/class-router.php

    r3717084 r3717578  
    7070     */
    7171    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).';
    7280
    7381    private Connector $connector;
    … …  
    177185            // already get from the 403 itself.
    178186            $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            }
    180198            return $this->forbidden($category);
    181199        }
    … …  
    280298            // into a minute.
    281299            //
    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            }
    293320
    294321            // The response is transmitted, stored by the control plane and
    … …  
    470497    {
    471498        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        );
    472585    }
    473586
    … …  
    719832     * The exception message as the local debug log line records it.
    720833     *
    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.
    727852     *
    728853     * @param string $msg Raw exception message.
  • fleet-agent-site-manager/tags/0.61.151/includes/support/class-debug-log.php

    r3636102 r3717578  
    2727{
    2828    /**
     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    /**
    2947     * Write a diagnostic line, but only when debugging is enabled.
    3048     *
    … …  
    3452    public static function write(string $message): void
    3553    {
    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()) {
    4155            return;
    4256        }
  • fleet-agent-site-manager/tags/0.61.151/readme.txt

    r3717084 r3717578  
    55Tested up to: 7.1
    66Requires PHP: 8.1
    7 Stable tag: 0.61.150
     7Stable tag: 0.61.151
    88License: MIT
    99License URI: https://opensource.org/licenses/MIT
    … …  
    286286
    287287The 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.
    288291
    289292= 0.61.150 =
    … …  
    463466== Upgrade Notice ==
    464467
     468= 0.61.151 =
     469Router 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
    465471= 0.61.150 =
    466472Command 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  
    44 * Plugin URI:        https://github.com/mosamlife/wpmgr
    55 * Description:        Connects this WordPress site to a WPMgr control plane for backups, updates, monitoring, and security scanning.
    6  * Version:           0.61.150
     6 * Version:           0.61.151
    77 * Requires at least: 6.2
    88 * Requires PHP:      8.1
    … …  
    2121}
    2222
    23 define('WPMGR_AGENT_VERSION', '0.61.150');
     23define('WPMGR_AGENT_VERSION', '0.61.151');
    2424define('WPMGR_WPORG_BUILD', true);
    2525
  • fleet-agent-site-manager/trunk/includes/class-router.php

    r3717084 r3717578  
    7070     */
    7171    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).';
    7280
    7381    private Connector $connector;
    … …  
    177185            // already get from the 403 itself.
    178186            $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            }
    180198            return $this->forbidden($category);
    181199        }
    … …  
    280298            // into a minute.
    281299            //
    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            }
    293320
    294321            // The response is transmitted, stored by the control plane and
    … …  
    470497    {
    471498        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        );
    472585    }
    473586
    … …  
    719832     * The exception message as the local debug log line records it.
    720833     *
    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.
    727852     *
    728853     * @param string $msg Raw exception message.
  • fleet-agent-site-manager/trunk/includes/support/class-debug-log.php

    r3636102 r3717578  
    2727{
    2828    /**
     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    /**
    2947     * Write a diagnostic line, but only when debugging is enabled.
    3048     *
    … …  
    3452    public static function write(string $message): void
    3553    {
    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()) {
    4155            return;
    4256        }
  • fleet-agent-site-manager/trunk/readme.txt

    r3717084 r3717578  
    55Tested up to: 7.1
    66Requires PHP: 8.1
    7 Stable tag: 0.61.150
     7Stable tag: 0.61.151
    88License: MIT
    99License URI: https://opensource.org/licenses/MIT
    … …  
    286286
    287287The 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.
    288291
    289292= 0.61.150 =
    … …  
    463466== Upgrade Notice ==
    464467
     468= 0.61.151 =
     469Router 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
    465471= 0.61.150 =
    466472Command 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.