pnp/cli-microsoft365

Bug report: JSON output corrupts string values containing newlines — getJsonOutput injects a literal backslash (regression from #2807 fix)

Open

#7,397 opened on 2026年6月9日

GitHub で見る
 (1 comment) (0 reactions) (0 assignees)TypeScript (399 forks)auto 404
bughelp wantedneeds research

Repository metrics

Stars
 (1,390 stars)
PR merge metrics
 (PR metrics pending)

説明

Description

Command.getJsonOutput() post-processes JSON.stringify output with a regex intended to escape newlines (comment references #2807):

getJsonOutput(logStatement: any): string {
  return JSON
    .stringify(logStatement, null, 2)
    // replace unescaped newlines with escaped newlines #2807
    .replace(/([^\\])\\n/g, '$1\\\\\\n');
}

JSON.stringify already emits valid JSON (all control characters in string values are escaped), so this regex never has a legitimate match to fix — but its single-character lookbehind cannot see escape context, so it corrupts data: any \n escape inside a string value that is preceded by a non-backslash character gets rewritten, which injects a literal backslash into the parsed value. Since every newline's \n escape is preceded by a normal character (\r\n → the n-escape follows r; bare \n follows the previous data character), this fires on every newline inside every string value of every command's JSON output.

Minimal repro (pure JS, current main logic)

const mangled = JSON.stringify({ b: "line1\r\nline2" })
  .replace(/([^\\])\\n/g, '$1\\\\\\n');
// mangled === '{"b":"line1\\r\\\\\\nline2"}'
JSON.parse(mangled).b;
// → "line1\r\\\nline2"  — a literal backslash injected between CR and LF

Bare-LF values are corrupted the same way ("a\nb""a\<LF>b" after parse).

Real-world impact

Microsoft Graph message bodies (Exchange HTML) are full of CRLF newlines. Reading a message through the CLI returns corrupted content:

m365 request --method post --url "https://graph.microsoft.com/v1.0/me/messages/{id}/createReplyAll"
# → body.content contains a literal "\" before every newline

The same draft fetched with curl and a bearer token comes back clean — on a real reply draft we measured 77 injected backslashes via the CLI vs 0 via curl for byte-identical Graph content. --output json and the default output are equally affected (both route through getJsonOutput).

This is not cosmetic: in a compose flow (createReplyAll → read draft body → PATCH body with quoted history → send), the injected backslashes are sent to recipients — at least one survives Outlook's HTML sanitization as visible \ text in the delivered email.

Steps to reproduce

  1. m365 request --url "https://graph.microsoft.com/v1.0/me/messages/{any-id}?$select=body" for any message whose HTML contains CRLF (virtually all Exchange-generated HTML).
  2. Compare body.content with the same GET via curl/Graph Explorer.

Or run the pure-JS snippet above.

Expected results

JSON output is JSON.stringify's output: parseable, and parsing returns the original data unchanged.

Actual results

Parsing the CLI's JSON output returns string values with a literal \ injected before every newline.

Suggested fix

Remove the .replace(...). The #2807 problem (invalid JSON from _ObjectIdentity_ containing raw control characters) cannot be reproduced through JSON.stringify, which escapes control characters by construction — if some code path emits raw control characters today, the fix belongs there, not in a post-stringify rewrite that corrupts valid output.

Diagnostics

  • CLI for Microsoft 365 version: 11.8.0 (latest at time of filing; logic present in current main)
  • Node.js: v22 (Homebrew), macOS (Darwin 25.5)
  • Shell: zsh

コントリビューターガイド