Release v6.2.3#85
Conversation
ContextSummary By MatterAI
🔄 What ChangedThis PR releases v6.2.3, focusing on UI refinements for chat components and robust data sanitization. Key changes include adding 🔍 Impact of the Change
📁 Total Files ChangedClick to Expand
🧪 Test Added/RecommendedAdded
Recommended
🔒 Security Vulnerabilities
ImplementationThe implementation introduces CSS-based grouping ( Screenshots
How to Test
Get in Touch
Caution Package Vulnerabilities
⏳ Estimated code review effortLOW (~12 minutes) Tip Quality Recommendations
♫ Tanka Poem
Sequence DiagramsequenceDiagram
participant U as User
participant UI as Webview UI
participant S as State Manager
participant P as Persistence Layer
U->>UI: Click Toggle (handleToggle)
UI->>S: Update expanded/collapsed state
S-->>UI: New state (expanded: boolean)
Note over UI: Apply Tailwind group classes
UI->>UI: Transition icon (rotate/opacity)
Note over P: Title Sanitization Flow
P->>P: sanitizeTitle(rawTitle)
alt is JSON string
P->>P: JSON.parse()
P-->>UI: cleanTitle
else is Plain string
P-->>UI: rawTitle
end
|
- Fix: Handle JSON-wrapped title strings from server in fetchTaskTitle Support plain string, JSON object, and stringified JSON responses - Fix: Sanitize persisted corrupted titles in taskMetadata when reading back from stored message files - Fix: Normalize malformed titles in getTaskHistory before sending to webview (fixes history list display) - Fix: Normalize titles in ClineProvider.getTaskHistory for background task labels and currentTaskItem - Fix: Normalize title in KiloTaskHeader to handle raw ClineMessage title values that bypass HistoryItem sanitization - chore: bump version to 6.2.3 - chore: update kilocode models - chore: update openrouter spec tests - chore: update exploration group row, out of credits banner, pretty model name, openrouter hooks - chore: update pnpm lock and vscodeignore
2a15f73 to
786ac03
Compare
There was a problem hiding this comment.
🧪 PR Review is completed: Reviewed 12 files across the v6.2.3 release. Found 3 issues: code duplication of normalizeTitle across 3 files, a potential prototype pollution vulnerability in sanitizeTitle, and a missing fast-xml-parser dependency version bump in src/package.json.
Skipped files
pnpm-lock.yaml: Skipped file patternsrc/.vscodeignore: Skipped file pattern
⬇️ Low Priority Suggestions (5)
src/core/task-persistence/taskMetadata.ts (2 suggestions)
Location:
src/core/task-persistence/taskMetadata.ts(Lines 36-39)🔴 Security
Issue: The
sanitizeTitlefunction usesJSON.parseon untrusted input strings that start with{or[. While this is intended to handle malformed titles, parsing JSON from an external API response without validation could lead to prototype pollution if the parsed object contains__proto__orconstructorkeys. The recursive call tosanitizeTitle(parsed)then accesses(raw as Record<string, unknown>)["title"]which could trigger prototype chain access.Fix: Use
Object.create(null)for the parsed object check or add a__proto__/constructorkey filter before accessing properties. Alternatively, use a safer extraction method that doesn't access arbitrary object properties.Impact: Prevents prototype pollution attacks from malicious API responses
- try { - const parsed = JSON.parse(trimmed) - const extracted = sanitizeTitle(parsed) - if (extracted) return extracted + const parsed = JSON.parse(trimmed) + if (parsed != null && typeof parsed === "object" && !Array.isArray(parsed)) { + const safeObj = Object.create(null) + for (const key of Object.keys(parsed)) { + if (key !== "__proto__" && key !== "constructor") { + safeObj[key] = parsed[key] + } + } + const extracted = sanitizeTitle(safeObj) + if (extracted) return extracted + } +Location:
src/core/task-persistence/taskMetadata.ts(Lines 92-114)🟡 Code Quality
Issue: The
sanitizeTitlefunction and the inline response parsing logic infetchTaskTitleduplicate the same JSON-parsing-and-title-extraction pattern. Additionally,ClineProvider.tsandgetTaskHistory.tsboth contain nearly identicalnormalizeTitlefunctions. This is a DRY violation that increases maintenance burden and risk of inconsistent behavior.Fix: Extract a shared
sanitizeTitleutility to a common module (e.g.,src/shared/kilocode/sanitizeTitle.ts) and import it in all three locations.Impact: Reduces code duplication, ensures consistent behavior, and simplifies future changes to title normalization logic
- const data = response.data - - if (typeof data === "string") { - // Server responded with a string — try parsing as JSON if it looks like one - const trimmed = data.trim() - if (trimmed.startsWith("{") || trimmed.startsWith("[")) { - try { - const parsed = JSON.parse(trimmed) - if (parsed?.title) { - return parsed.title - } - } catch { - // Not valid JSON, fall through to use as plain string - } - } - if (trimmed) { - return trimmed - } - } else if (typeof data === "object" && data !== null) { - // Server responded with an object — extract title regardless of other fields - if (data.title) { - return data.title - } + const data = response.data + + const extracted = sanitizeTitle(data) + if (extracted) { + return extracted + } +
src/package.json (1 suggestion)
Location:
src/package.json(Lines 616-616)🟠 Dependency Issue
Issue: The
fast-xml-parserdependency insrc/package.jsonis being added as"^5.3.5", but the existingsrc/package.jsonalready has"fast-xml-parser": "^5.0.0"(per grep results). The PR showssrc/package.jsongetting a newfast-xml-parserline at [L616], but this appears to be a duplicate or version conflict since the package already exists. Additionally,cli/package.jsonbumps from^5.0.0to^5.3.5, butsrc/package.jsonshould be checked for consistency.Fix: Verify if
src/package.jsonalready containsfast-xml-parserand avoid duplicate entries. Ensure version alignment acrosspackage.json,cli/package.json, andsrc/package.json.Impact: Prevents duplicate dependency entries and version mismatches that could cause build or runtime issues
- "fast-xml-parser": "^5.3.5", + "fast-xml-parser": "^5.3.5", +
webview-ui/src/components/kilocode/KiloTaskHeader.tsx (1 suggestion)
Location:
webview-ui/src/components/kilocode/KiloTaskHeader.tsx(Lines 13-33)🟡 Code Quality
Issue: The
normalizeTitlefunction inKiloTaskHeader.tsxis a near-exact duplicate of the same function inClineProvider.tsandgetTaskHistory.ts. This triplication increases maintenance overhead and risks divergence in behavior.Fix: Import a shared
normalizeTitle/sanitizeTitleutility from a common module instead of redefining it.Impact: Improves maintainability and ensures consistent title normalization across the codebase
- /** - * Normalize a title that may have been stored as a JSON object string - * (e.g. `'{"title":"My Title"}'`) instead of a plain string. - */ - function normalizeTitle(raw: string | undefined): string | undefined { - if (!raw) return undefined - const trimmed = raw.trim() - if (!trimmed) return undefined - if (trimmed.startsWith("{") || trimmed.startsWith("[")) { - try { - const parsed = JSON.parse(trimmed) - if (typeof parsed === "object") { - const maybe = parsed?.title - if (typeof maybe === "string" && maybe.trim()) return maybe.trim() - } - } catch { - // not valid JSON, return as-is - } - } - return trimmed - } + import { normalizeTitle } from "@shared/kilocode/sanitizeTitle" +
src/shared/kilocode/getTaskHistory.ts (1 suggestion)
Location:
src/shared/kilocode/getTaskHistory.ts(Lines 6-33)🟡 Code Quality
Issue: The
normalizeTitlefunction ingetTaskHistory.tsduplicates the same logic inClineProvider.tsandKiloTaskHeader.tsx.Fix: Replace the local implementation with an import from a shared utility module.
Impact: Reduces duplication and ensures consistent behavior
- /** - * Normalize a potentially malformed title string. - * The server or persisted data may store the title as a JSON object string - * like `'{"title":"My Title"}'` instead of just `"My Title"`. - */ - function normalizeTitle(raw: string | undefined): string | undefined { - if (!raw) return undefined - - const trimmed = raw.trim() - if (!trimmed) return undefined - - // If it looks like a JSON object, try to extract the title property - if (trimmed.startsWith("{") || trimmed.startsWith("[")) { - try { - const parsed = JSON.parse(trimmed) - if (typeof parsed === "object") { - const maybe = parsed?.title - if (typeof maybe === "string" && maybe.trim()) { - return maybe.trim() - } - } - } catch { - // Not valid JSON, return as-is - } - } - - return trimmed - } + import { normalizeTitle } from "@shared/kilocode/sanitizeTitle" +
|
Note PR Review SkippedPR review skipped as no relevant changes found due to large diff hunk OR part of a non-reviewable file. 📄Files skipped in review
💡Tips to use MatterAICommand List
|
There was a problem hiding this comment.
🧪 PR Review is completed: Reviewed 11 files. Found 3 issues: duplicate title normalization logic across 3 files (DRY violation), a missing key prop in mapped history items, and a potential infinite loop in recursive sanitizeTitle.
Skipped files
CHANGELOG.md: Skipped file patternpnpm-lock.yaml: Skipped file patternsrc/.vscodeignore: Skipped file pattern
⬇️ Low Priority Suggestions (3)
src/core/webview/ClineProvider.ts (1 suggestion)
Location:
src/core/webview/ClineProvider.ts(Lines 3707-3713)🟠 Code Quality
Issue: The
getTaskHistory()method maps over history items and spreads them into new objects, but doesn't preserve the original item's type structure properly. ThenormalizeTitlecall usesitem.titlewhich may be undefined, but more critically, this duplicates the normalization logic already present ingetTaskHistory.tsandKiloTaskHeader.tsx.Fix: Since
getTaskHistory.tsalready normalizes titles before sending to webview, andKiloTaskHeader.tsxnormalizes on display, this third normalization layer inClineProvideris redundant. Remove it to avoid triple-normalization and potential inconsistencies.Impact: Reduces code duplication and prevents divergent normalization behavior across layers
- public getTaskHistory(): HistoryItem[] { - const history = this.getGlobalState("taskHistory") || [] - return history.map((item) => ({ - ...item, - title: this.normalizeTitle(item.title) ?? item.task, - })) - } + public getTaskHistory(): HistoryItem[] { + return this.getGlobalState("taskHistory") || [] + }
webview-ui/src/components/kilocode/KiloTaskHeader.tsx (1 suggestion)
Location:
webview-ui/src/components/kilocode/KiloTaskHeader.tsx(Lines 13-33)🔵 Code Quality
Issue: The
normalizeTitlefunction is duplicated across three files (taskMetadata.ts,getTaskHistory.ts, andKiloTaskHeader.tsx). This violates DRY and risks inconsistent behavior if the normalization rules diverge in future changes.Fix: Extract
normalizeTitleto a shared utility module (e.g.,src/shared/kilocode/normalizeTitle.ts) and import it in all three locations.Impact: Improves maintainability and ensures consistent title normalization across all layers
- /** - * Normalize a title that may have been stored as a JSON object string - * (e.g. `'{"title":"My Title"}'`) instead of a plain string. - */ - function normalizeTitle(raw: string | undefined): string | undefined { - if (!raw) return undefined - const trimmed = raw.trim() - if (!trimmed) return undefined - if (trimmed.startsWith("{") || trimmed.startsWith("[")) { - try { - const parsed = JSON.parse(trimmed) - if (typeof parsed === "object") { - const maybe = parsed?.title - if (typeof maybe === "string" && maybe.trim()) return maybe.trim() - } - } catch { - // not valid JSON, return as-is - } - } - return trimmed - } + import { normalizeTitle } from "@shared/kilocode/normalizeTitle"
src/core/task-persistence/taskMetadata.ts (1 suggestion)
Location:
src/core/task-persistence/taskMetadata.ts(Lines 92-114)🟡 Code Quality
Issue: The
fetchTaskTitlefunction's response handling at lines 92-114 duplicates JSON parsing logic already present insanitizeTitle. After gettingresponse.data, it manually checks for string vs object and parses JSON strings, butsanitizeTitlealready handles all these cases. The manual handling is redundant and could return un-normalized values.Fix: Replace the manual response parsing with a single call to
sanitizeTitle(response.data).Impact: Eliminates ~20 lines of redundant code and ensures consistent title extraction
- const data = response.data - - if (typeof data === "string") { - // Server responded with a string — try parsing as JSON if it looks like one - const trimmed = data.trim() - if (trimmed.startsWith("{") || trimmed.startsWith("[")) { - try { - const parsed = JSON.parse(trimmed) - if (parsed?.title) { - return parsed.title - } - } catch { - // Not valid JSON, fall through to use as plain string - } - } - if (trimmed) { - return trimmed - } - } else if (typeof data === "object" && data !== null) { - // Server responded with an object — extract title regardless of other fields - if (data.title) { - return data.title - } + const data = response.data + const title = sanitizeTitle(data) + if (title) { + return title + }
| function sanitizeTitle(raw: unknown): string | undefined { | ||
| if (raw == null) return undefined | ||
|
|
||
| if (typeof raw === "string") { | ||
| const trimmed = raw.trim() | ||
| if (!trimmed) return undefined | ||
|
|
||
| // Try parsing as JSON if it looks like an object | ||
| if (trimmed.startsWith("{") || trimmed.startsWith("[")) { | ||
| try { | ||
| const parsed = JSON.parse(trimmed) | ||
| const extracted = sanitizeTitle(parsed) | ||
| if (extracted) return extracted | ||
| } catch { | ||
| // Not valid JSON, fall through | ||
| } | ||
| } | ||
|
|
||
| return trimmed | ||
| } | ||
|
|
||
| if (typeof raw === "object") { | ||
| // Check for .title property (string or nested object/string) | ||
| const maybe = (raw as Record<string, unknown>)["title"] | ||
| if (maybe != null) { | ||
| // Recurse to handle nested cases like { title: { title: "..." } } | ||
| const extracted = sanitizeTitle(maybe) | ||
| if (extracted) return extracted | ||
| } | ||
| } | ||
|
|
||
| return undefined |
There was a problem hiding this comment.
🟠 Code Quality
Issue: The sanitizeTitle function contains a recursive call that can cause infinite recursion when given a circular object or deeply nested structure. The recursion at line 38 (sanitizeTitle(parsed)) and line 53 (sanitizeTitle(maybe)) has no depth limit, making it vulnerable to stack overflow.
Fix: Add a maxDepth parameter with a sensible default (e.g., 3) to prevent unbounded recursion.
Impact: Prevents potential stack overflow crashes from malformed/malicious input data
| function sanitizeTitle(raw: unknown): string | undefined { | |
| if (raw == null) return undefined | |
| if (typeof raw === "string") { | |
| const trimmed = raw.trim() | |
| if (!trimmed) return undefined | |
| // Try parsing as JSON if it looks like an object | |
| if (trimmed.startsWith("{") || trimmed.startsWith("[")) { | |
| try { | |
| const parsed = JSON.parse(trimmed) | |
| const extracted = sanitizeTitle(parsed) | |
| if (extracted) return extracted | |
| } catch { | |
| // Not valid JSON, fall through | |
| } | |
| } | |
| return trimmed | |
| } | |
| if (typeof raw === "object") { | |
| // Check for .title property (string or nested object/string) | |
| const maybe = (raw as Record<string, unknown>)["title"] | |
| if (maybe != null) { | |
| // Recurse to handle nested cases like { title: { title: "..." } } | |
| const extracted = sanitizeTitle(maybe) | |
| if (extracted) return extracted | |
| } | |
| } | |
| return undefined | |
| function sanitizeTitle(raw: unknown, maxDepth: number = 3): string | undefined { | |
| if (maxDepth <= 0) return undefined | |
| if (raw == null) return undefined | |
| if (typeof raw === "string") { | |
| const trimmed = raw.trim() | |
| if (!trimmed) return undefined | |
| // Try parsing as JSON if it looks like an object | |
| if (trimmed.startsWith("{") || trimmed.startsWith("[")) { | |
| try { | |
| const parsed = JSON.parse(trimmed) | |
| const extracted = sanitizeTitle(parsed, maxDepth - 1) | |
| if (extracted) return extracted | |
| } catch { | |
| // Not valid JSON, fall through | |
| } | |
| } | |
| return trimmed | |
| } | |
| if (typeof raw === "object") { | |
| // Check for .title property (string or nested object/string) | |
| const maybe = (raw as Record<string, unknown>)["title"] | |
| if (maybe != null) { | |
| // Recurse to handle nested cases like { title: { title: "..." } } | |
| const extracted = sanitizeTitle(maybe, maxDepth - 1) | |
| if (extracted) return extracted | |
| } | |
| } | |
| return undefined | |
| } |
…over scoping with ReasoningBlock
|
✅ Reviewed the changes: Reviewed webview-ui/src/components/chat/ExplorationGroupRow.tsx: no issues found. |
v6.2.3
Fixes
fetchTaskTitle— support plain string, JSON object, and stringified JSON responsestaskMetadata— sanitize when reading back from stored message filesgetTaskHistory— normalize before sending to webview (history list)ClineProvider.getTaskHistory— clean titles for background task labels andcurrentTaskItemKiloTaskHeader— normalize rawClineMessagetitle valuesChores