@@ -8,16 +8,26 @@ import {
88 mergeTransactionAttributes ,
99} from "./semantic-display.js" ;
1010
11- /** Unicode bidirectional override/isolate characters that can reorder terminal output. */
11+ /**
12+ * Characters unsafe for JSON terminal display: C1 control characters
13+ * (U+0080–U+009F, e.g. CSI=U+009B) and Unicode bidirectional overrides.
14+ * `JSON.stringify` only escapes C0 (U+0000–U+001F) per RFC 8259;
15+ * C1 and BiDi pass through unescaped.
16+ */
17+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping C1 control chars from untrusted data
18+ const JSON_UNSAFE_RE = / [ \x80 - \x9f \u200e \u200f \u202a - \u202e \u2066 - \u2069 ] / g;
19+
20+ /** BiDi-only regex for the full `sanitize()` function. */
1221const BIDI_RE = / [ \u200e \u200f \u202a - \u202e \u2066 - \u2069 ] / g;
1322
1423/**
15- * Strip Unicode bidirectional override characters from a string.
16- * Used for JSON output where `JSON.stringify` handles C0/C1 escaping
17- * but BiDi characters pass through and can reorder terminal text.
24+ * Strip C1 control characters and Unicode BiDi overrides from a string.
25+ * Used for JSON output where `JSON.stringify` escapes C0 controls but
26+ * leaves C1 (U+0080–U+009F) and BiDi chars intact — both can cause
27+ * terminal injection when JSON output is displayed in a terminal.
1828 */
1929export function stripBidi ( text : string ) : string {
20- return text . replace ( BIDI_RE , "" ) ;
30+ return text . replace ( JSON_UNSAFE_RE , "" ) ;
2131}
2232
2333/**
@@ -466,10 +476,11 @@ function formatLogJson(
466476 * coding agents and automation tools.
467477 *
468478 * Unlike the human formatters, JSON output uses `stripBidi()` instead of
469- * the full `sanitize()`. `JSON.stringify()` escapes C0/C1 control characters
470- * to `\uXXXX` notation, but Unicode bidirectional override characters pass
471- * through and can reorder terminal text. `stripBidi()` removes those while
472- * preserving the original data for downstream consumers.
479+ * the full `sanitize()`. `JSON.stringify()` escapes C0 control characters
480+ * (U+0000–U+001F) but leaves C1 controls (U+0080–U+009F) and BiDi overrides
481+ * intact. `stripBidi()` strips both, preventing terminal injection when
482+ * JSON output is viewed in a terminal, while preserving the original data
483+ * structure for downstream consumers.
473484 */
474485export function formatItemJson (
475486 itemType : string | undefined ,
0 commit comments