Skip to content

ref(cli): Escape Markdown with the shared toolkit-core builders - #1805

Merged
BYK merged 5 commits into
mainfrom
ref/cli-shared-markdown
Nov 2, 2026
Merged

BYK merged 5 commits into
mainfrom
ref/cli-shared-markdown

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #1795. The CLI's three escape helpers are now the shared builders in @sentry/toolkit-core/markdown, so CLI and MCP escape untrusted values the same way, checked by property tests against the same marked version the CLI renders with.

CLI helper now was
escapeMarkdownInline text escaped \ * _ [ ] < >`
escapeMarkdownCell tableCell (new): text plus escaped pipes escaped \ | < > and newlines only
safeCodeSpan inlineCode single-backtick span, ` → ˋ, | → │

The names stay, so the ~220 call sites don't change. (text would also shadow a lot of local text variables.)

tableCell escapes each | after text has doubled every backslash, so every \| is an escaped pipe, never an escaped backslash followed by a cell delimiter. New property tests put it in a two-column GFM table and check that, with both marked and micromark, the value comes back intact and the next cell is untouched. They fail on "|" if the pipe escape is removed.

Behavior changes in CLI output

  • Cells now escape emphasis, links, code spans, and line-start syntax, not just pipes and angle brackets. A dashboard or monitor name like *prod* no longer renders in italics.
  • Code spans keep backticks and pipes instead of swapping in look-alike characters. Pipes in key-value tables still become │: mdKvTable does that for table structure.
  • Underscores inside words stay unescaped (hello_world).
  • Autolinks: URLs in escaped text render as plain text, not clickable OSC 8 links. Untrusted text shouldn't create links. Deliberate links still use explicit link syntax, and most terminals detect plain URLs on their own. I kept renderOneInline's escape-stripping for autolinks, because raw Markdown from other sources can still reach it.
  • text trims a value and puts it on one line. For the four multi-line callers there's a new escapeMarkdownLines, which keeps line breaks and up to three spaces of indent: blockquoted issue and log messages, cli fix instructions, and credential errors.

Fixes along the way

  • mdKvTable turned an escaped \| into \│, so a backslash showed. It now drops the escape. Even runs of backslashes are kept.
  • Replay tags and breadcrumb labels put escaped text inside hand-written backticks, so the backslashes showed. They now use safeCodeSpan.
  • The offline-downgrade message passed " (offline, from cache)" through the escaper, and text would trim its leading space. It's a plain literal now.

Verification: toolkit-core 372 tests, mcp-core 1897 (6 skipped), CLI 13002 passed. tsc and lint (including the workspace dependency check) are clean. Six CLI tests fail in my sandbox with or without this change: permission/readonly cases (EACCES, EPERM, readonly DB, unreadable file, lock) that need a non-privileged user. CI runs them as one.

via @BYK.

--

View Session in Sentry

The CLI's escapeMarkdownInline, escapeMarkdownCell, and safeCodeSpan are
now toolkit-core's text, tableCell (new), and inlineCode, so the CLI and
MCP escape untrusted values the same way, verified by property tests
against the same marked version the CLI renders with.

Behavior changes in CLI output:
- Cells escape emphasis, links, and the rest of inline syntax, not only
  pipes and angle brackets.
- Code spans keep backticks and pipes instead of replacing them with
  look-alike characters.
- Underscores inside words stay unescaped.
- URLs in escaped text render as plain text rather than autolinks.

escapeMarkdownLines keeps line breaks for the multi-line values
(blockquoted messages, fix instructions, credential errors).
mdKvTable no longer shows the backslash of an escaped pipe. Replay tags
and breadcrumb labels use code spans instead of escaped text inside
backticks, where the backslashes showed.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Nov 2, 2026 10:01pm UTC

Request Review

@github-actions github-actions Bot added the risk: medium PR risk score: medium label Nov 2, 2026
…yntax

tableCell escaped pipes in a second replace after text. That was correct,
since text had already doubled every backslash, but CodeQL flagged it as
incomplete escaping. Both helpers now share one escape pass, and tableCell
adds the pipe to the pattern.
mdKvTable dropped any backslash before a pipe, including a literal one
inside a code span (culprit, location) or in a raw value. It now scans
escapes, code spans, and pipes in order: only an escaped pipe loses its
backslash, and code-span content keeps every backslash. Log custom
attribute names and values are now escaped like other API data.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3f8880d. Configure here.

Comment thread packages/cli/src/lib/formatters/log.ts Resolved
Comment thread packages/cli/src/lib/formatters/log.ts Resolved
The extraFields fallback in log details now escapes attribute names and
values like the allAttributes path. mdKvTable now runs labels through the
same cell sanitizer as values, so a pipe in a label cannot split the row.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
lines.push(
`| **${label}** | ${value.replace(/\n/g, " ").replace(/\|/g, "\u2502")} |`,
);
lines.push(`| **${kvTableCell(label)}** | ${kvTableCell(value)} |`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really feel like we should consider using helpers and AST to construct proper markdown outputs at this point. Is this not feasible?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feasible, yes. I'd do it as a follow-up rather than in this PR. I tried it quickly:

  • It works with our renderer. I built an mdast tree (a table with untrusted text, strong, and inline code containing |, `, *, [, <, \, and a URL), serialized it with mdast-util-to-markdown + gfmToMarkdown(), and parsed it with the CLI's marked. Every cell came back as the literal input. The serializer escapes pipes properly, so kvTableCell and the │ replacement could go away.
  • Cost: both packages are already in the lockfile (mdast-util-gfm is a toolkit-core devDependency). Bundled and minified they add about 63 KB (21 KB gzipped) to the CLI.
  • Scope: about 60 CLI source files and roughly 430 call sites build Markdown from strings today, and colorTag (<red>…</red>) would need a custom node or html nodes. That's too much to fold into a PR that's meant to share escaping.

Suggested path: add mdast-backed builders (kv table, table row, paragraph) to @sentry/toolkit-core/markdown, cover them with the existing marked + micromark property tests, then move formatters over in a few PRs, with mdKvTable first since it has the most hand-written escaping. Want me to open that as a stacked follow-up?

Drop the escapeMarkdownCell, escapeMarkdownInline, and safeCodeSpan
aliases. Call sites now import tableCell, text, and inlineCode from
@sentry/toolkit-core/markdown, as MCP does.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
@github-actions github-actions Bot removed the risk: medium PR risk score: medium label Nov 2, 2026
@BYK
BYK enabled auto-merge (squash) November 2, 2026 22:11
@BYK
BYK merged commit 38034b8 into main Nov 2, 2026
28 of 30 checks passed
@BYK
BYK deleted the ref/cli-shared-markdown branch November 2, 2026 22:15
sentry-junior Bot added a commit that referenced this pull request Nov 2, 2026
Resolve import conflicts with #1805 (shared toolkit-core Markdown
builders) by keeping both the valibot schema imports and the new
markdown helper imports. In release create's dry-run output, use
inlineCode() from main on the now string-typed version.

Co-Authored-By: Junior (Claude) <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
Preview — fae38c83 Deployed Nov 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants