From e1bff8590cec1c50b5c9bc6f79435ec4acbce1b6 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 25 Mar 2026 19:04:15 +0000 Subject: [PATCH] fix(ui): address pr review comments regarding ansi reset vulnerability --- packages/cli/src/ui/utils/TableRenderer.tsx | 9 ++------- packages/cli/src/ui/utils/styleSpans.ts | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/ui/utils/TableRenderer.tsx b/packages/cli/src/ui/utils/TableRenderer.tsx index 9b6a395757..15d69211cf 100644 --- a/packages/cli/src/ui/utils/TableRenderer.tsx +++ b/packages/cli/src/ui/utils/TableRenderer.tsx @@ -171,7 +171,7 @@ export const TableRenderer: React.FC = ({ } // --- Pre-wrap and Optimize Widths --- - const actualColumnWidths = new Array(numColumns).fill(0); + const actualColumnWidths: number[] = new Array(numColumns).fill(0); const wrapAndProcessRow = (row: StyleSpan[][]) => { const rowResult: ProcessedLine[][] = []; @@ -202,11 +202,7 @@ export const TableRenderer: React.FC = ({ const wrappedRows = styledRows.map((row) => wrapAndProcessRow(row)); // Use the TIGHTEST widths that fit the wrapped content + padding - const adjustedWidths = actualColumnWidths.map( - (w) => - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - w + COLUMN_PADDING, - ); + const adjustedWidths = actualColumnWidths.map((w) => w + COLUMN_PADDING); return { wrappedHeaders, wrappedRows, adjustedWidths }; }, [styledHeaders, styledRows, terminalWidth]); @@ -257,7 +253,6 @@ export const TableRenderer: React.FC = ({ isHeader = false, ): React.ReactNode => { const renderedCells = cells.map((cell, index) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const width = adjustedWidths[index] || 0; return renderCell(cell, width, isHeader); }); diff --git a/packages/cli/src/ui/utils/styleSpans.ts b/packages/cli/src/ui/utils/styleSpans.ts index 3d72271090..3f5ebb5cf2 100644 --- a/packages/cli/src/ui/utils/styleSpans.ts +++ b/packages/cli/src/ui/utils/styleSpans.ts @@ -40,7 +40,11 @@ export const parseToStyleSpans = (text: string): StyleSpan[] => { if ( ansiCode === '\x1b[0m' || ansiCode === '\x1b[39m' || - ansiCode === '\x1b[49m' + ansiCode === '\x1b[49m' || + ansiCode === '\x1b[22m' || + ansiCode === '\x1b[23m' || + ansiCode === '\x1b[24m' || + ansiCode === '\x1b[29m' ) { if (ansiCode === '\x1b[0m') { currentAnsiState = []; @@ -54,8 +58,16 @@ export const parseToStyleSpans = (text: string): StyleSpan[] => { // eslint-disable-next-line no-control-regex (c) => !c.match(/\x1b\[4\d(?:;\d+)*m/), ); - } else { - currentAnsiState.push(ansiCode); + } else if (ansiCode === '\x1b[22m') { + currentAnsiState = currentAnsiState.filter( + (c) => c !== '\x1b[1m' && c !== '\x1b[2m', + ); + } else if (ansiCode === '\x1b[23m') { + currentAnsiState = currentAnsiState.filter((c) => c !== '\x1b[3m'); + } else if (ansiCode === '\x1b[24m') { + currentAnsiState = currentAnsiState.filter((c) => c !== '\x1b[4m'); + } else if (ansiCode === '\x1b[29m') { + currentAnsiState = currentAnsiState.filter((c) => c !== '\x1b[9m'); } } else { currentAnsiState.push(ansiCode);