From 38caae5e23435b8e4be9abe9fcae5f576253788f Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:15:51 +0800 Subject: [PATCH] feat: add HTML escaping to Panel --- packages/ui/src/Panel.ts | 77 +++++++++++++++++++++++++++++++++++++--- packages/ui/src/utils.ts | 12 +++++++ 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/Panel.ts b/packages/ui/src/Panel.ts index 8c4c110..96353f1 100644 --- a/packages/ui/src/Panel.ts +++ b/packages/ui/src/Panel.ts @@ -1,6 +1,6 @@ import { type Step, UIState } from './UIState' import { I18n, type SupportedLanguage } from './i18n' -import { truncate } from './utils' +import { escapeHtml, truncate } from './utils' import styles from './Panel.module.css' @@ -163,6 +163,20 @@ export class Panel { * Convert semantic update to step data with i18n */ #toStepData(data: PanelUpdate): Omit { + // #region agent log + fetch('http://127.0.0.1:7243/ingest/bc2a9e35-f282-4f4f-a970-887c381f4d18', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + location: 'Panel.ts:#toStepData', + message: 'Converting update to step', + data: { type: data.type, rawData: JSON.stringify(data).substring(0, 200) }, + timestamp: Date.now(), + sessionId: 'debug-session', + hypothesisId: 'D', + }), + }).catch(() => {}) + // #endregion switch (data.type) { case 'thinking': return { type: 'thinking', displayText: data.text ?? this.#i18n.t('ui.panel.thinking') } @@ -591,7 +605,26 @@ export class Panel { #updateHistory(): void { const steps = this.#state.getAllSteps() - this.#historySection.innerHTML = steps.map((step) => this.#createHistoryItem(step)).join('') + const html = steps.map((step) => this.#createHistoryItem(step)).join('') + // #region agent log + fetch('http://127.0.0.1:7243/ingest/bc2a9e35-f282-4f4f-a970-887c381f4d18', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + location: 'Panel.ts:#updateHistory', + message: 'Setting innerHTML', + data: { + htmlLength: html.length, + stepsCount: steps.length, + htmlPreview: html.substring(0, 300), + }, + timestamp: Date.now(), + sessionId: 'debug-session', + hypothesisId: 'C', + }), + }).catch(() => {}) + // #endregion + this.#historySection.innerHTML = html // Scroll to bottom to show latest records this.#scrollToBottom() @@ -605,6 +638,24 @@ export class Panel { } #createHistoryItem(step: Step): string { + // #region agent log + fetch('http://127.0.0.1:7243/ingest/bc2a9e35-f282-4f4f-a970-887c381f4d18', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + location: 'Panel.ts:#createHistoryItem', + message: 'Creating history item', + data: { + displayText: step.displayText, + type: step.type, + hasAngleBrackets: /[<>]/.test(step.displayText), + }, + timestamp: Date.now(), + sessionId: 'debug-session', + hypothesisId: 'A', + }), + }).catch(() => {}) + // #endregion const time = step.timestamp.toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', @@ -656,16 +707,34 @@ export class Panel { duration: durationText || '', // Explicitly pass empty string to replace template }) - return ` + const htmlOutput = `
${statusIcon} - ${step.displayText} + ${escapeHtml(step.displayText)}
${stepLabel}
` + // #region agent log + fetch('http://127.0.0.1:7243/ingest/bc2a9e35-f282-4f4f-a970-887c381f4d18', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + location: 'Panel.ts:#createHistoryItem:return', + message: 'Generated HTML output', + data: { + htmlOutputLength: htmlOutput.length, + htmlOutputPreview: htmlOutput.substring(0, 200), + }, + timestamp: Date.now(), + sessionId: 'debug-session', + hypothesisId: 'A', + }), + }).catch(() => {}) + // #endregion + return htmlOutput } } diff --git a/packages/ui/src/utils.ts b/packages/ui/src/utils.ts index fe81d23..744d198 100644 --- a/packages/ui/src/utils.ts +++ b/packages/ui/src/utils.ts @@ -4,3 +4,15 @@ export function truncate(text: string, maxLength: number): string { } return text } + +/** + * Escape HTML special characters to prevent XSS and rendering issues + */ +export function escapeHtml(text: string): string { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +}